Add a caching downloader to avoid double clonse - fixes #22
diff --git a/downloader.go b/downloader.go
new file mode 100644
index 0000000..3fb76aa
--- /dev/null
+++ b/downloader.go
@@ -0,0 +1,69 @@
+package main
+
+import (
+ "sync"
+
+ "github.com/FiloSottile/gvt/gbvendor"
+)
+
+type cacheKey struct {
+ url, repoType string
+ branch, tag, revision string
+}
+
+type cacheEntry struct {
+ wg sync.WaitGroup
+ v vendor.WorkingCopy
+ err error
+}
+
+// Downloader acts as a cache for downloaded repositories
+type Downloader struct {
+ mu sync.Mutex
+ m map[cacheKey]*cacheEntry
+}
+
+var GlobalDownloader = Downloader{}
+
+func init() {
+ GlobalDownloader.m = make(map[cacheKey]*cacheEntry)
+}
+
+// Get returns a cached WorkingCopy, or runs RemoteRepo.Checkout
+func (d *Downloader) Get(repo vendor.RemoteRepo, branch, tag, revision string) (vendor.WorkingCopy, error) {
+ key := cacheKey{
+ url: repo.URL(), repoType: repo.Type(),
+ branch: branch, tag: tag, revision: revision,
+ }
+ d.mu.Lock()
+ if entry, ok := d.m[key]; ok {
+ d.mu.Unlock()
+ entry.wg.Wait()
+ return entry.v, entry.err
+ }
+
+ entry := &cacheEntry{}
+ entry.wg.Add(1)
+ d.m[key] = entry
+ d.mu.Unlock()
+
+ entry.v, entry.err = repo.Checkout(branch, tag, revision)
+ entry.wg.Done()
+ return entry.v, entry.err
+}
+
+func (d *Downloader) Flush() error {
+ d.mu.Lock()
+ defer d.mu.Unlock()
+
+ for _, entry := range d.m {
+ entry.wg.Wait()
+ if entry.err != nil {
+ continue
+ }
+ if err := entry.v.Destroy(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/fetch.go b/fetch.go
index 5797bef..b21f3b4 100644
--- a/fetch.go
+++ b/fetch.go
@@ -94,7 +94,7 @@
return fmt.Errorf("%s is already vendored", path)
}
- wc, err := repo.Checkout(branch, tag, revision)
+ wc, err := GlobalDownloader.Get(repo, branch, tag, revision)
if err != nil {
return err
@@ -138,10 +138,6 @@
return err
}
- if err := wc.Destroy(); err != nil {
- return err
- }
-
if !recurse {
return nil
}
diff --git a/gbvendor/repo.go b/gbvendor/repo.go
index 916dbe8..9b970f3 100644
--- a/gbvendor/repo.go
+++ b/gbvendor/repo.go
@@ -24,9 +24,11 @@
// specific.
Checkout(branch, tag, revision string) (WorkingCopy, error)
- // URL returns the URL the clone was taken from. It should
- // only be called after Clone.
+ // URL returns the URL the clone was/will be taken from.
URL() string
+
+ // Type returns the repository type (git, hg, ...)
+ Type() string
}
// WorkingCopy represents a local copy of a remote dvcs repository.
@@ -274,6 +276,10 @@
return g.url
}
+func (g *gitrepo) Type() string {
+ return "git"
+}
+
// Checkout fetchs the remote branch, tag, or revision. If the branch is blank,
// then the default remote branch will be used. If the branch is "HEAD" and
// revision is empty, an impossible update is assumed.
@@ -380,7 +386,8 @@
url string
}
-func (h *hgrepo) URL() string { return h.url }
+func (h *hgrepo) URL() string { return h.url }
+func (h *hgrepo) Type() string { return "hg" }
func (h *hgrepo) Checkout(branch, tag, revision string) (WorkingCopy, error) {
if !atMostOne(tag, revision) {
@@ -454,6 +461,10 @@
return b.url
}
+func (b *bzrrepo) Type() string {
+ return "bzr"
+}
+
func (b *bzrrepo) Checkout(branch, tag, revision string) (WorkingCopy, error) {
if !atMostOne(tag, revision) {
return nil, fmt.Errorf("only one of tag or revision may be supplied")
diff --git a/main.go b/main.go
index 3f435b8..129b95b 100644
--- a/main.go
+++ b/main.go
@@ -67,6 +67,9 @@
if err := command.Run(fs.Args()); err != nil {
log.Fatalf("command %q failed: %v", command.Name, err)
}
+ if err := GlobalDownloader.Flush(); err != nil {
+ log.Fatalf("failed to delete tempdirs: %v", err)
+ }
return
}
}
diff --git a/restore.go b/restore.go
index 27eb024..dfd078a 100644
--- a/restore.go
+++ b/restore.go
@@ -107,7 +107,7 @@
}
// We can't pass the branch here, and benefit from narrow clones, as the
// revision might not be in the branch tree anymore. Thanks rebase.
- wc, err := repo.Checkout("", "", dep.Revision)
+ wc, err := GlobalDownloader.Get(repo, "", "", dep.Revision)
if err != nil {
return fmt.Errorf("dependency could not be fetched: %s", err)
}
@@ -128,10 +128,6 @@
return err
}
- if err := wc.Destroy(); err != nil {
- return err
- }
-
// Check for for manifests in dependencies
man := filepath.Join(dst, "vendor", "manifest")
venDir := filepath.Join(dst, "vendor")
diff --git a/update.go b/update.go
index 17f0358..f7d38d6 100644
--- a/update.go
+++ b/update.go
@@ -75,7 +75,7 @@
return fmt.Errorf("could not determine repository for import %q", d.Importpath)
}
- wc, err := repo.Checkout(d.Branch, "", "")
+ wc, err := GlobalDownloader.Get(repo, d.Branch, "", "")
if err != nil {
return err
}
@@ -122,10 +122,6 @@
if err := vendor.WriteManifest(manifestFile(), m); err != nil {
return err
}
-
- if err := wc.Destroy(); err != nil {
- return err
- }
}
return nil