Merge pull request #315 from Masterminds/dont-dup-update

Track updated packages to avoid dup work
diff --git a/glide.go b/glide.go
index 301c165..26dc1b5 100644
--- a/glide.go
+++ b/glide.go
@@ -219,14 +219,13 @@
 					msg.Warn("Only resolving dependencies for the current OS/Arch")
 				}
 
-				inst := &repo.Installer{
-					Force:           c.Bool("force"),
-					UseCache:        c.Bool("cache"),
-					UseGopath:       c.Bool("use-gopath"),
-					UseCacheGopath:  c.Bool("cache-gopath"),
-					UpdateVendored:  c.Bool("update-vendored"),
-					ResolveAllFiles: c.Bool("all-dependencies"),
-				}
+				inst := repo.NewInstaller()
+				inst.Force = c.Bool("force")
+				inst.UseCache = c.Bool("cache")
+				inst.UseGopath = c.Bool("use-gopath")
+				inst.UseCacheGopath = c.Bool("cache-gopath")
+				inst.UpdateVendored = c.Bool("update-vendored")
+				inst.ResolveAllFiles = c.Bool("all-dependencies")
 				packages := []string(c.Args())
 				insecure := c.Bool("insecure")
 				action.Get(packages, inst, insecure, c.Bool("no-recursive"))
@@ -259,10 +258,8 @@
 					// FIXME: Implement this in the installer.
 					fmt.Println("Delete is not currently implemented.")
 				}
-
-				inst := &repo.Installer{
-					Force: c.Bool("force"),
-				}
+				inst := repo.NewInstaller()
+				inst.Force = c.Bool("force")
 				packages := []string(c.Args())
 				action.Remove(packages, inst)
 			},
@@ -396,15 +393,14 @@
 				},
 			},
 			Action: func(c *cli.Context) {
-				installer := &repo.Installer{
-					DeleteUnused:   c.Bool("deleteOptIn"),
-					UpdateVendored: c.Bool("update-vendored"),
-					Force:          c.Bool("force"),
-					UseCache:       c.Bool("cache"),
-					UseCacheGopath: c.Bool("cache-gopath"),
-					UseGopath:      c.Bool("use-gopath"),
-					Home:           gpath.Home(),
-				}
+				installer := repo.NewInstaller()
+				installer.Force = c.Bool("force")
+				installer.UseCache = c.Bool("cache")
+				installer.UseGopath = c.Bool("use-gopath")
+				installer.UseCacheGopath = c.Bool("cache-gopath")
+				installer.UpdateVendored = c.Bool("update-vendored")
+				installer.Home = gpath.Home()
+				installer.DeleteUnused = c.Bool("deleteOptIn")
 
 				action.Install(installer)
 			},
@@ -487,16 +483,15 @@
 					msg.Warn("Only resolving dependencies for the current OS/Arch")
 				}
 
-				installer := &repo.Installer{
-					DeleteUnused:    c.Bool("deleteOptIn"),
-					UpdateVendored:  c.Bool("update-vendored"),
-					ResolveAllFiles: c.Bool("all-dependencies"),
-					Force:           c.Bool("force"),
-					UseCache:        c.Bool("cache"),
-					UseCacheGopath:  c.Bool("cache-gopath"),
-					UseGopath:       c.Bool("use-gopath"),
-					Home:            gpath.Home(),
-				}
+				installer := repo.NewInstaller()
+				installer.Force = c.Bool("force")
+				installer.UseCache = c.Bool("cache")
+				installer.UseGopath = c.Bool("use-gopath")
+				installer.UseCacheGopath = c.Bool("cache-gopath")
+				installer.UpdateVendored = c.Bool("update-vendored")
+				installer.ResolveAllFiles = c.Bool("all-dependencies")
+				installer.Home = gpath.Home()
+				installer.DeleteUnused = c.Bool("deleteOptIn")
 
 				action.Update(installer, c.Bool("no-recursive"))
 			},
diff --git a/repo/installer.go b/repo/installer.go
index 1ceaec1..bebc3df 100644
--- a/repo/installer.go
+++ b/repo/installer.go
@@ -47,6 +47,15 @@
 	// of every file of every package, rather than only following imported
 	// packages.
 	ResolveAllFiles bool
+
+	// Updated tracks the packages that have been remotely fetched.
+	Updated *UpdateTracker
+}
+
+func NewInstaller() *Installer {
+	i := &Installer{}
+	i.Updated = NewUpdateTracker()
+	return i
 }
 
 // VendorPath returns the path to the location to put vendor packages
@@ -157,6 +166,7 @@
 		updateVendored: i.UpdateVendored,
 		Config:         conf,
 		Use:            ic,
+		updated:        i.Updated,
 	}
 
 	v := &VersionHandler{
@@ -244,7 +254,7 @@
 				select {
 				case dep := <-ch:
 					dest := filepath.Join(i.VendorPath(), dep.Name)
-					if err := VcsUpdate(dep, dest, i.Home, i.UseCache, i.UseCacheGopath, i.UseGopath, i.Force, i.UpdateVendored); err != nil {
+					if err := VcsUpdate(dep, dest, i.Home, i.UseCache, i.UseCacheGopath, i.UseGopath, i.Force, i.UpdateVendored, i.Updated); err != nil {
 						msg.Err("Update failed for %s: %s\n", dep.Name, err)
 						// Capture the error while making sure the concurrent
 						// operations don't step on each other.
@@ -312,6 +322,7 @@
 	cache, cacheGopath, useGopath, force, updateVendored bool
 	Config                                               *cfg.Config
 	Use                                                  *importCache
+	updated                                              *UpdateTracker
 }
 
 // NotFound attempts to retrieve a package when not found in the local vendor/
@@ -429,7 +440,7 @@
 		m.Config.Imports = append(m.Config.Imports, d)
 	}
 
-	if err := VcsUpdate(d, dest, m.home, m.cache, m.cacheGopath, m.useGopath, m.force, m.updateVendored); err != nil {
+	if err := VcsUpdate(d, dest, m.home, m.cache, m.cacheGopath, m.useGopath, m.force, m.updateVendored, m.updated); err != nil {
 		return err
 	}
 
diff --git a/repo/tracker.go b/repo/tracker.go
new file mode 100644
index 0000000..c017ffd
--- /dev/null
+++ b/repo/tracker.go
@@ -0,0 +1,42 @@
+package repo
+
+import (
+	"sync"
+)
+
+// UpdateTracker holds a list of all the packages that have been updated from
+// an external source. This is a concurrency safe implementation.
+type UpdateTracker struct {
+	sync.RWMutex
+
+	updated map[string]bool
+}
+
+// NewUpdateTracker creates a new instance of UpdateTracker ready for use.
+func NewUpdateTracker() *UpdateTracker {
+	u := &UpdateTracker{}
+	u.updated = map[string]bool{}
+	return u
+}
+
+// Add adds a name to the list of items being tracked.
+func (u *UpdateTracker) Add(name string) {
+	u.Lock()
+	u.updated[name] = true
+	u.Unlock()
+}
+
+// Check returns if an item is on the list or not.
+func (u *UpdateTracker) Check(name string) bool {
+	u.RLock()
+	_, f := u.updated[name]
+	u.RUnlock()
+	return f
+}
+
+// Remove takes a package off the list
+func (u *UpdateTracker) Remove(name string) {
+	u.Lock()
+	delete(u.updated, name)
+	u.Unlock()
+}
diff --git a/repo/tracker_test.go b/repo/tracker_test.go
new file mode 100644
index 0000000..94150f2
--- /dev/null
+++ b/repo/tracker_test.go
@@ -0,0 +1,23 @@
+package repo
+
+import "testing"
+
+func TestUpdateTracker(t *testing.T) {
+	tr := NewUpdateTracker()
+
+	if f := tr.Check("github.com/foo/bar"); f != false {
+		t.Error("Error, package Check passed on empty tracker")
+	}
+
+	tr.Add("github.com/foo/bar")
+
+	if f := tr.Check("github.com/foo/bar"); f != true {
+		t.Error("Error, failed to add package to tracker")
+	}
+
+	tr.Remove("github.com/foo/bar")
+
+	if f := tr.Check("github.com/foo/bar"); f != false {
+		t.Error("Error, failed to remove package from tracker")
+	}
+}
diff --git a/repo/vcs.go b/repo/vcs.go
index 6055b6e..989c38d 100644
--- a/repo/vcs.go
+++ b/repo/vcs.go
@@ -21,7 +21,7 @@
 )
 
 // VcsUpdate updates to a particular checkout based on the VCS setting.
-func VcsUpdate(dep *cfg.Dependency, dest, home string, cache, cacheGopath, useGopath, force, updateVendored bool) error {
+func VcsUpdate(dep *cfg.Dependency, dest, home string, cache, cacheGopath, useGopath, force, updateVendored bool, updated *UpdateTracker) error {
 
 	// If the dependency has already been pinned we can skip it. This is a
 	// faster path so we don't need to resolve it again.
@@ -30,6 +30,12 @@
 		return nil
 	}
 
+	if updated.Check(dep.Name) {
+		msg.Debug("%s was already updated, skipping.", dep.Name)
+		return nil
+	}
+	updated.Add(dep.Name)
+
 	msg.Info("Fetching updates for %s.\n", dep.Name)
 
 	if filterArchOs(dep) {