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