Update to latest gps

This update is to catch the changes to NewLockedProject's signature, but
is mostly about getting us the fixes to ProjectIdentifier.NetworkName.
diff --git a/cfg/lock.go b/cfg/lock.go
index fa5b018..0d91363 100644
--- a/cfg/lock.go
+++ b/cfg/lock.go
@@ -156,7 +156,11 @@
 			v = r
 		}
 
-		lp[k] = gps.NewLockedProject(gps.ProjectRoot(l.Name), v, l.Repository, nil)
+		id := gps.ProjectIdentifier{
+			ProjectRoot: gps.ProjectRoot(l.Name),
+			NetworkName: l.Repository,
+		}
+		lp[k] = gps.NewLockedProject(id, v, nil)
 	}
 
 	return lp
diff --git a/glide.lock b/glide.lock
index a5d5c91..0482f9d 100644
--- a/glide.lock
+++ b/glide.lock
@@ -10,7 +10,7 @@
 - name: github.com/Masterminds/vcs
   version: fbe9fb6ad5b5f35b3e82a7c21123cfc526cbf895
 - name: github.com/sdboyer/gps
-  version: f6e74e8d79b34ee6954d0cc8b770758ec99870ad
+  version: 6e8a101af5e735feedcdae1f716bebc338b74525
 - name: github.com/termie/go-shutil
   version: bcacb06fecaeec8dc42af03c87c6949f4a05c74c
 - name: gopkg.in/yaml.v2
diff --git a/gom/gom.go b/gom/gom.go
index 08f0c2c..a33601d 100644
--- a/gom/gom.go
+++ b/gom/gom.go
@@ -143,23 +143,27 @@
 			dep.Constraint = gps.NewBranch(body)
 		}
 
+		id := gps.ProjectIdentifier{
+			ProjectRoot: gps.ProjectRoot(dir),
+		}
+		var version gps.Version
 		if val, ok := gom.options["commit"]; ok {
 			body := val.(string)
 			if v != nil {
-				v.Is(gps.Revision(body))
-				l = append(l, gps.NewLockedProject(gps.ProjectRoot(dir), v, dir, nil))
+				version = v.Is(gps.Revision(body))
 			} else {
 				// As with the other third-party system integrations, we're
 				// going to choose not to put revisions into a manifest, even
 				// though gom has a lot more information than most and the
 				// argument could be made for it.
 				dep.Constraint = gps.Any()
-				l = append(l, gps.NewLockedProject(gps.ProjectRoot(dir), gps.Revision(body), dir, nil))
+				version = gps.Revision(body)
 			}
 		} else if v != nil {
 			// This is kinda uncomfortable - lock w/no immut - but OK
-			l = append(l, gps.NewLockedProject(gps.ProjectRoot(dir), v, dir, nil))
+			version = v
 		}
+		l = append(l, gps.NewLockedProject(id, version, nil))
 
 		// TODO We ignore GOOS, GOARCH for now
 	}
diff --git a/vendor/github.com/sdboyer/gps/bridge.go b/vendor/github.com/sdboyer/gps/bridge.go
index 298b023..a7368e3 100644
--- a/vendor/github.com/sdboyer/gps/bridge.go
+++ b/vendor/github.com/sdboyer/gps/bridge.go
@@ -4,7 +4,6 @@
 	"fmt"
 	"os"
 	"path/filepath"
-	"sort"
 	"sync/atomic"
 
 	"github.com/Masterminds/semver"
@@ -91,9 +90,9 @@
 	}
 
 	if b.s.params.Downgrade {
-		sort.Sort(downgradeVersionSorter(vl))
+		SortForDowngrade(vl)
 	} else {
-		sort.Sort(upgradeVersionSorter(vl))
+		SortForUpgrade(vl)
 	}
 
 	b.vlists[id] = vl
@@ -556,96 +555,3 @@
 }
 
 func (av versionTypeUnion) _private() {}
-
-type upgradeVersionSorter []Version
-type downgradeVersionSorter []Version
-
-func (vs upgradeVersionSorter) Len() int {
-	return len(vs)
-}
-
-func (vs upgradeVersionSorter) Swap(i, j int) {
-	vs[i], vs[j] = vs[j], vs[i]
-}
-
-func (vs downgradeVersionSorter) Len() int {
-	return len(vs)
-}
-
-func (vs downgradeVersionSorter) Swap(i, j int) {
-	vs[i], vs[j] = vs[j], vs[i]
-}
-
-func (vs upgradeVersionSorter) Less(i, j int) bool {
-	l, r := vs[i], vs[j]
-
-	if tl, ispair := l.(versionPair); ispair {
-		l = tl.v
-	}
-	if tr, ispair := r.(versionPair); ispair {
-		r = tr.v
-	}
-
-	switch compareVersionType(l, r) {
-	case -1:
-		return true
-	case 1:
-		return false
-	case 0:
-		break
-	default:
-		panic("unreachable")
-	}
-
-	switch l.(type) {
-	// For these, now nothing to do but alpha sort
-	case Revision, branchVersion, plainVersion:
-		return l.String() < r.String()
-	}
-
-	// This ensures that pre-release versions are always sorted after ALL
-	// full-release versions
-	lsv, rsv := l.(semVersion).sv, r.(semVersion).sv
-	lpre, rpre := lsv.Prerelease() == "", rsv.Prerelease() == ""
-	if (lpre && !rpre) || (!lpre && rpre) {
-		return lpre
-	}
-	return lsv.GreaterThan(rsv)
-}
-
-func (vs downgradeVersionSorter) Less(i, j int) bool {
-	l, r := vs[i], vs[j]
-
-	if tl, ispair := l.(versionPair); ispair {
-		l = tl.v
-	}
-	if tr, ispair := r.(versionPair); ispair {
-		r = tr.v
-	}
-
-	switch compareVersionType(l, r) {
-	case -1:
-		return true
-	case 1:
-		return false
-	case 0:
-		break
-	default:
-		panic("unreachable")
-	}
-
-	switch l.(type) {
-	// For these, now nothing to do but alpha
-	case Revision, branchVersion, plainVersion:
-		return l.String() < r.String()
-	}
-
-	// This ensures that pre-release versions are always sorted after ALL
-	// full-release versions
-	lsv, rsv := l.(semVersion).sv, r.(semVersion).sv
-	lpre, rpre := lsv.Prerelease() == "", rsv.Prerelease() == ""
-	if (lpre && !rpre) || (!lpre && rpre) {
-		return lpre
-	}
-	return lsv.LessThan(rsv)
-}
diff --git a/vendor/github.com/sdboyer/gps/constraints.go b/vendor/github.com/sdboyer/gps/constraints.go
index 794100e..cf1b484 100644
--- a/vendor/github.com/sdboyer/gps/constraints.go
+++ b/vendor/github.com/sdboyer/gps/constraints.go
@@ -192,7 +192,7 @@
 
 	for _, pc := range l {
 		final[pc.Ident.ProjectRoot] = ProjectProperties{
-			NetworkName: pc.Ident.netName(),
+			NetworkName: pc.Ident.NetworkName,
 			Constraint:  pc.Constraint,
 		}
 	}
@@ -207,7 +207,7 @@
 				final[pc.Ident.ProjectRoot] = pp
 			} else {
 				final[pc.Ident.ProjectRoot] = ProjectProperties{
-					NetworkName: pc.Ident.netName(),
+					NetworkName: pc.Ident.NetworkName,
 					Constraint:  pc.Constraint,
 				}
 			}
@@ -257,7 +257,7 @@
 // ProjectConstraints map.
 func (m ProjectConstraints) override(pc ProjectConstraint) workingConstraint {
 	wc := workingConstraint{
-		Ident:      pc.Ident.normalize(), // necessary to normalize?
+		Ident:      pc.Ident,
 		Constraint: pc.Constraint,
 	}
 
diff --git a/vendor/github.com/sdboyer/gps/glide.yaml b/vendor/github.com/sdboyer/gps/glide.yaml
index 690f9e1..5e379fa 100644
--- a/vendor/github.com/sdboyer/gps/glide.yaml
+++ b/vendor/github.com/sdboyer/gps/glide.yaml
@@ -2,13 +2,8 @@
 owners:
 - name: Sam Boyer
   email: tech@samboyer.org
-import:
+dependencies:
 - package: github.com/Masterminds/semver
   branch: 2.x
-  vcs: git
-- package: github.com/Masterminds/vcs
-  vcs: git
 - package: github.com/termie/go-shutil
   version: bcacb06fecaeec8dc42af03c87c6949f4a05c74c
-  vcs: git
-- package: github.com/armon/go-radix
diff --git a/vendor/github.com/sdboyer/gps/hash_test.go b/vendor/github.com/sdboyer/gps/hash_test.go
index 171f377..f356ced 100644
--- a/vendor/github.com/sdboyer/gps/hash_test.go
+++ b/vendor/github.com/sdboyer/gps/hash_test.go
@@ -26,10 +26,8 @@
 
 	elems := []string{
 		"a",
-		"a",
 		"1.0.0",
 		"b",
-		"b",
 		"1.0.0",
 		stdlibPkgs,
 		appenginePkgs,
@@ -75,10 +73,8 @@
 
 	elems := []string{
 		"a",
-		"a",
 		"1.0.0",
 		"b",
-		"b",
 		"1.0.0",
 		stdlibPkgs,
 		appenginePkgs,
@@ -129,10 +125,8 @@
 
 	elems := []string{
 		"a",
-		"a",
 		"1.0.0",
 		"b",
-		"b",
 		"1.0.0",
 		stdlibPkgs,
 		appenginePkgs,
@@ -168,10 +162,8 @@
 
 	elems = []string{
 		"a",
-		"a",
 		"1.0.0",
 		"b",
-		"b",
 		"1.0.0",
 		stdlibPkgs,
 		appenginePkgs,
@@ -210,10 +202,8 @@
 
 	elems = []string{
 		"a",
-		"a",
 		"1.0.0",
 		"b",
-		"b",
 		"1.0.0",
 		stdlibPkgs,
 		appenginePkgs,
@@ -254,10 +244,8 @@
 
 	elems = []string{
 		"a",
-		"a",
 		"fluglehorn",
 		"b",
-		"b",
 		"1.0.0",
 		stdlibPkgs,
 		appenginePkgs,
@@ -303,7 +291,6 @@
 		"nota",
 		"1.0.0",
 		"b",
-		"b",
 		"1.0.0",
 		stdlibPkgs,
 		appenginePkgs,
@@ -350,7 +337,6 @@
 		"nota",
 		"fluglehorn",
 		"b",
-		"b",
 		"1.0.0",
 		stdlibPkgs,
 		appenginePkgs,
diff --git a/vendor/github.com/sdboyer/gps/lock.go b/vendor/github.com/sdboyer/gps/lock.go
index 1d4db56..729d501 100644
--- a/vendor/github.com/sdboyer/gps/lock.go
+++ b/vendor/github.com/sdboyer/gps/lock.go
@@ -1,5 +1,7 @@
 package gps
 
+import "sort"
+
 // Lock represents data from a lock file (or however the implementing tool
 // chooses to store it) at a particular version that is relevant to the
 // satisfiability solving process.
@@ -56,16 +58,13 @@
 // to simply dismiss that project. By creating a hard failure case via panic
 // instead, we are trying to avoid inflicting the resulting pain on the user by
 // instead forcing a decision on the Analyzer implementation.
-func NewLockedProject(n ProjectRoot, v Version, url string, pkgs []string) LockedProject {
+func NewLockedProject(id ProjectIdentifier, v Version, pkgs []string) LockedProject {
 	if v == nil {
 		panic("must provide a non-nil version to create a LockedProject")
 	}
 
 	lp := LockedProject{
-		pi: ProjectIdentifier{
-			ProjectRoot: n,
-			NetworkName: url,
-		},
+		pi:   id,
 		pkgs: pkgs,
 	}
 
@@ -136,26 +135,36 @@
 	return sl.p
 }
 
-// prepLock ensures a lock is prepared and safe for use by the solver.
-// This entails two things:
-//
-//  * Ensuring that all LockedProject's identifiers are normalized.
-//  * Defensively ensuring that no outside routine can modify the lock while the
-//  solver is in-flight.
+// prepLock ensures a lock is prepared and safe for use by the solver. This is
+// mostly about defensively ensuring that no outside routine can modify the lock
+// while the solver is in-flight.
 //
 // This is achieved by copying the lock's data into a new safeLock.
 func prepLock(l Lock) Lock {
 	pl := l.Projects()
 
-	rl := safeLock{
-		h: l.InputHash(),
-		p: make([]LockedProject, len(pl)),
-	}
-
-	for k, lp := range pl {
-		lp.pi = lp.pi.normalize()
-		rl.p[k] = lp
-	}
+	rl := safeLock{h: l.InputHash()}
+	copy(rl.p, pl)
 
 	return rl
 }
+
+// SortLockedProjects sorts a slice of LockedProject in alphabetical order by
+// ProjectRoot.
+func SortLockedProjects(lps []LockedProject) {
+	sort.Stable(lpsorter(lps))
+}
+
+type lpsorter []LockedProject
+
+func (lps lpsorter) Swap(i, j int) {
+	lps[i], lps[j] = lps[j], lps[i]
+}
+
+func (lps lpsorter) Len() int {
+	return len(lps)
+}
+
+func (lps lpsorter) Less(i, j int) bool {
+	return lps[i].pi.ProjectRoot < lps[j].pi.ProjectRoot
+}
diff --git a/vendor/github.com/sdboyer/gps/lock_test.go b/vendor/github.com/sdboyer/gps/lock_test.go
new file mode 100644
index 0000000..b580502
--- /dev/null
+++ b/vendor/github.com/sdboyer/gps/lock_test.go
@@ -0,0 +1,26 @@
+package gps
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestLockedProjectSorting(t *testing.T) {
+	// version doesn't matter here
+	lps := []LockedProject{
+		NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0"), nil),
+		NewLockedProject(mkPI("foo"), NewVersion("nada"), nil),
+		NewLockedProject(mkPI("bar"), NewVersion("zip"), nil),
+		NewLockedProject(mkPI("qux"), NewVersion("zilch"), nil),
+	}
+	lps2 := make([]LockedProject, len(lps))
+	copy(lps2, lps)
+
+	SortLockedProjects(lps2)
+
+	// only the two should have switched positions
+	lps[0], lps[2] = lps[2], lps[0]
+	if !reflect.DeepEqual(lps, lps2) {
+		t.Errorf("SortLockedProject did not sort as expected:\n\t(GOT) %s\n\t(WNT) %s", lps2, lps)
+	}
+}
diff --git a/vendor/github.com/sdboyer/gps/manager_test.go b/vendor/github.com/sdboyer/gps/manager_test.go
index df85293..f3892d6 100644
--- a/vendor/github.com/sdboyer/gps/manager_test.go
+++ b/vendor/github.com/sdboyer/gps/manager_test.go
@@ -7,7 +7,6 @@
 	"path"
 	"path/filepath"
 	"runtime"
-	"sort"
 	"sync"
 	"testing"
 
@@ -124,7 +123,7 @@
 		}
 	}()
 
-	id := mkPI("github.com/Masterminds/VCSTestRepo")
+	id := mkPI("github.com/Masterminds/VCSTestRepo").normalize()
 	v, err := sm.ListVersions(id)
 	if err != nil {
 		t.Errorf("Unexpected error during initial project setup/fetching %s", err)
@@ -142,7 +141,7 @@
 
 		// SourceManager itself doesn't guarantee ordering; sort them here so we
 		// can dependably check output
-		sort.Sort(upgradeVersionSorter(v))
+		SortForUpgrade(v)
 
 		for k, e := range expected {
 			if v[k] != e {
@@ -220,7 +219,7 @@
 
 func TestMgrMethodsFailWithBadPath(t *testing.T) {
 	// a symbol will always bork it up
-	bad := mkPI("foo/##&^")
+	bad := mkPI("foo/##&^").normalize()
 	sm, clean := mkNaiveSM(t)
 	defer clean()
 
@@ -257,9 +256,9 @@
 	sm, clean := mkNaiveSM(t)
 
 	pil := []ProjectIdentifier{
-		mkPI("github.com/Masterminds/VCSTestRepo"),
-		mkPI("bitbucket.org/mattfarina/testhgrepo"),
-		mkPI("launchpad.net/govcstestbzrrepo"),
+		mkPI("github.com/Masterminds/VCSTestRepo").normalize(),
+		mkPI("bitbucket.org/mattfarina/testhgrepo").normalize(),
+		mkPI("launchpad.net/govcstestbzrrepo").normalize(),
 	}
 
 	wg := &sync.WaitGroup{}
@@ -325,7 +324,7 @@
 
 	// setup done, now do the test
 
-	id := mkPI("github.com/Masterminds/VCSTestRepo")
+	id := mkPI("github.com/Masterminds/VCSTestRepo").normalize()
 
 	_, _, err := sm.GetManifestAndLock(id, NewVersion("1.0.0"))
 	if err != nil {
diff --git a/vendor/github.com/sdboyer/gps/manifest.go b/vendor/github.com/sdboyer/gps/manifest.go
index 86d06cc..94513d0 100644
--- a/vendor/github.com/sdboyer/gps/manifest.go
+++ b/vendor/github.com/sdboyer/gps/manifest.go
@@ -92,12 +92,8 @@
 }
 
 // prepManifest ensures a manifest is prepared and safe for use by the solver.
-// This entails two things:
-//
-//  * Ensuring that all ProjectIdentifiers are normalized (otherwise matching
-//  can get screwy and the queues go out of alignment)
-//  * Defensively ensuring that no outside routine can modify the manifest while
-//  the solver is in-flight.
+// This is mostly about ensuring that no outside routine can modify the manifest
+// while the solver is in-flight.
 //
 // This is achieved by copying the manifest's data into a new SimpleManifest.
 func prepManifest(m Manifest) Manifest {
@@ -114,11 +110,9 @@
 	}
 
 	for k, d := range deps {
-		d.Ident = d.Ident.normalize()
 		rm.Deps[k] = d
 	}
 	for k, d := range ddeps {
-		d.Ident = d.Ident.normalize()
 		rm.TestDeps[k] = d
 	}
 
diff --git a/vendor/github.com/sdboyer/gps/result.go b/vendor/github.com/sdboyer/gps/result.go
index 00dac45..d62d06b 100644
--- a/vendor/github.com/sdboyer/gps/result.go
+++ b/vendor/github.com/sdboyer/gps/result.go
@@ -32,6 +32,10 @@
 // whether or not to strip vendor directories contained in the exported
 // dependencies.
 func WriteDepTree(basedir string, l Lock, sm SourceManager, sv bool) error {
+	if l == nil {
+		return fmt.Errorf("must provide non-nil Lock to WriteDepTree")
+	}
+
 	err := os.MkdirAll(basedir, 0777)
 	if err != nil {
 		return err
@@ -49,7 +53,7 @@
 		err = sm.ExportProject(p.Ident(), p.Version(), to)
 		if err != nil {
 			removeAll(basedir)
-			return fmt.Errorf("Error while exporting %s: %s", p.Ident().ProjectRoot, err)
+			return fmt.Errorf("error while exporting %s: %s", p.Ident().ProjectRoot, err)
 		}
 		if sv {
 			filepath.Walk(to, stripVendor)
diff --git a/vendor/github.com/sdboyer/gps/result_test.go b/vendor/github.com/sdboyer/gps/result_test.go
index 2ae07ec..ac98678 100644
--- a/vendor/github.com/sdboyer/gps/result_test.go
+++ b/vendor/github.com/sdboyer/gps/result_test.go
@@ -37,10 +37,10 @@
 	}
 }
 
-func TestResultCreateVendorTree(t *testing.T) {
+func TestWriteDepTree(t *testing.T) {
 	// This test is a bit slow, skip it on -short
 	if testing.Short() {
-		t.Skip("Skipping vendor tree creation test in short mode")
+		t.Skip("Skipping dep tree writing test in short mode")
 	}
 
 	r := basicResult
@@ -51,7 +51,13 @@
 	sm, clean := mkNaiveSM(t)
 	defer clean()
 
-	err := WriteDepTree(path.Join(tmp, "export"), r, sm, true)
+	// nil lock/result should err immediately
+	err := WriteDepTree(path.Join(tmp, "export"), nil, sm, true)
+	if err == nil {
+		t.Errorf("Should error if nil lock is passed to WriteDepTree")
+	}
+
+	err = WriteDepTree(path.Join(tmp, "export"), r, sm, true)
 	if err != nil {
 		t.Errorf("Unexpected error while creating vendor tree: %s", err)
 	}
diff --git a/vendor/github.com/sdboyer/gps/satisfy.go b/vendor/github.com/sdboyer/gps/satisfy.go
index ef9e688..78cffa0 100644
--- a/vendor/github.com/sdboyer/gps/satisfy.go
+++ b/vendor/github.com/sdboyer/gps/satisfy.go
@@ -194,22 +194,20 @@
 // network source is.
 func (s *solver) checkIdentMatches(a atomWithPackages, cdep completeDep) error {
 	dep := cdep.workingConstraint
-	if cur, exists := s.names[dep.Ident.ProjectRoot]; exists {
-		if cur != dep.Ident.netName() {
-			deps := s.sel.getDependenciesOn(a.a.id)
-			// Fail all the other deps, as there's no way atom can ever be
-			// compatible with them
-			for _, d := range deps {
-				s.fail(d.depender.id)
-			}
+	if curid, has := s.sel.getIdentFor(dep.Ident.ProjectRoot); has && !curid.equiv(dep.Ident) {
+		deps := s.sel.getDependenciesOn(a.a.id)
+		// Fail all the other deps, as there's no way atom can ever be
+		// compatible with them
+		for _, d := range deps {
+			s.fail(d.depender.id)
+		}
 
-			return &sourceMismatchFailure{
-				shared:   dep.Ident.ProjectRoot,
-				sel:      deps,
-				current:  cur,
-				mismatch: dep.Ident.netName(),
-				prob:     a.a,
-			}
+		return &sourceMismatchFailure{
+			shared:   dep.Ident.ProjectRoot,
+			sel:      deps,
+			current:  curid.netName(),
+			mismatch: dep.Ident.netName(),
+			prob:     a.a,
 		}
 	}
 
diff --git a/vendor/github.com/sdboyer/gps/selection.go b/vendor/github.com/sdboyer/gps/selection.go
index 9362fb0..7f03c51 100644
--- a/vendor/github.com/sdboyer/gps/selection.go
+++ b/vendor/github.com/sdboyer/gps/selection.go
@@ -2,7 +2,7 @@
 
 type selection struct {
 	projects []selected
-	deps     map[ProjectIdentifier][]dependency
+	deps     map[ProjectRoot][]dependency
 	sm       sourceBridge
 }
 
@@ -12,13 +12,29 @@
 }
 
 func (s *selection) getDependenciesOn(id ProjectIdentifier) []dependency {
-	if deps, exists := s.deps[id]; exists {
+	if deps, exists := s.deps[id.ProjectRoot]; exists {
 		return deps
 	}
 
 	return nil
 }
 
+// getIdentFor returns the ProjectIdentifier (so, the network name) currently in
+// use for the provided ProjectRoot.
+//
+// If no dependencies are present yet that designate a network name for
+// the provided root, this will return an empty ProjectIdentifier and false.
+func (s *selection) getIdentFor(pr ProjectRoot) (ProjectIdentifier, bool) {
+	deps := s.getDependenciesOn(ProjectIdentifier{ProjectRoot: pr})
+	if len(deps) == 0 {
+		return ProjectIdentifier{}, false
+	}
+
+	// For now, at least, the solver maintains (assumes?) the invariant that
+	// whatever is first in the deps list decides the net name to be used.
+	return deps[0].dep.Ident, true
+}
+
 // pushSelection pushes a new atomWithPackages onto the selection stack, along
 // with an indicator as to whether this selection indicates a new project *and*
 // packages, or merely some new packages on a project that was already selected.
@@ -40,21 +56,21 @@
 }
 
 func (s *selection) pushDep(dep dependency) {
-	s.deps[dep.dep.Ident] = append(s.deps[dep.dep.Ident], dep)
+	s.deps[dep.dep.Ident.ProjectRoot] = append(s.deps[dep.dep.Ident.ProjectRoot], dep)
 }
 
 func (s *selection) popDep(id ProjectIdentifier) (dep dependency) {
-	deps := s.deps[id]
-	dep, s.deps[id] = deps[len(deps)-1], deps[:len(deps)-1]
+	deps := s.deps[id.ProjectRoot]
+	dep, s.deps[id.ProjectRoot] = deps[len(deps)-1], deps[:len(deps)-1]
 	return dep
 }
 
 func (s *selection) depperCount(id ProjectIdentifier) int {
-	return len(s.deps[id])
+	return len(s.deps[id.ProjectRoot])
 }
 
 func (s *selection) setDependenciesOn(id ProjectIdentifier, deps []dependency) {
-	s.deps[id] = deps
+	s.deps[id.ProjectRoot] = deps
 }
 
 // Compute a list of the unique packages within the given ProjectIdentifier that
@@ -64,7 +80,7 @@
 	// precompute it on pushing a new dep, and preferably with an immut
 	// structure so that we can pop with zero cost.
 	uniq := make(map[string]int)
-	for _, dep := range s.deps[id] {
+	for _, dep := range s.deps[id.ProjectRoot] {
 		for _, pkg := range dep.dep.pl {
 			if count, has := uniq[pkg]; has {
 				count++
@@ -103,7 +119,7 @@
 }
 
 func (s *selection) getConstraint(id ProjectIdentifier) Constraint {
-	deps, exists := s.deps[id]
+	deps, exists := s.deps[id.ProjectRoot]
 	if !exists || len(deps) == 0 {
 		return any
 	}
@@ -133,7 +149,7 @@
 // have happened later.
 func (s *selection) selected(id ProjectIdentifier) (atomWithPackages, bool) {
 	for _, p := range s.projects {
-		if p.a.a.id.eq(id) {
+		if p.a.a.id.ProjectRoot == id.ProjectRoot {
 			return p.a, true
 		}
 	}
diff --git a/vendor/github.com/sdboyer/gps/solve_basic_test.go b/vendor/github.com/sdboyer/gps/solve_basic_test.go
index e4c1352..c0ca587 100644
--- a/vendor/github.com/sdboyer/gps/solve_basic_test.go
+++ b/vendor/github.com/sdboyer/gps/solve_basic_test.go
@@ -8,7 +8,7 @@
 	"github.com/Masterminds/semver"
 )
 
-var regfrom = regexp.MustCompile(`^(\w*) from (\w*) ([0-9\.]*)`)
+var regfrom = regexp.MustCompile(`^(\w*) from (\w*) ([0-9\.\*]*)`)
 
 // nvSplit splits an "info" string on " " into the pair of name and
 // version/constraint, and returns each individually.
@@ -28,9 +28,6 @@
 	}
 
 	id.ProjectRoot, version = ProjectRoot(s[0]), s[1]
-	if id.NetworkName == "" {
-		id.NetworkName = string(id.ProjectRoot)
-	}
 	return
 }
 
@@ -44,7 +41,7 @@
 func nvrSplit(info string) (id ProjectIdentifier, version string, revision Revision) {
 	if strings.Contains(info, " from ") {
 		parts := regfrom.FindStringSubmatch(info)
-		info = parts[1] + " " + parts[3]
+		info = fmt.Sprintf("%s %s", parts[1], parts[3])
 		id.NetworkName = parts[2]
 	}
 
@@ -54,9 +51,6 @@
 	}
 
 	id.ProjectRoot, version = ProjectRoot(s[0]), s[1]
-	if id.NetworkName == "" {
-		id.NetworkName = string(id.ProjectRoot)
-	}
 
 	if len(s) == 3 {
 		revision = Revision(s[2])
@@ -211,7 +205,7 @@
 // treated as a test-only dependency.
 func mkDepspec(pi string, deps ...string) depspec {
 	pa := mkAtom(pi)
-	if string(pa.id.ProjectRoot) != pa.id.NetworkName {
+	if string(pa.id.ProjectRoot) != pa.id.NetworkName && pa.id.NetworkName != "" {
 		panic("alternate source on self makes no sense")
 	}
 
@@ -249,7 +243,6 @@
 			workingConstraint: workingConstraint{
 				Ident: ProjectIdentifier{
 					ProjectRoot: ProjectRoot(pdep),
-					NetworkName: pdep,
 				},
 				Constraint: c,
 			},
@@ -259,11 +252,13 @@
 }
 
 // mkPI creates a ProjectIdentifier with the ProjectRoot as the provided
-// string, and with the NetworkName normalized to be the same.
+// string, and the NetworkName unset.
+//
+// Call normalize() on the returned value if you need the NetworkName to be be
+// equal to the ProjectRoot.
 func mkPI(root string) ProjectIdentifier {
 	return ProjectIdentifier{
 		ProjectRoot: ProjectRoot(root),
-		NetworkName: root,
 	}
 }
 
@@ -281,7 +276,7 @@
 	l := make(fixLock, 0)
 	for _, s := range pairs {
 		pa := mkAtom(s)
-		l = append(l, NewLockedProject(pa.id.ProjectRoot, pa.v, pa.id.netName(), nil))
+		l = append(l, NewLockedProject(pa.id, pa.v, nil))
 	}
 
 	return l
@@ -293,19 +288,18 @@
 	l := make(fixLock, 0)
 	for _, s := range pairs {
 		pa := mkAtom(s)
-		l = append(l, NewLockedProject(pa.id.ProjectRoot, pa.v.(PairedVersion).Underlying(), pa.id.netName(), nil))
+		l = append(l, NewLockedProject(pa.id, pa.v.(PairedVersion).Underlying(), nil))
 	}
 
 	return l
 }
 
 // mksolution makes a result set
-func mksolution(pairs ...string) map[string]Version {
-	m := make(map[string]Version)
+func mksolution(pairs ...string) map[ProjectIdentifier]Version {
+	m := make(map[ProjectIdentifier]Version)
 	for _, pair := range pairs {
 		a := mkAtom(pair)
-		// TODO(sdboyer) identifierify
-		m[string(a.id.ProjectRoot)] = a.v
+		m[a.id] = a.v
 	}
 
 	return m
@@ -356,7 +350,7 @@
 	rootmanifest() RootManifest
 	specs() []depspec
 	maxTries() int
-	solution() map[string]Version
+	solution() map[ProjectIdentifier]Version
 	failure() error
 }
 
@@ -380,7 +374,7 @@
 	// depspecs. always treat first as root
 	ds []depspec
 	// results; map of name/version pairs
-	r map[string]Version
+	r map[ProjectIdentifier]Version
 	// max attempts the solver should need to find solution. 0 means no limit
 	maxAttempts int
 	// Use downgrade instead of default upgrade sorter
@@ -407,7 +401,7 @@
 	return f.maxAttempts
 }
 
-func (f basicFixture) solution() map[string]Version {
+func (f basicFixture) solution() map[ProjectIdentifier]Version {
 	return f.r
 }
 
@@ -519,28 +513,6 @@
 		),
 		maxAttempts: 2,
 	},
-	"with mismatched net addrs": {
-		ds: []depspec{
-			mkDepspec("root 1.0.0", "foo 1.0.0", "bar 1.0.0"),
-			mkDepspec("foo 1.0.0", "bar from baz 1.0.0"),
-			mkDepspec("bar 1.0.0"),
-		},
-		fail: &noVersionError{
-			pn: mkPI("foo"),
-			fails: []failedVersion{
-				{
-					v: NewVersion("1.0.0"),
-					f: &sourceMismatchFailure{
-						shared:   ProjectRoot("bar"),
-						current:  "bar",
-						mismatch: "baz",
-						prob:     mkAtom("foo 1.0.0"),
-						sel:      []dependency{mkDep("root", "foo 1.0.0", "foo")},
-					},
-				},
-			},
-		},
-	},
 	// fixtures with locks
 	"with compatible locked dependency": {
 		ds: []depspec{
@@ -1098,7 +1070,7 @@
 		},
 		r: mksolution(
 			"foo 1.0.0",
-			"bar 1.0.0",
+			"bar from bar 1.0.0",
 		),
 	},
 
@@ -1204,7 +1176,7 @@
 
 func (sm *depspecSourceManager) GetManifestAndLock(id ProjectIdentifier, v Version) (Manifest, Lock, error) {
 	for _, ds := range sm.specs {
-		if id.ProjectRoot == ds.n && v.Matches(ds.v) {
+		if id.netName() == string(ds.n) && v.Matches(ds.v) {
 			return ds, dummyLock{}, nil
 		}
 	}
@@ -1218,7 +1190,7 @@
 }
 
 func (sm *depspecSourceManager) ExternalReach(id ProjectIdentifier, v Version) (map[string][]string, error) {
-	pid := pident{n: id.ProjectRoot, v: v}
+	pid := pident{n: ProjectRoot(id.netName()), v: v}
 	if m, exists := sm.rm[pid]; exists {
 		return m, nil
 	}
@@ -1227,7 +1199,7 @@
 
 func (sm *depspecSourceManager) ListExternal(id ProjectIdentifier, v Version) ([]string, error) {
 	// This should only be called for the root
-	pid := pident{n: id.ProjectRoot, v: v}
+	pid := pident{n: ProjectRoot(id.netName()), v: v}
 	if r, exists := sm.rm[pid]; exists {
 		return r[string(id.ProjectRoot)], nil
 	}
@@ -1235,18 +1207,17 @@
 }
 
 func (sm *depspecSourceManager) ListPackages(id ProjectIdentifier, v Version) (PackageTree, error) {
-	pid := pident{n: id.ProjectRoot, v: v}
-	n := id.ProjectRoot
+	pid := pident{n: ProjectRoot(id.netName()), v: v}
 
 	if r, exists := sm.rm[pid]; exists {
 		ptree := PackageTree{
-			ImportRoot: string(n),
+			ImportRoot: string(pid.n),
 			Packages: map[string]PackageOrErr{
-				string(n): {
+				string(pid.n): {
 					P: Package{
-						ImportPath: string(n),
-						Name:       string(n),
-						Imports:    r[string(n)],
+						ImportPath: string(pid.n),
+						Name:       string(pid.n),
+						Imports:    r[string(pid.n)],
 					},
 				},
 			},
@@ -1254,14 +1225,14 @@
 		return ptree, nil
 	}
 
-	return PackageTree{}, fmt.Errorf("Project %s at version %s could not be found", n, v)
+	return PackageTree{}, fmt.Errorf("Project %s at version %s could not be found", pid.n, v)
 }
 
 func (sm *depspecSourceManager) ListVersions(id ProjectIdentifier) (pi []Version, err error) {
 	for _, ds := range sm.specs {
 		// To simulate the behavior of the real SourceManager, we do not return
 		// revisions from ListVersions().
-		if _, isrev := ds.v.(Revision); !isrev && id.ProjectRoot == ds.n {
+		if _, isrev := ds.v.(Revision); !isrev && id.netName() == string(ds.n) {
 			pi = append(pi, ds.v)
 		}
 	}
@@ -1275,7 +1246,7 @@
 
 func (sm *depspecSourceManager) RevisionPresentIn(id ProjectIdentifier, r Revision) (bool, error) {
 	for _, ds := range sm.specs {
-		if id.ProjectRoot == ds.n && r == ds.v {
+		if id.netName() == string(ds.n) && r == ds.v {
 			return true, nil
 		}
 	}
@@ -1285,7 +1256,7 @@
 
 func (sm *depspecSourceManager) SourceExists(id ProjectIdentifier) (bool, error) {
 	for _, ds := range sm.specs {
-		if id.ProjectRoot == ds.n {
+		if id.netName() == string(ds.n) {
 			return true, nil
 		}
 	}
diff --git a/vendor/github.com/sdboyer/gps/solve_bimodal_test.go b/vendor/github.com/sdboyer/gps/solve_bimodal_test.go
index f62619d..9ebe483 100644
--- a/vendor/github.com/sdboyer/gps/solve_bimodal_test.go
+++ b/vendor/github.com/sdboyer/gps/solve_bimodal_test.go
@@ -516,6 +516,172 @@
 			"a 1.0.0",
 		),
 	},
+	"alternate net address": {
+		ds: []depspec{
+			dsp(mkDepspec("root 1.0.0", "foo from bar 2.0.0"),
+				pkg("root", "foo")),
+			dsp(mkDepspec("foo 1.0.0"),
+				pkg("foo")),
+			dsp(mkDepspec("foo 2.0.0"),
+				pkg("foo")),
+			dsp(mkDepspec("bar 1.0.0"),
+				pkg("foo")),
+			dsp(mkDepspec("bar 2.0.0"),
+				pkg("foo")),
+		},
+		r: mksolution(
+			"foo from bar 2.0.0",
+		),
+	},
+	"alternate net address, version only in alt": {
+		ds: []depspec{
+			dsp(mkDepspec("root 1.0.0", "foo from bar 2.0.0"),
+				pkg("root", "foo")),
+			dsp(mkDepspec("foo 1.0.0"),
+				pkg("foo")),
+			dsp(mkDepspec("bar 1.0.0"),
+				pkg("foo")),
+			dsp(mkDepspec("bar 2.0.0"),
+				pkg("foo")),
+		},
+		r: mksolution(
+			"foo from bar 2.0.0",
+		),
+	},
+	"alternate net address in dep": {
+		ds: []depspec{
+			dsp(mkDepspec("root 1.0.0", "foo 1.0.0"),
+				pkg("root", "foo")),
+			dsp(mkDepspec("foo 1.0.0", "bar from baz 2.0.0"),
+				pkg("foo", "bar")),
+			dsp(mkDepspec("bar 1.0.0"),
+				pkg("bar")),
+			dsp(mkDepspec("baz 1.0.0"),
+				pkg("bar")),
+			dsp(mkDepspec("baz 2.0.0"),
+				pkg("bar")),
+		},
+		r: mksolution(
+			"foo 1.0.0",
+			"bar from baz 2.0.0",
+		),
+	},
+	// Because NOT specifying an alternate net address for a given import path
+	// is taken as an "eh, whatever", if we see an empty net addr after
+	// something else has already set an alternate one, then the second should
+	// just "go along" with whatever's already been specified.
+	"alternate net address with second depper": {
+		ds: []depspec{
+			dsp(mkDepspec("root 1.0.0", "foo from bar 2.0.0"),
+				pkg("root", "foo", "baz")),
+			dsp(mkDepspec("foo 1.0.0"),
+				pkg("foo")),
+			dsp(mkDepspec("foo 2.0.0"),
+				pkg("foo")),
+			dsp(mkDepspec("bar 1.0.0"),
+				pkg("foo")),
+			dsp(mkDepspec("bar 2.0.0"),
+				pkg("foo")),
+			dsp(mkDepspec("baz 1.0.0"),
+				pkg("baz", "foo")),
+		},
+		r: mksolution(
+			"foo from bar 2.0.0",
+			"baz 1.0.0",
+		),
+	},
+	// Same as the previous, except the alternate declaration originates in a
+	// dep, not the root.
+	"alternate net addr from dep, with second default depper": {
+		ds: []depspec{
+			dsp(mkDepspec("root 1.0.0", "foo 1.0.0"),
+				pkg("root", "foo", "bar")),
+			dsp(mkDepspec("foo 1.0.0", "bar 2.0.0"),
+				pkg("foo", "baz")),
+			dsp(mkDepspec("foo 2.0.0", "bar 2.0.0"),
+				pkg("foo", "baz")),
+			dsp(mkDepspec("bar 2.0.0", "baz from quux 1.0.0"),
+				pkg("bar", "baz")),
+			dsp(mkDepspec("baz 1.0.0"),
+				pkg("baz")),
+			dsp(mkDepspec("baz 2.0.0"),
+				pkg("baz")),
+			dsp(mkDepspec("quux 1.0.0"),
+				pkg("baz")),
+		},
+		r: mksolution(
+			"foo 1.0.0",
+			"bar 2.0.0",
+			"baz from quux 1.0.0",
+		),
+	},
+	// When a given project is initially brought in using the default (i.e.,
+	// empty) ProjectIdentifier.NetworkName, and a later, presumably
+	// as-yet-undiscovered dependency specifies an alternate net addr for it, we
+	// have to fail - even though, if the deps were visited in the opposite
+	// order (deeper dep w/the alternate location first, default location
+	// second), it would be fine.
+	//
+	// TODO A better solution here would involve restarting the solver w/a
+	// marker to use that alternate, or (ugh) introducing a new failure
+	// path/marker type that changes how backtracking works. (In fact, these
+	// approaches are probably demonstrably equivalent.)
+	"fails with net mismatch when deeper dep specs it": {
+		ds: []depspec{
+			dsp(mkDepspec("root 1.0.0", "foo 1.0.0"),
+				pkg("root", "foo", "baz")),
+			dsp(mkDepspec("foo 1.0.0", "bar 2.0.0"),
+				pkg("foo", "bar")),
+			dsp(mkDepspec("bar 2.0.0", "baz from quux 1.0.0"),
+				pkg("bar", "baz")),
+			dsp(mkDepspec("baz 1.0.0"),
+				pkg("baz")),
+			dsp(mkDepspec("quux 1.0.0"),
+				pkg("baz")),
+		},
+		fail: &noVersionError{
+			pn: mkPI("bar"),
+			fails: []failedVersion{
+				{
+					v: NewVersion("2.0.0"),
+					f: &sourceMismatchFailure{
+						shared:   ProjectRoot("baz"),
+						current:  "baz",
+						mismatch: "quux",
+						prob:     mkAtom("bar 2.0.0"),
+						sel:      []dependency{mkDep("foo 1.0.0", "bar 2.0.0", "bar")},
+					},
+				},
+			},
+		},
+	},
+	"with mismatched net addrs": {
+		ds: []depspec{
+			dsp(mkDepspec("root 1.0.0", "foo 1.0.0", "bar 1.0.0"),
+				pkg("root", "foo", "bar")),
+			dsp(mkDepspec("foo 1.0.0", "bar from baz 1.0.0"),
+				pkg("foo", "bar")),
+			dsp(mkDepspec("bar 1.0.0"),
+				pkg("bar")),
+			dsp(mkDepspec("baz 1.0.0"),
+				pkg("bar")),
+		},
+		fail: &noVersionError{
+			pn: mkPI("foo"),
+			fails: []failedVersion{
+				{
+					v: NewVersion("1.0.0"),
+					f: &sourceMismatchFailure{
+						shared:   ProjectRoot("bar"),
+						current:  "bar",
+						mismatch: "baz",
+						prob:     mkAtom("foo 1.0.0"),
+						sel:      []dependency{mkDep("root", "foo 1.0.0", "foo")},
+					},
+				},
+			},
+		},
+	},
 	"overridden mismatched net addrs, alt in dep": {
 		ds: []depspec{
 			dsp(mkDepspec("root 0.0.0"),
@@ -575,7 +741,7 @@
 	// bimodal project. first is always treated as root project
 	ds []depspec
 	// results; map of name/version pairs
-	r map[string]Version
+	r map[ProjectIdentifier]Version
 	// max attempts the solver should need to find solution. 0 means no limit
 	maxAttempts int
 	// Use downgrade instead of default upgrade sorter
@@ -607,7 +773,7 @@
 	return f.maxAttempts
 }
 
-func (f bimodalFixture) solution() map[string]Version {
+func (f bimodalFixture) solution() map[ProjectIdentifier]Version {
 	return f.r
 }
 
@@ -652,9 +818,9 @@
 func (sm *bmSourceManager) ListPackages(id ProjectIdentifier, v Version) (PackageTree, error) {
 	for k, ds := range sm.specs {
 		// Cheat for root, otherwise we blow up b/c version is empty
-		if id.ProjectRoot == ds.n && (k == 0 || ds.v.Matches(v)) {
+		if id.netName() == string(ds.n) && (k == 0 || ds.v.Matches(v)) {
 			ptree := PackageTree{
-				ImportRoot: string(id.ProjectRoot),
+				ImportRoot: id.netName(),
 				Packages:   make(map[string]PackageOrErr),
 			}
 			for _, pkg := range ds.pkgs {
@@ -676,8 +842,8 @@
 
 func (sm *bmSourceManager) GetManifestAndLock(id ProjectIdentifier, v Version) (Manifest, Lock, error) {
 	for _, ds := range sm.specs {
-		if id.ProjectRoot == ds.n && v.Matches(ds.v) {
-			if l, exists := sm.lm[string(id.ProjectRoot)+" "+v.String()]; exists {
+		if id.netName() == string(ds.n) && v.Matches(ds.v) {
+			if l, exists := sm.lm[id.netName()+" "+v.String()]; exists {
 				return ds, l, nil
 			}
 			return ds, dummyLock{}, nil
diff --git a/vendor/github.com/sdboyer/gps/solve_test.go b/vendor/github.com/sdboyer/gps/solve_test.go
index 94ed8ba..53bcdcd 100644
--- a/vendor/github.com/sdboyer/gps/solve_test.go
+++ b/vendor/github.com/sdboyer/gps/solve_test.go
@@ -1,7 +1,9 @@
 package gps
 
 import (
+	"bytes"
 	"flag"
+	"fmt"
 	"io/ioutil"
 	"log"
 	"math/rand"
@@ -153,6 +155,14 @@
 }
 
 func fixtureSolveSimpleChecks(fix specfix, soln Solution, err error, t *testing.T) (Solution, error) {
+	ppi := func(id ProjectIdentifier) string {
+		// need this so we can clearly tell if there's a NetworkName or not
+		if id.NetworkName == "" {
+			return string(id.ProjectRoot)
+		}
+		return fmt.Sprintf("%s (from %s)", id.ProjectRoot, id.NetworkName)
+	}
+
 	fixfail := fix.failure()
 	if err != nil {
 		if fixfail == nil {
@@ -163,7 +173,12 @@
 			t.Errorf("(fixture: %q) Failure mismatch:\n\t(GOT): %s\n\t(WNT): %s", fix.name(), err, fixfail)
 		}
 	} else if fixfail != nil {
-		t.Errorf("(fixture: %q) Solver succeeded, but expecting failure:\n%s", fix.name(), fixfail)
+		var buf bytes.Buffer
+		fmt.Fprintf(&buf, "(fixture: %q) Solver succeeded, but expecting failure:\n%s\nProjects in solution:", fix.name(), fixfail)
+		for _, p := range soln.Projects() {
+			fmt.Fprintf(&buf, "\n\t- %s at %s", ppi(p.Ident()), p.Version())
+		}
+		t.Error(buf.String())
 	} else {
 		r := soln.(solution)
 		if fix.maxTries() > 0 && r.Attempts() > fix.maxTries() {
@@ -171,10 +186,10 @@
 		}
 
 		// Dump result projects into a map for easier interrogation
-		rp := make(map[string]Version)
+		rp := make(map[ProjectIdentifier]Version)
 		for _, p := range r.p {
 			pa := p.toAtom()
-			rp[string(pa.id.ProjectRoot)] = pa.v
+			rp[pa.id] = pa.v
 		}
 
 		fixlen, rlen := len(fix.solution()), len(rp)
@@ -187,12 +202,12 @@
 		// Walk through fixture/expected results first
 		for p, v := range fix.solution() {
 			if av, exists := rp[p]; !exists {
-				t.Errorf("(fixture: %q) Project %q expected but missing from results", fix.name(), p)
+				t.Errorf("(fixture: %q) Project %q expected but missing from results", fix.name(), ppi(p))
 			} else {
 				// delete result from map so we skip it on the reverse pass
 				delete(rp, p)
 				if v != av {
-					t.Errorf("(fixture: %q) Expected version %q of project %q, but actual version was %q", fix.name(), v, p, av)
+					t.Errorf("(fixture: %q) Expected version %q of project %q, but actual version was %q", fix.name(), v, ppi(p), av)
 				}
 			}
 		}
@@ -200,9 +215,9 @@
 		// Now walk through remaining actual results
 		for p, v := range rp {
 			if fv, exists := fix.solution()[p]; !exists {
-				t.Errorf("(fixture: %q) Unexpected project %q present in results", fix.name(), p)
+				t.Errorf("(fixture: %q) Unexpected project %q present in results", fix.name(), ppi(p))
 			} else if v != fv {
-				t.Errorf("(fixture: %q) Got version %q of project %q, but expected version was %q", fix.name(), v, p, fv)
+				t.Errorf("(fixture: %q) Got version %q of project %q, but expected version was %q", fix.name(), v, ppi(p), fv)
 			}
 		}
 	}
diff --git a/vendor/github.com/sdboyer/gps/solver.go b/vendor/github.com/sdboyer/gps/solver.go
index 507133b..f7d9a24 100644
--- a/vendor/github.com/sdboyer/gps/solver.go
+++ b/vendor/github.com/sdboyer/gps/solver.go
@@ -145,22 +145,17 @@
 	// A map of the ProjectRoot (local names) that should be allowed to change
 	chng map[ProjectRoot]struct{}
 
-	// A map of the ProjectRoot (local names) that are currently selected, and
-	// the network name to which they currently correspond.
-	// TODO(sdboyer) i think this is cruft and can be removed
-	names map[ProjectRoot]string
-
 	// A ProjectConstraints map containing the validated (guaranteed non-empty)
 	// overrides declared by the root manifest.
 	ovr ProjectConstraints
 
-	// A map of the names listed in the root's lock.
-	rlm map[ProjectIdentifier]LockedProject
+	// A map of the project names listed in the root's lock.
+	rlm map[ProjectRoot]LockedProject
 
-	// A normalized, copied version of the root manifest.
+	// A defensively-copied instance of the root manifest.
 	rm Manifest
 
-	// A normalized, copied version of the root lock.
+	// A defensively-copied instance of the root lock.
 	rl Lock
 }
 
@@ -253,8 +248,7 @@
 
 	// Initialize maps
 	s.chng = make(map[ProjectRoot]struct{})
-	s.rlm = make(map[ProjectIdentifier]LockedProject)
-	s.names = make(map[ProjectRoot]string)
+	s.rlm = make(map[ProjectRoot]LockedProject)
 
 	for _, v := range s.params.ToChange {
 		s.chng[v] = struct{}{}
@@ -262,7 +256,7 @@
 
 	// Initialize stacks and queues
 	s.sel = &selection{
-		deps: make(map[ProjectIdentifier][]dependency),
+		deps: make(map[ProjectRoot][]dependency),
 		sm:   s.b,
 	}
 	s.unsel = &unselected{
@@ -274,7 +268,7 @@
 	s.rm = prepManifest(s.params.Manifest)
 	if s.params.Lock != nil {
 		for _, lp := range s.params.Lock.Projects() {
-			s.rlm[lp.Ident().normalize()] = lp
+			s.rlm[lp.Ident().ProjectRoot] = lp
 		}
 
 		// Also keep a prepped one, mostly for the bridge. This is probably
@@ -305,8 +299,8 @@
 		}
 
 		// An err here is impossible; it could only be caused by a parsing error
-		// of the root tree, but that necessarily succeeded back up
-		// selectRoot(), so we can ignore this err
+		// of the root tree, but that necessarily already succeeded back up in
+		// selectRoot(), so we can ignore the err return here
 		soln.hd, _ = s.HashInputs()
 
 		// Convert ProjectAtoms into LockedProjects
@@ -426,7 +420,7 @@
 	return projs, nil
 }
 
-// selectRoot is a specialized selectAtomWithPackages, used solely to initially
+// selectRoot is a specialized selectAtom, used solely to initially
 // populate the queues at the beginning of a solve run.
 func (s *solver) selectRoot() error {
 	pa := atom{
@@ -480,13 +474,12 @@
 		// If we have no lock, or if this dep isn't in the lock, then prefetch
 		// it. See explanation longer comment in selectRoot() for how we benefit
 		// from parallelism here.
-		if _, has := s.rlm[dep.Ident]; !has {
+		if _, has := s.rlm[dep.Ident.ProjectRoot]; !has {
 			go s.b.SyncSourceFor(dep.Ident)
 		}
 
 		s.sel.pushDep(dependency{depender: pa, dep: dep})
 		// Add all to unselected queue
-		s.names[dep.Ident.ProjectRoot] = dep.Ident.netName()
 		heap.Push(s.unsel, bimodalIdentifier{id: dep.Ident, pl: dep.pl, fromRoot: true})
 	}
 
@@ -546,7 +539,6 @@
 	}
 
 	deps := s.ovr.overrideAll(m.DependencyConstraints())
-
 	return s.intersectConstraintsWithImports(deps, reach)
 }
 
@@ -575,19 +567,8 @@
 
 		// Look for a prefix match; it'll be the root project/repo containing
 		// the reached package
-		if k, idep, match := xt.LongestPrefix(rp); match {
-			// The radix tree gets it mostly right, but we have to guard against
-			// possibilities like this:
-			//
-			// github.com/sdboyer/foo
-			// github.com/sdboyer/foobar/baz
-			//
-			// The latter would incorrectly be conflated with the former. So, as
-			// we know we're operating on strings that describe paths, guard
-			// against this case by verifying that either the input is the same
-			// length as the match (in which case we know they're equal), or
-			// that the next character is the is the PathSeparator.
-			if len(k) == len(rp) || strings.IndexRune(rp[:len(k)], os.PathSeparator) == 0 {
+		if pre, idep, match := xt.LongestPrefix(rp); match {
+			if isPathPrefixOrEqual(pre, rp) {
 				// Match is valid; put it in the dmap, either creating a new
 				// completeDep or appending it to the existing one for this base
 				// project/prefix.
@@ -616,7 +597,6 @@
 		pd := s.ovr.override(ProjectConstraint{
 			Ident: ProjectIdentifier{
 				ProjectRoot: root,
-				NetworkName: string(root),
 			},
 			Constraint: Any(),
 		})
@@ -843,7 +823,7 @@
 		}
 	}
 
-	lp, exists := s.rlm[id]
+	lp, exists := s.rlm[id.ProjectRoot]
 	if !exists {
 		return nil, nil
 	}
@@ -980,6 +960,9 @@
 
 	// FIXME the impl here is currently O(n) in the number of selections; it
 	// absolutely cannot stay in a hot sorting path like this
+	// FIXME while other solver invariants probably protect us from it, this
+	// call-out means that it's possible for external state change to invalidate
+	// heap invariants.
 	_, isel := s.sel.selected(iname)
 	_, jsel := s.sel.selected(jname)
 
@@ -994,8 +977,8 @@
 		return false
 	}
 
-	_, ilock := s.rlm[iname]
-	_, jlock := s.rlm[jname]
+	_, ilock := s.rlm[iname.ProjectRoot]
+	_, jlock := s.rlm[jname.ProjectRoot]
 
 	switch {
 	case ilock && !jlock:
@@ -1101,7 +1084,7 @@
 		// few microseconds before blocking later. Best case, the dep doesn't
 		// come up next, but some other dep comes up that wasn't prefetched, and
 		// both fetches proceed in parallel.
-		if _, has := s.rlm[dep.Ident]; !has {
+		if _, has := s.rlm[dep.Ident.ProjectRoot]; !has {
 			go s.b.SyncSourceFor(dep.Ident)
 		}
 
@@ -1128,10 +1111,6 @@
 			}
 			heap.Push(s.unsel, bmi)
 		}
-
-		if s.sel.depperCount(dep.Ident) == 1 {
-			s.names[dep.Ident.ProjectRoot] = dep.Ident.netName()
-		}
 	}
 
 	s.traceSelect(a, pkgonly)
@@ -1153,7 +1132,6 @@
 
 		// if no parents/importers, remove from unselected queue
 		if s.sel.depperCount(dep.Ident) == 0 {
-			delete(s.names, dep.Ident.ProjectRoot)
 			s.unsel.remove(bimodalIdentifier{id: dep.Ident, pl: dep.pl})
 		}
 	}
@@ -1164,7 +1142,7 @@
 // simple (temporary?) helper just to convert atoms into locked projects
 func pa2lp(pa atom, pkgs map[string]struct{}) LockedProject {
 	lp := LockedProject{
-		pi: pa.id.normalize(), // shouldn't be necessary, but normalize just in case
+		pi: pa.id,
 	}
 
 	switch v := pa.v.(type) {
diff --git a/vendor/github.com/sdboyer/gps/source_manager.go b/vendor/github.com/sdboyer/gps/source_manager.go
index dc5a7ef..82064e4 100644
--- a/vendor/github.com/sdboyer/gps/source_manager.go
+++ b/vendor/github.com/sdboyer/gps/source_manager.go
@@ -257,9 +257,11 @@
 		// The non-matching tail of the import path could still be malformed.
 		// Validate just that part, if it exists
 		if prefix != ip {
-			if !pathvld.MatchString(strings.TrimPrefix(ip, prefix)) {
-				return "", fmt.Errorf("%q is not a valid import path", ip)
-			}
+			// TODO(sdboyer) commented until i find a proper description of how
+			// to validate an import path
+			//if !pathvld.MatchString(strings.TrimPrefix(ip, prefix+"/")) {
+			//return "", fmt.Errorf("%q is not a valid import path", ip)
+			//}
 			// There was one, and it validated fine - add it so we don't have to
 			// revalidate it later
 			sm.rootxt.Insert(ip, root)
diff --git a/vendor/github.com/sdboyer/gps/source_test.go b/vendor/github.com/sdboyer/gps/source_test.go
index 907d9c3..ffee963 100644
--- a/vendor/github.com/sdboyer/gps/source_test.go
+++ b/vendor/github.com/sdboyer/gps/source_test.go
@@ -4,7 +4,6 @@
 	"io/ioutil"
 	"net/url"
 	"reflect"
-	"sort"
 	"testing"
 )
 
@@ -84,7 +83,7 @@
 	if len(vlist) != 3 {
 		t.Errorf("git test repo should've produced three versions, got %v: vlist was %s", len(vlist), vlist)
 	} else {
-		sort.Sort(upgradeVersionSorter(vlist))
+		SortForUpgrade(vlist)
 		evl := []Version{
 			NewVersion("1.0.0").Is(Revision("30605f6ac35fcb075ad0bfa9296f90a7d891523e")),
 			NewBranch("master").Is(Revision("30605f6ac35fcb075ad0bfa9296f90a7d891523e")),
@@ -281,7 +280,7 @@
 	if len(vlist) != 2 {
 		t.Errorf("hg test repo should've produced one version, got %v", len(vlist))
 	} else {
-		sort.Sort(upgradeVersionSorter(vlist))
+		SortForUpgrade(vlist)
 		if !reflect.DeepEqual(vlist, evl) {
 			t.Errorf("Version list was not what we expected:\n\t(GOT): %s\n\t(WNT): %s", vlist, evl)
 		}
@@ -303,7 +302,7 @@
 	if len(vlist) != 2 {
 		t.Errorf("hg test repo should've produced one version, got %v", len(vlist))
 	} else {
-		sort.Sort(upgradeVersionSorter(vlist))
+		SortForUpgrade(vlist)
 		if !reflect.DeepEqual(vlist, evl) {
 			t.Errorf("Version list was not what we expected:\n\t(GOT): %s\n\t(WNT): %s", vlist, evl)
 		}
diff --git a/vendor/github.com/sdboyer/gps/typed_radix.go b/vendor/github.com/sdboyer/gps/typed_radix.go
index 9f56a9b..76b2f68 100644
--- a/vendor/github.com/sdboyer/gps/typed_radix.go
+++ b/vendor/github.com/sdboyer/gps/typed_radix.go
@@ -26,24 +26,24 @@
 
 // Delete is used to delete a key, returning the previous value and if it was deleted
 func (t deducerTrie) Delete(s string) (pathDeducer, bool) {
-	if v, had := t.t.Delete(s); had {
-		return v.(pathDeducer), had
+	if d, had := t.t.Delete(s); had {
+		return d.(pathDeducer), had
 	}
 	return nil, false
 }
 
 // Get is used to lookup a specific key, returning the value and if it was found
 func (t deducerTrie) Get(s string) (pathDeducer, bool) {
-	if v, has := t.t.Get(s); has {
-		return v.(pathDeducer), has
+	if d, has := t.t.Get(s); has {
+		return d.(pathDeducer), has
 	}
 	return nil, false
 }
 
 // Insert is used to add a newentry or update an existing entry. Returns if updated.
-func (t deducerTrie) Insert(s string, v pathDeducer) (pathDeducer, bool) {
-	if v2, had := t.t.Insert(s, v); had {
-		return v2.(pathDeducer), had
+func (t deducerTrie) Insert(s string, d pathDeducer) (pathDeducer, bool) {
+	if d2, had := t.t.Insert(s, d); had {
+		return d2.(pathDeducer), had
 	}
 	return nil, false
 }
@@ -56,8 +56,8 @@
 // LongestPrefix is like Get, but instead of an exact match, it will return the
 // longest prefix match.
 func (t deducerTrie) LongestPrefix(s string) (string, pathDeducer, bool) {
-	if p, v, has := t.t.LongestPrefix(s); has {
-		return p, v.(pathDeducer), has
+	if p, d, has := t.t.LongestPrefix(s); has {
+		return p, d.(pathDeducer), has
 	}
 	return "", nil, false
 }
@@ -65,8 +65,8 @@
 // ToMap is used to walk the tree and convert it to a map.
 func (t deducerTrie) ToMap() map[string]pathDeducer {
 	m := make(map[string]pathDeducer)
-	t.t.Walk(func(s string, v interface{}) bool {
-		m[s] = v.(pathDeducer)
+	t.t.Walk(func(s string, d interface{}) bool {
+		m[s] = d.(pathDeducer)
 		return false
 	})
 
@@ -85,24 +85,24 @@
 
 // Delete is used to delete a key, returning the previous value and if it was deleted
 func (t prTrie) Delete(s string) (ProjectRoot, bool) {
-	if v, had := t.t.Delete(s); had {
-		return v.(ProjectRoot), had
+	if pr, had := t.t.Delete(s); had {
+		return pr.(ProjectRoot), had
 	}
 	return "", false
 }
 
 // Get is used to lookup a specific key, returning the value and if it was found
 func (t prTrie) Get(s string) (ProjectRoot, bool) {
-	if v, has := t.t.Get(s); has {
-		return v.(ProjectRoot), has
+	if pr, has := t.t.Get(s); has {
+		return pr.(ProjectRoot), has
 	}
 	return "", false
 }
 
 // Insert is used to add a newentry or update an existing entry. Returns if updated.
-func (t prTrie) Insert(s string, v ProjectRoot) (ProjectRoot, bool) {
-	if v2, had := t.t.Insert(s, v); had {
-		return v2.(ProjectRoot), had
+func (t prTrie) Insert(s string, pr ProjectRoot) (ProjectRoot, bool) {
+	if pr2, had := t.t.Insert(s, pr); had {
+		return pr2.(ProjectRoot), had
 	}
 	return "", false
 }
@@ -115,8 +115,8 @@
 // LongestPrefix is like Get, but instead of an exact match, it will return the
 // longest prefix match.
 func (t prTrie) LongestPrefix(s string) (string, ProjectRoot, bool) {
-	if p, v, has := t.t.LongestPrefix(s); has && isPathPrefixOrEqual(p, s) {
-		return p, v.(ProjectRoot), has
+	if p, pr, has := t.t.LongestPrefix(s); has && isPathPrefixOrEqual(p, s) {
+		return p, pr.(ProjectRoot), has
 	}
 	return "", "", false
 }
@@ -124,8 +124,8 @@
 // ToMap is used to walk the tree and convert it to a map.
 func (t prTrie) ToMap() map[string]ProjectRoot {
 	m := make(map[string]ProjectRoot)
-	t.t.Walk(func(s string, v interface{}) bool {
-		m[s] = v.(ProjectRoot)
+	t.t.Walk(func(s string, pr interface{}) bool {
+		m[s] = pr.(ProjectRoot)
 		return false
 	})
 
@@ -133,7 +133,8 @@
 }
 
 // isPathPrefixOrEqual is an additional helper check to ensure that the literal
-// string prefix returned from a radix tree prefix match is also a tree match.
+// string prefix returned from a radix tree prefix match is also a path tree
+// match.
 //
 // The radix tree gets it mostly right, but we have to guard against
 // possibilities like this:
@@ -142,10 +143,18 @@
 // github.com/sdboyer/foobar/baz
 //
 // The latter would incorrectly be conflated with the former. As we know we're
-// operating on strings that describe paths, guard against this case by
+// operating on strings that describe import paths, guard against this case by
 // verifying that either the input is the same length as the match (in which
-// case we know they're equal), or that the next character is a "/".
+// case we know they're equal), or that the next character is a "/". (Import
+// paths are defined to always use "/", not the OS-specific path separator.)
 func isPathPrefixOrEqual(pre, path string) bool {
-	prflen := len(pre)
-	return prflen == len(path) || strings.Index(path[:prflen], "/") == 0
+	prflen, pathlen := len(pre), len(path)
+	if pathlen == prflen+1 {
+		// this can never be the case
+		return false
+	}
+
+	// we assume something else (a trie) has done equality check up to the point
+	// of the prefix, so we just check len
+	return prflen == pathlen || strings.Index(path[prflen:], "/") == 0
 }
diff --git a/vendor/github.com/sdboyer/gps/typed_radix_test.go b/vendor/github.com/sdboyer/gps/typed_radix_test.go
new file mode 100644
index 0000000..8edf39b
--- /dev/null
+++ b/vendor/github.com/sdboyer/gps/typed_radix_test.go
@@ -0,0 +1,22 @@
+package gps
+
+import "testing"
+
+// basically a regression test
+func TestPathPrefixOrEqual(t *testing.T) {
+	if !isPathPrefixOrEqual("foo", "foo") {
+		t.Error("Same path should return true")
+	}
+
+	if isPathPrefixOrEqual("foo", "fooer") {
+		t.Error("foo is not a path-type prefix of fooer")
+	}
+
+	if !isPathPrefixOrEqual("foo", "foo/bar") {
+		t.Error("foo is a path prefix of foo/bar")
+	}
+
+	if isPathPrefixOrEqual("foo", "foo/") {
+		t.Error("special case - foo is not a path prefix of foo/")
+	}
+}
diff --git a/vendor/github.com/sdboyer/gps/types.go b/vendor/github.com/sdboyer/gps/types.go
index 657d786..11221e3 100644
--- a/vendor/github.com/sdboyer/gps/types.go
+++ b/vendor/github.com/sdboyer/gps/types.go
@@ -84,7 +84,7 @@
 		return false
 	}
 
-	return i.NetworkName < j.NetworkName
+	return i.netName() < j.netName()
 }
 
 func (i ProjectIdentifier) eq(j ProjectIdentifier) bool {
@@ -100,7 +100,32 @@
 		return true
 	}
 
-	// TODO(sdboyer) attempt conversion to URL and compare base + path
+	return false
+}
+
+// equiv will check if the two identifiers are "equivalent," under special
+// rules.
+//
+// Given that the ProjectRoots are equal (==), equivalency occurs if:
+//
+// 1. The NetworkNames are equal (==), OR
+// 2. The LEFT (the receiver) NetworkName is non-empty, and the right
+// NetworkName is empty.
+//
+// *This is, very much intentionally, an asymmetric binary relation.* It's
+// specifically intended to facilitate the case where we allow for a
+// ProjectIdentifier with an explicit NetworkName to match one without.
+func (i ProjectIdentifier) equiv(j ProjectIdentifier) bool {
+	if i.ProjectRoot != j.ProjectRoot {
+		return false
+	}
+	if i.NetworkName == j.NetworkName {
+		return true
+	}
+
+	if i.NetworkName != "" && j.NetworkName == "" {
+		return true
+	}
 
 	return false
 }
diff --git a/vendor/github.com/sdboyer/gps/version.go b/vendor/github.com/sdboyer/gps/version.go
index ad79bff..1e15029 100644
--- a/vendor/github.com/sdboyer/gps/version.go
+++ b/vendor/github.com/sdboyer/gps/version.go
@@ -1,6 +1,10 @@
 package gps
 
-import "github.com/Masterminds/semver"
+import (
+	"sort"
+
+	"github.com/Masterminds/semver"
+)
 
 // Version represents one of the different types of versions used by gps.
 //
@@ -297,7 +301,11 @@
 }
 
 func (v semVersion) String() string {
-	return v.sv.Original()
+	str := v.sv.Original()
+	if str == "" {
+		str = v.sv.String()
+	}
+	return str
 }
 
 func (r semVersion) Type() string {
@@ -513,3 +521,151 @@
 	}
 	panic("unknown version type")
 }
+
+// SortForUpgrade sorts a slice of []Version in roughly descending order, so
+// that presumably newer versions are visited first. The rules are:
+//
+//  - All semver versions come first, and sort mostly according to the semver
+//  2.0 spec (as implemented by github.com/Masterminds/semver lib), with one
+//  exception:
+//  - Semver versions with a prerelease are after *all* non-prerelease semver.
+//  Against each other, they are sorted first by their numerical component, then
+//  lexicographically by their prerelease version.
+//  - All non-semver versions (tags) are next, and sort lexicographically
+//  against each other.
+//  - All branches are next, and sort lexicographically against each other.
+//  - Revisions are last, and sort lexicographically against each other.
+//
+// So, given a slice of the following versions:
+//
+//  - Branch: master devel
+//  - Semver tags: v1.0.0, v1.1.0, v1.1.0-alpha1
+//  - Non-semver tags: footag
+//  - Revision: f6e74e8d
+//
+// Sorting for upgrade will result in the following slice.
+//
+//  [v1.1.0 v1.0.0 v1.1.0-alpha1 footag devel master f6e74e8d]
+func SortForUpgrade(vl []Version) {
+	sort.Sort(upgradeVersionSorter(vl))
+}
+
+// SortForDowngrade sorts a slice of []Version in roughly ascending order, so
+// that presumably older versions are visited first.
+//
+// This is *not* the reverse of the same as SortForUpgrade (or you could simply
+// sort.Reverse(). The type precedence is the same, including the
+// semver vs. semver-with-prerelease relation. Lexicographic comparisons within
+// non-semver tags, branches, and revisions remains the same as well; because
+// these domains have no implicit chronology, there is no reason to reverse
+// them.
+//
+// The only binary relation that is reversed for downgrade is within-type
+// comparisons for semver (with and without prerelease).
+//
+// So, given a slice of the following versions:
+//
+//  - Branch: master devel
+//  - Semver tags: v1.0.0, v1.1.0, v1.1.0-alpha1
+//  - Non-semver tags: footag
+//  - Revision: f6e74e8d
+//
+// Sorting for downgrade will result in the following slice.
+//
+//  [v1.0.0 v1.1.0 v1.1.0-alpha1 footag devel master f6e74e8d]
+func SortForDowngrade(vl []Version) {
+	sort.Sort(downgradeVersionSorter(vl))
+}
+
+type upgradeVersionSorter []Version
+type downgradeVersionSorter []Version
+
+func (vs upgradeVersionSorter) Len() int {
+	return len(vs)
+}
+
+func (vs upgradeVersionSorter) Swap(i, j int) {
+	vs[i], vs[j] = vs[j], vs[i]
+}
+
+func (vs downgradeVersionSorter) Len() int {
+	return len(vs)
+}
+
+func (vs downgradeVersionSorter) Swap(i, j int) {
+	vs[i], vs[j] = vs[j], vs[i]
+}
+
+func (vs upgradeVersionSorter) Less(i, j int) bool {
+	l, r := vs[i], vs[j]
+
+	if tl, ispair := l.(versionPair); ispair {
+		l = tl.v
+	}
+	if tr, ispair := r.(versionPair); ispair {
+		r = tr.v
+	}
+
+	switch compareVersionType(l, r) {
+	case -1:
+		return true
+	case 1:
+		return false
+	case 0:
+		break
+	default:
+		panic("unreachable")
+	}
+
+	switch l.(type) {
+	// For these, now nothing to do but alpha sort
+	case Revision, branchVersion, plainVersion:
+		return l.String() < r.String()
+	}
+
+	// This ensures that pre-release versions are always sorted after ALL
+	// full-release versions
+	lsv, rsv := l.(semVersion).sv, r.(semVersion).sv
+	lpre, rpre := lsv.Prerelease() == "", rsv.Prerelease() == ""
+	if (lpre && !rpre) || (!lpre && rpre) {
+		return lpre
+	}
+	return lsv.GreaterThan(rsv)
+}
+
+func (vs downgradeVersionSorter) Less(i, j int) bool {
+	l, r := vs[i], vs[j]
+
+	if tl, ispair := l.(versionPair); ispair {
+		l = tl.v
+	}
+	if tr, ispair := r.(versionPair); ispair {
+		r = tr.v
+	}
+
+	switch compareVersionType(l, r) {
+	case -1:
+		return true
+	case 1:
+		return false
+	case 0:
+		break
+	default:
+		panic("unreachable")
+	}
+
+	switch l.(type) {
+	// For these, now nothing to do but alpha
+	case Revision, branchVersion, plainVersion:
+		return l.String() < r.String()
+	}
+
+	// This ensures that pre-release versions are always sorted after ALL
+	// full-release versions
+	lsv, rsv := l.(semVersion).sv, r.(semVersion).sv
+	lpre, rpre := lsv.Prerelease() == "", rsv.Prerelease() == ""
+	if (lpre && !rpre) || (!lpre && rpre) {
+		return lpre
+	}
+	return lsv.LessThan(rsv)
+}
diff --git a/vendor/github.com/sdboyer/gps/version_queue.go b/vendor/github.com/sdboyer/gps/version_queue.go
index 7c92253..dc5da98 100644
--- a/vendor/github.com/sdboyer/gps/version_queue.go
+++ b/vendor/github.com/sdboyer/gps/version_queue.go
@@ -18,6 +18,7 @@
 	b            sourceBridge
 	failed       bool
 	allLoaded    bool
+	adverr       error
 }
 
 func newVersionQueue(id ProjectIdentifier, lockv, prefv Version, b sourceBridge) (*versionQueue, error) {
@@ -63,10 +64,10 @@
 
 // advance moves the versionQueue forward to the next available version,
 // recording the failure that eliminated the current version.
-func (vq *versionQueue) advance(fail error) (err error) {
+func (vq *versionQueue) advance(fail error) error {
 	// Nothing in the queue means...nothing in the queue, nicely enough
-	if len(vq.pi) == 0 {
-		return
+	if vq.adverr != nil || len(vq.pi) == 0 { // should be a redundant check, but just in case
+		return vq.adverr
 	}
 
 	// Record the fail reason and pop the queue
@@ -80,32 +81,43 @@
 	if len(vq.pi) == 0 {
 		if vq.allLoaded {
 			// This branch gets hit when the queue is first fully exhausted,
-			// after having been populated by ListVersions() on a previous
-			// advance()
-			return
+			// after a previous advance() already called ListVersions().
+			return nil
 		}
-
 		vq.allLoaded = true
-		vq.pi, err = vq.b.ListVersions(vq.id)
-		if err != nil {
-			return err
-		}
 
-		// search for and remove locked and pref versions
+		var vltmp []Version
+		vltmp, vq.adverr = vq.b.ListVersions(vq.id)
+		if vq.adverr != nil {
+			return vq.adverr
+		}
+		// defensive copy - calling ListVersions here means slice contents may
+		// be modified when removing prefv/lockv.
+		vq.pi = make([]Version, len(vltmp))
+		copy(vq.pi, vltmp)
+
+		// search for and remove lockv and prefv, in a pointer GC-safe manner
 		//
 		// could use the version comparator for binary search here to avoid
 		// O(n) each time...if it matters
+		var delkeys []int
 		for k, pi := range vq.pi {
 			if pi == vq.lockv || pi == vq.prefv {
-				// GC-safe deletion for slice w/pointer elements
-				vq.pi, vq.pi[len(vq.pi)-1] = append(vq.pi[:k], vq.pi[k+1:]...), nil
-				//vq.pi = append(vq.pi[:k], vq.pi[k+1:]...)
+				delkeys = append(delkeys, k)
 			}
 		}
 
+		for k, dk := range delkeys {
+			dk -= k
+			copy(vq.pi[dk:], vq.pi[dk+1:])
+			// write nil to final position for GC safety
+			vq.pi[len(vq.pi)-1] = nil
+			vq.pi = vq.pi[:len(vq.pi)-1]
+		}
+
 		if len(vq.pi) == 0 {
 			// If listing versions added nothing (new), then return now
-			return
+			return nil
 		}
 	}
 
@@ -117,7 +129,7 @@
 	// If all have been loaded and the queue is empty, we're definitely out
 	// of things to try. Return empty, though, because vq semantics dictate
 	// that we don't explicitly indicate the end of the queue here.
-	return
+	return nil
 }
 
 // isExhausted indicates whether or not the queue has definitely been exhausted,
diff --git a/vendor/github.com/sdboyer/gps/version_queue_test.go b/vendor/github.com/sdboyer/gps/version_queue_test.go
new file mode 100644
index 0000000..2e6174d
--- /dev/null
+++ b/vendor/github.com/sdboyer/gps/version_queue_test.go
@@ -0,0 +1,249 @@
+package gps
+
+import (
+	"fmt"
+	"testing"
+)
+
+// just need a ListVersions method
+type fakeBridge struct {
+	*bridge
+	vl []Version
+}
+
+var fakevl = []Version{
+	NewVersion("v2.0.0").Is("200rev"),
+	NewVersion("v1.1.1").Is("111rev"),
+	NewVersion("v1.1.0").Is("110rev"),
+	NewVersion("v1.0.0").Is("100rev"),
+	NewBranch("master").Is("masterrev"),
+}
+
+func init() {
+	SortForUpgrade(fakevl)
+}
+
+func (fb *fakeBridge) ListVersions(id ProjectIdentifier) ([]Version, error) {
+	// it's a fixture, we only ever do the one, regardless of id
+	return fb.vl, nil
+}
+
+type fakeFailBridge struct {
+	*bridge
+}
+
+var vqerr = fmt.Errorf("vqerr")
+
+func (fb *fakeFailBridge) ListVersions(id ProjectIdentifier) ([]Version, error) {
+	return nil, vqerr
+}
+
+func TestVersionQueueSetup(t *testing.T) {
+	id := ProjectIdentifier{ProjectRoot: ProjectRoot("foo")}.normalize()
+
+	// shouldn't even need to embed a real bridge
+	fb := &fakeBridge{vl: fakevl}
+	ffb := &fakeFailBridge{}
+
+	_, err := newVersionQueue(id, nil, nil, ffb)
+	if err == nil {
+		t.Error("Expected err when providing no prefv or lockv, and injected bridge returns err from ListVersions()")
+	}
+
+	vq, err := newVersionQueue(id, nil, nil, fb)
+	if err != nil {
+		t.Errorf("Unexpected err on vq create: %s", err)
+	} else {
+		if len(vq.pi) != 5 {
+			t.Errorf("Should have five versions from ListVersions() when providing no prefv or lockv; got %v:\n\t%s", len(vq.pi), vq.String())
+		}
+		if !vq.allLoaded {
+			t.Errorf("allLoaded flag should be set, but wasn't")
+		}
+
+		if vq.prefv != nil || vq.lockv != nil {
+			t.Error("lockv and prefv should be nil")
+		}
+		if vq.current() != fakevl[0] {
+			t.Errorf("current should be head of fakevl (%s), got %s", fakevl[0], vq.current())
+		}
+	}
+
+	lockv := fakevl[0]
+	prefv := fakevl[1]
+	vq, err = newVersionQueue(id, lockv, nil, fb)
+	if err != nil {
+		t.Errorf("Unexpected err on vq create: %s", err)
+	} else {
+		if len(vq.pi) != 1 {
+			t.Errorf("Should have one version when providing only a lockv; got %v:\n\t%s", len(vq.pi), vq.String())
+		}
+		if vq.allLoaded {
+			t.Errorf("allLoaded flag should not be set")
+		}
+		if vq.lockv != lockv {
+			t.Errorf("lockv should be %s, was %s", lockv, vq.lockv)
+		}
+		if vq.current() != lockv {
+			t.Errorf("current should be lockv (%s), got %s", lockv, vq.current())
+		}
+	}
+
+	vq, err = newVersionQueue(id, nil, prefv, fb)
+	if err != nil {
+		t.Errorf("Unexpected err on vq create: %s", err)
+	} else {
+		if len(vq.pi) != 1 {
+			t.Errorf("Should have one version when providing only a prefv; got %v:\n\t%s", len(vq.pi), vq.String())
+		}
+		if vq.allLoaded {
+			t.Errorf("allLoaded flag should not be set")
+		}
+		if vq.prefv != prefv {
+			t.Errorf("prefv should be %s, was %s", prefv, vq.prefv)
+		}
+		if vq.current() != prefv {
+			t.Errorf("current should be prefv (%s), got %s", prefv, vq.current())
+		}
+	}
+
+	vq, err = newVersionQueue(id, lockv, prefv, fb)
+	if err != nil {
+		t.Errorf("Unexpected err on vq create: %s", err)
+	} else {
+		if len(vq.pi) != 2 {
+			t.Errorf("Should have two versions when providing both a prefv and lockv; got %v:\n\t%s", len(vq.pi), vq.String())
+		}
+		if vq.allLoaded {
+			t.Errorf("allLoaded flag should not be set")
+		}
+		if vq.prefv != prefv {
+			t.Errorf("prefv should be %s, was %s", prefv, vq.prefv)
+		}
+		if vq.lockv != lockv {
+			t.Errorf("lockv should be %s, was %s", lockv, vq.lockv)
+		}
+		if vq.current() != lockv {
+			t.Errorf("current should be lockv (%s), got %s", lockv, vq.current())
+		}
+	}
+}
+
+func TestVersionQueueAdvance(t *testing.T) {
+	fb := &fakeBridge{vl: fakevl}
+	id := ProjectIdentifier{ProjectRoot: ProjectRoot("foo")}.normalize()
+
+	// First with no prefv or lockv
+	vq, err := newVersionQueue(id, nil, nil, fb)
+	if err != nil {
+		t.Errorf("Unexpected err on vq create: %s", err)
+		t.FailNow()
+	}
+
+	for k, v := range fakevl[1:] {
+		err = vq.advance(fmt.Errorf("advancment fail for %s", fakevl[k]))
+		if err != nil {
+			t.Errorf("error on advancing vq from %s to %s", fakevl[k], v)
+			break
+		}
+
+		if vq.current() != v {
+			t.Errorf("on advance() %v, current should be %s, got %s", k, v, vq.current())
+		}
+	}
+
+	if vq.isExhausted() {
+		t.Error("should not be exhausted until advancing 'past' the end")
+	}
+	if err = vq.advance(fmt.Errorf("final advance failure")); err != nil {
+		t.Errorf("should not error on advance, even past end, but got %s", err)
+	}
+
+	if !vq.isExhausted() {
+		t.Error("advanced past end, should now report exhaustion")
+	}
+	if vq.current() != nil {
+		t.Error("advanced past end, current should return nil")
+	}
+
+	// now, do one with both a prefv and lockv
+	lockv := fakevl[2]
+	prefv := fakevl[0]
+	vq, err = newVersionQueue(id, lockv, prefv, fb)
+	if vq.String() != "[v1.1.0, v2.0.0]" {
+		t.Error("stringifying vq did not have expected outcome, got", vq.String())
+	}
+	if vq.isExhausted() {
+		t.Error("can't be exhausted, we aren't even 'allLoaded' yet")
+	}
+
+	err = vq.advance(fmt.Errorf("dequeue lockv"))
+	if err != nil {
+		t.Error("unexpected error when advancing past lockv", err)
+	} else {
+		if vq.current() != prefv {
+			t.Errorf("current should be prefv (%s) after first advance, got %s", prefv, vq.current())
+		}
+		if len(vq.pi) != 1 {
+			t.Errorf("should have just prefv elem left in vq, but there are %v:\n\t%s", len(vq.pi), vq.String())
+		}
+	}
+
+	err = vq.advance(fmt.Errorf("dequeue prefv"))
+	if err != nil {
+		t.Error("unexpected error when advancing past prefv", err)
+	} else {
+		if !vq.allLoaded {
+			t.Error("allLoaded should now be true")
+		}
+		if len(vq.pi) != 3 {
+			t.Errorf("should have three remaining versions after removing prefv and lockv, but there are %v:\n\t%s", len(vq.pi), vq.String())
+		}
+		if vq.current() != fakevl[1] {
+			t.Errorf("current should be first elem of fakevl (%s) after advancing into all, got %s", fakevl[1], vq.current())
+		}
+	}
+
+	// make sure the queue ordering is still right even with a double-delete
+	vq.advance(nil)
+	if vq.current() != fakevl[3] {
+		t.Errorf("second elem after ListVersions() should be idx 3 of fakevl (%s), got %s", fakevl[3], vq.current())
+	}
+	vq.advance(nil)
+	if vq.current() != fakevl[4] {
+		t.Errorf("third elem after ListVersions() should be idx 4 of fakevl (%s), got %s", fakevl[4], vq.current())
+	}
+	vq.advance(nil)
+	if vq.current() != nil || !vq.isExhausted() {
+		t.Error("should be out of versions in the queue")
+	}
+
+	// Make sure we handle things correctly when listVersions adds nothing new
+	fb = &fakeBridge{vl: []Version{lockv, prefv}}
+	vq, err = newVersionQueue(id, lockv, prefv, fb)
+	vq.advance(nil)
+	vq.advance(nil)
+	if vq.current() != nil || !vq.isExhausted() {
+		t.Errorf("should have no versions left, as ListVersions() added nothing new, but still have %s", vq.String())
+	}
+	err = vq.advance(nil)
+	if err != nil {
+		t.Errorf("should be fine to advance on empty queue, per docs, but got err %s", err)
+	}
+
+	// Also handle it well when advancing calls ListVersions() and it gets an
+	// error
+	vq, err = newVersionQueue(id, lockv, nil, &fakeFailBridge{})
+	if err != nil {
+		t.Errorf("should not err on creation when preseeded with lockv, but got err %s", err)
+	}
+	err = vq.advance(nil)
+	if err == nil {
+		t.Error("advancing should trigger call to erroring bridge, but no err")
+	}
+	err = vq.advance(nil)
+	if err == nil {
+		t.Error("err should be stored for reuse on any subsequent calls")
+	}
+
+}
diff --git a/vendor/github.com/sdboyer/gps/version_test.go b/vendor/github.com/sdboyer/gps/version_test.go
index f8b9b89..436dbe4 100644
--- a/vendor/github.com/sdboyer/gps/version_test.go
+++ b/vendor/github.com/sdboyer/gps/version_test.go
@@ -1,9 +1,6 @@
 package gps
 
-import (
-	"sort"
-	"testing"
-)
+import "testing"
 
 func TestVersionSorts(t *testing.T) {
 	rev := Revision("flooboofoobooo")
@@ -47,7 +44,7 @@
 		rev, // revs
 	}
 
-	sort.Sort(upgradeVersionSorter(up))
+	SortForUpgrade(up)
 	var wrong []int
 	for k, v := range up {
 		if eup[k] != v {
@@ -60,7 +57,7 @@
 		t.Errorf("Upgrade sort positions with wrong versions: %v", wrong)
 	}
 
-	sort.Sort(downgradeVersionSorter(down))
+	SortForDowngrade(down)
 	wrong = wrong[:0]
 	for k, v := range down {
 		if edown[k] != v {
@@ -74,7 +71,7 @@
 	}
 
 	// Now make sure we sort back the other way correctly...just because
-	sort.Sort(upgradeVersionSorter(down))
+	SortForUpgrade(down)
 	wrong = wrong[:0]
 	for k, v := range down {
 		if eup[k] != v {
@@ -88,7 +85,7 @@
 	}
 
 	// Now make sure we sort back the other way correctly...just because
-	sort.Sort(downgradeVersionSorter(up))
+	SortForDowngrade(up)
 	wrong = wrong[:0]
 	for k, v := range up {
 		if edown[k] != v {