func (d *VersionHandler) pkgPath(pkg string) string { root, sub := util.NormalizeName(pkg) // For the parent applications source skip the cache. if root == d.Config.Name { pth := gpath.Basepath() return filepath.Join(pth, filepath.FromSlash(sub)) } dep := d.Config.Imports.Get(root) if dep == nil { dep = d.Config.DevImports.Get(root) } if dep == nil { dep, _ = d.Use.Get(root) if dep == nil { dep = &cfg.Dependency{Name: root} } } key, err := cache.Key(dep.Remote()) if err != nil { msg.Die("Error generating cache key for %s", dep.Name) } return filepath.Join(cache.Location(), "src", key, filepath.FromSlash(sub)) }
// ConcurrentUpdate takes a list of dependencies and updates in parallel. func ConcurrentUpdate(deps []*cfg.Dependency, i *Installer, c *cfg.Config) error { done := make(chan struct{}, concurrentWorkers) in := make(chan *cfg.Dependency, concurrentWorkers) var wg sync.WaitGroup var lock sync.Mutex var returnErr error for ii := 0; ii < concurrentWorkers; ii++ { go func(ch <-chan *cfg.Dependency) { for { select { case dep := <-ch: loc := dep.Remote() key, err := cache.Key(loc) if err != nil { msg.Die(err.Error()) } cache.Lock(key) if err := VcsUpdate(dep, i.Force, 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. lock.Lock() if returnErr == nil { returnErr = err } else { returnErr = cli.NewMultiError(returnErr, err) } lock.Unlock() } cache.Unlock(key) wg.Done() case <-done: return } } }(in) } for _, dep := range deps { if !c.HasIgnore(dep.Name) { wg.Add(1) in <- dep } } wg.Wait() // Close goroutines setting the version for ii := 0; ii < concurrentWorkers; ii++ { done <- struct{}{} } return returnErr }
// VcsGet figures out how to fetch a dependency, and then gets it. // // VcsGet installs into the cache. func VcsGet(dep *cfg.Dependency) error { key, err := cp.Key(dep.Remote()) if err != nil { msg.Die("Cache key generation error: %s", err) } location := cp.Location() d := filepath.Join(location, "src", key) repo, err := dep.GetRepo(d) if err != nil { return err } // If the directory does not exist this is a first cache. if _, err = os.Stat(d); os.IsNotExist(err) { msg.Debug("Adding %s to the cache for the first time", dep.Name) err = repo.Get() if err != nil { return err } branch := findCurrentBranch(repo) if branch != "" { msg.Debug("Saving default branch for %s", repo.Remote()) c := cp.RepoInfo{DefaultBranch: branch} err = cp.SaveRepoData(key, c) if err == cp.ErrCacheDisabled { msg.Debug("Unable to cache default branch because caching is disabled") } else if err != nil { msg.Debug("Error saving %s to cache. Error: %s", repo.Remote(), err) } } } else { msg.Debug("Updating %s in the cache", dep.Name) err = repo.Update() if err != nil { return err } } return nil }
// LazyConcurrentUpdate updates only deps that are not already checkout out at the right version. // // This is only safe when updating from a lock file. func LazyConcurrentUpdate(deps []*cfg.Dependency, i *Installer, c *cfg.Config) error { newDeps := []*cfg.Dependency{} for _, dep := range deps { key, err := cache.Key(dep.Remote()) if err != nil { newDeps = append(newDeps, dep) continue } destPath := filepath.Join(cache.Location(), "src", key) // Get a VCS object for this directory repo, err := dep.GetRepo(destPath) if err != nil { newDeps = append(newDeps, dep) continue } ver, err := repo.Version() if err != nil { newDeps = append(newDeps, dep) continue } if dep.Reference != "" { ci, err := repo.CommitInfo(dep.Reference) if err == nil && ci.Commit == dep.Reference { msg.Info("--> Found desired version locally %s %s!", dep.Name, dep.Reference) continue } } msg.Debug("--> Queue %s for update (%s != %s).", dep.Name, ver, dep.Reference) newDeps = append(newDeps, dep) } if len(newDeps) > 0 { return ConcurrentUpdate(newDeps, i, c) } return nil }
func (d *VersionHandler) pkgPath(pkg string) string { root, sub := util.NormalizeName(pkg) dep := d.Config.Imports.Get(root) if dep == nil { dep = d.Config.DevImports.Get(root) } if dep == nil { dep, _ = d.Use.Get(root) if dep == nil { dep = &cfg.Dependency{Name: root} } } key, err := cache.Key(dep.Remote()) if err != nil { msg.Die("Error generating cache key for %s", dep.Name) } return filepath.Join(cache.Location(), "src", key, sub) }
// PkgPath resolves the location on the filesystem where the package should be. // This handles making sure to use the cache location. func (m *MissingPackageHandler) PkgPath(pkg string) string { root, sub := util.NormalizeName(pkg) d := m.Config.Imports.Get(root) if d == nil { d = m.Config.DevImports.Get(root) } if d == nil { d, _ = m.Use.Get(root) if d == nil { d = &cfg.Dependency{Name: root} } } key, err := cache.Key(d.Remote()) if err != nil { msg.Die("Error generating cache key for %s", d.Name) } return filepath.Join(cache.Location(), "src", key, sub) }
func wizardFindVersions(d *cfg.Dependency) { l, err := cache.Location() if err != nil { msg.Debug("Problem detecting cache location: %s", err) return } var remote string if d.Repository != "" { remote = d.Repository } else { remote = "https://" + d.Name } key, err := cache.Key(remote) if err != nil { msg.Debug("Problem generating cache key for %s: %s", remote, err) return } local := filepath.Join(l, "src", key) repo, err := vcs.NewRepo(remote, local) if err != nil { msg.Debug("Problem getting repo instance: %s", err) return } var useLocal bool if _, err = os.Stat(local); err == nil { useLocal = true } // Git endpoints allow for querying without fetching the codebase locally. // We try that first to avoid fetching right away. Is this premature // optimization? cc := true if !useLocal && repo.Vcs() == vcs.Git { out, err2 := exec.Command("git", "ls-remote", remote).CombinedOutput() if err2 == nil { cache.MemTouch(remote) cc = false lines := strings.Split(string(out), "\n") for _, i := range lines { ti := strings.TrimSpace(i) if found := createGitParseVersion.FindString(ti); found != "" { tg := strings.TrimPrefix(strings.TrimSuffix(found, "^{}"), "tags/") cache.MemPut(remote, tg) if d.Reference != "" && strings.HasPrefix(ti, d.Reference) { cache.MemSetCurrent(remote, tg) } } } } } if cc { cache.Lock(key) cache.MemTouch(remote) if _, err = os.Stat(local); os.IsNotExist(err) { repo.Get() branch := findCurrentBranch(repo) c := cache.RepoInfo{DefaultBranch: branch} err = cache.SaveRepoData(key, c) if err != nil { msg.Debug("Error saving cache repo details: %s", err) } } else { repo.Update() } tgs, err := repo.Tags() if err != nil { msg.Debug("Problem getting tags: %s", err) } else { for _, v := range tgs { cache.MemPut(remote, v) } } if d.Reference != "" && repo.IsReference(d.Reference) { tgs, err = repo.TagsFromCommit(d.Reference) if err != nil { msg.Debug("Problem getting tags for commit: %s", err) } else { if len(tgs) > 0 { for _, v := range tgs { if !(repo.Vcs() == vcs.Hg && v == "tip") { cache.MemSetCurrent(remote, v) } } } } } cache.Unlock(key) } }
// ConcurrentUpdate takes a list of dependencies and updates in parallel. func ConcurrentUpdate(deps []*cfg.Dependency, cwd string, i *Installer, c *cfg.Config) error { done := make(chan struct{}, concurrentWorkers) in := make(chan *cfg.Dependency, concurrentWorkers) var wg sync.WaitGroup var lock sync.Mutex var returnErr error msg.Info("Downloading dependencies. Please wait...") for ii := 0; ii < concurrentWorkers; ii++ { go func(ch <-chan *cfg.Dependency) { for { select { case dep := <-ch: var loc string if dep.Repository != "" { loc = dep.Repository } else { loc = "https://" + dep.Name } key, err := cache.Key(loc) if err != nil { msg.Die(err.Error()) } cache.Lock(key) dest := filepath.Join(i.VendorPath(), dep.Name) 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. lock.Lock() if returnErr == nil { returnErr = err } else { returnErr = cli.NewMultiError(returnErr, err) } lock.Unlock() } cache.Unlock(key) wg.Done() case <-done: return } } }(in) } for _, dep := range deps { if !c.HasIgnore(dep.Name) { wg.Add(1) in <- dep } } wg.Wait() // Close goroutines setting the version for ii := 0; ii < concurrentWorkers; ii++ { done <- struct{}{} } return returnErr }
// Export from the cache to the vendor directory func (i *Installer) Export(conf *cfg.Config) error { tempDir, err := ioutil.TempDir(gpath.Tmp, "glide-vendor") if err != nil { return err } defer func() { err = os.RemoveAll(tempDir) if err != nil { msg.Err(err.Error()) } }() vp := filepath.Join(tempDir, "vendor") err = os.MkdirAll(vp, 0755) msg.Info("Exporting resolved dependencies...") done := make(chan struct{}, concurrentWorkers) in := make(chan *cfg.Dependency, concurrentWorkers) var wg sync.WaitGroup var lock sync.Mutex var returnErr error for ii := 0; ii < concurrentWorkers; ii++ { go func(ch <-chan *cfg.Dependency) { for { select { case dep := <-ch: loc := dep.Remote() key, err := cache.Key(loc) if err != nil { msg.Die(err.Error()) } cache.Lock(key) cdir := filepath.Join(cache.Location(), "src", key) repo, err := dep.GetRepo(cdir) if err != nil { msg.Die(err.Error()) } msg.Info("--> Exporting %s", dep.Name) if err := repo.ExportDir(filepath.Join(vp, filepath.ToSlash(dep.Name))); err != nil { msg.Err("Export failed for %s: %s\n", dep.Name, err) // Capture the error while making sure the concurrent // operations don't step on each other. lock.Lock() if returnErr == nil { returnErr = err } else { returnErr = cli.NewMultiError(returnErr, err) } lock.Unlock() } cache.Unlock(key) wg.Done() case <-done: return } } }(in) } for _, dep := range conf.Imports { if !conf.HasIgnore(dep.Name) { err = os.MkdirAll(filepath.Join(vp, filepath.ToSlash(dep.Name)), 0755) if err != nil { lock.Lock() if returnErr == nil { returnErr = err } else { returnErr = cli.NewMultiError(returnErr, err) } lock.Unlock() } wg.Add(1) in <- dep } } if i.ResolveTest { for _, dep := range conf.DevImports { if !conf.HasIgnore(dep.Name) { err = os.MkdirAll(filepath.Join(vp, filepath.ToSlash(dep.Name)), 0755) if err != nil { lock.Lock() if returnErr == nil { returnErr = err } else { returnErr = cli.NewMultiError(returnErr, err) } lock.Unlock() } wg.Add(1) in <- dep } } } wg.Wait() // Close goroutines setting the version for ii := 0; ii < concurrentWorkers; ii++ { done <- struct{}{} } if returnErr != nil { return returnErr } msg.Info("Replacing existing vendor dependencies") err = os.RemoveAll(i.VendorPath()) if err != nil { return err } err = os.Rename(vp, i.VendorPath()) if err != nil { // When there are different physical devices we cannot rename cross device. // Instead we copy. switch terr := err.(type) { case *os.LinkError: // syscall.EXDEV is the common name for the cross device link error // which has varying output text across different operating systems. if terr.Err == syscall.EXDEV { msg.Debug("Cross link err, trying manual copy: %s", err) return gpath.CopyDir(vp, i.VendorPath()) } else if runtime.GOOS == "windows" { // In windows it can drop down to an operating system call that // returns an operating system error with a different number and // message. Checking for that as a fall back. noerr, ok := terr.Err.(syscall.Errno) // 0x11 (ERROR_NOT_SAME_DEVICE) is the windows error. // See https://msdn.microsoft.com/en-us/library/cc231199.aspx if ok && noerr == 0x11 { msg.Debug("Cross link err on Windows, trying manual copy: %s", err) return gpath.CopyDir(vp, i.VendorPath()) } } } } return err }
// defaultBranch tries to ascertain the default branch for the given repo. // Some repos will have multiple branches in them (e.g. Git) while others // (e.g. Svn) will not. func defaultBranch(repo v.Repo, home string) string { // Svn and Bzr use different locations (paths or entire locations) // for branches so we won't have a default branch. if repo.Vcs() == v.Svn || repo.Vcs() == v.Bzr { return "" } // Check the cache for a value. key, kerr := cp.Key(repo.Remote()) var d cp.RepoInfo if kerr == nil { d, err := cp.RepoData(key) if err == nil { if d.DefaultBranch != "" { return d.DefaultBranch } } } // If we don't have it in the store try some APIs r := repo.Remote() u, err := url.Parse(r) if err != nil { return "" } if u.Scheme == "" { // Where there is no scheme we try urls like [email protected]:foo/bar r = strings.Replace(r, ":", "/", -1) r = "ssh://" + r u, err = url.Parse(r) if err != nil { return "" } u.Scheme = "" } if u.Host == "github.com" { parts := strings.Split(u.Path, "/") if len(parts) != 2 { return "" } api := fmt.Sprintf("https://api.github.com/repos/%s/%s", parts[0], parts[1]) resp, err := http.Get(api) if err != nil { return "" } defer resp.Body.Close() if resp.StatusCode >= 300 || resp.StatusCode < 200 { return "" } body, err := ioutil.ReadAll(resp.Body) var data interface{} err = json.Unmarshal(body, &data) if err != nil { return "" } gh := data.(map[string]interface{}) db := gh["default_branch"].(string) if kerr == nil { d.DefaultBranch = db err := cp.SaveRepoData(key, d) if err == cp.ErrCacheDisabled { msg.Debug("Unable to cache default branch because caching is disabled") } else if err != nil { msg.Debug("Error saving %s to cache. Error: %s", repo.Remote(), err) } } return db } if u.Host == "bitbucket.org" { parts := strings.Split(u.Path, "/") if len(parts) != 2 { return "" } api := fmt.Sprintf("https://bitbucket.org/api/1.0/repositories/%s/%s/main-branch/", parts[0], parts[1]) resp, err := http.Get(api) if err != nil { return "" } defer resp.Body.Close() if resp.StatusCode >= 300 || resp.StatusCode < 200 { return "" } body, err := ioutil.ReadAll(resp.Body) var data interface{} err = json.Unmarshal(body, &data) if err != nil { return "" } bb := data.(map[string]interface{}) db := bb["name"].(string) if kerr == nil { d.DefaultBranch = db err := cp.SaveRepoData(key, d) if err == cp.ErrCacheDisabled { msg.Debug("Unable to cache default branch because caching is disabled") } else if err != nil { msg.Debug("Error saving %s to cache. Error: %s", repo.Remote(), err) } } return db } return "" }
// VcsGet figures out how to fetch a dependency, and then gets it. // // VcsGet installs into the dest. func VcsGet(dep *cfg.Dependency, dest, home string, cache, cacheGopath, useGopath bool) error { // When not skipping the $GOPATH look in it for a copy of the package if useGopath { // Check if the $GOPATH has a viable version to use and if so copy to vendor gps := gpath.Gopaths() for _, p := range gps { d := filepath.Join(p, "src", dep.Name) if _, err := os.Stat(d); err == nil { empty, err := gpath.IsDirectoryEmpty(d) if empty || err != nil { continue } repo, err := dep.GetRepo(d) if err != nil { continue } // Dirty repos have uncommitted changes. if repo.IsDirty() { continue } // Having found a repo we copy it to vendor and update it. msg.Info("Copying package %s from the GOPATH.", dep.Name) msg.Debug("Found %s in GOPATH at %s. Copying to %s", dep.Name, d, dest) err = gpath.CopyDir(d, dest) if err != nil { return err } // Update the repo in the vendor directory msg.Debug("Updating %s, now in the vendor path at %s", dep.Name, dest) repo, err = dep.GetRepo(dest) if err != nil { return err } err = repo.Update() if err != nil { return err } // If there is no reference set on the dep we try to checkout // the default branch. if dep.Reference == "" { db := defaultBranch(repo, home) if db != "" { err = repo.UpdateVersion(db) if err != nil && msg.Default.IsDebugging { msg.Debug("Attempting to set the version on %s to %s failed. Error %s", dep.Name, db, err) } } } return nil } } } // When opting in to cache in the GOPATH attempt to do put a copy there. if cacheGopath { // Since we didn't find an existing copy in the GOPATHs try to clone there. gp := gpath.Gopath() if gp != "" { d := filepath.Join(gp, "src", dep.Name) if _, err := os.Stat(d); os.IsNotExist(err) { // Empty directory so we checkout out the code here. msg.Debug("Retrieving %s to %s before copying to vendor", dep.Name, d) repo, err := dep.GetRepo(d) if err != nil { return err } repo.Get() branch := findCurrentBranch(repo) if branch != "" { // we know the default branch so we can store it in the cache var loc string if dep.Repository != "" { loc = dep.Repository } else { loc = "https://" + dep.Name } key, err := cp.Key(loc) if err == nil { msg.Debug("Saving default branch for %s", repo.Remote()) c := cp.RepoInfo{DefaultBranch: branch} err = cp.SaveRepoData(key, c) if msg.Default.IsDebugging && err == cp.ErrCacheDisabled { msg.Debug("Unable to cache default branch because caching is disabled") } } } msg.Debug("Copying %s from GOPATH at %s to %s", dep.Name, d, dest) err = gpath.CopyDir(d, dest) if err != nil { return err } return nil } } } // If opting in to caching attempt to put it in the cache folder if cache { // Check if the cache has a viable version and try to use that. var loc string if dep.Repository != "" { loc = dep.Repository } else { loc = "https://" + dep.Name } key, err := cp.Key(loc) if err == nil { location, err := cp.Location() if err != nil { return err } d := filepath.Join(location, "src", key) repo, err := dep.GetRepo(d) if err != nil { return err } // If the directory does not exist this is a first cache. if _, err = os.Stat(d); os.IsNotExist(err) { msg.Debug("Adding %s to the cache for the first time", dep.Name) err = repo.Get() if err != nil { return err } branch := findCurrentBranch(repo) if branch != "" { // we know the default branch so we can store it in the cache var loc string if dep.Repository != "" { loc = dep.Repository } else { loc = "https://" + dep.Name } key, err := cp.Key(loc) if err == nil { msg.Debug("Saving default branch for %s", repo.Remote()) c := cp.RepoInfo{DefaultBranch: branch} err = cp.SaveRepoData(key, c) if err == cp.ErrCacheDisabled { msg.Debug("Unable to cache default branch because caching is disabled") } else if err != nil { msg.Debug("Error saving %s to cache. Error: %s", repo.Remote(), err) } } } } else { msg.Debug("Updating %s in the cache", dep.Name) err = repo.Update() if err != nil { return err } } msg.Debug("Copying %s from the cache to %s", dep.Name, dest) err = gpath.CopyDir(d, dest) if err != nil { return err } return nil } msg.Warn("Cache key generation error: %s", err) } // If unable to cache pull directly into the vendor/ directory. repo, err := dep.GetRepo(dest) if err != nil { return err } gerr := repo.Get() // Attempt to cache the default branch if cache { if branch := findCurrentBranch(repo); branch != "" { // we know the default branch so we can store it in the cache var loc string if dep.Repository != "" { loc = dep.Repository } else { loc = "https://" + dep.Name } key, err := cp.Key(loc) if err == nil { msg.Debug("Saving default branch for %s", repo.Remote()) c := cp.RepoInfo{DefaultBranch: branch} err = cp.SaveRepoData(key, c) if err == cp.ErrCacheDisabled { msg.Debug("Unable to cache default branch because caching is disabled") } else if err != nil { msg.Debug("Error saving %s to cache - Error: %s", repo.Remote(), err) } } } } return gerr }
// SetReference is a command to set the VCS reference (commit id, tag, etc) for // a project. func SetReference(conf *cfg.Config, resolveTest bool) error { if len(conf.Imports) == 0 && len(conf.DevImports) == 0 { msg.Info("No references set.\n") return nil } done := make(chan struct{}, concurrentWorkers) in := make(chan *cfg.Dependency, concurrentWorkers) var wg sync.WaitGroup var lock sync.Mutex var returnErr error for i := 0; i < concurrentWorkers; i++ { go func(ch <-chan *cfg.Dependency) { for { select { case dep := <-ch: var loc string if dep.Repository != "" { loc = dep.Repository } else { loc = "https://" + dep.Name } key, err := cache.Key(loc) if err != nil { msg.Die(err.Error()) } cache.Lock(key) if err := VcsVersion(dep); err != nil { msg.Err("Failed to set version on %s to %s: %s\n", dep.Name, dep.Reference, err) // Capture the error while making sure the concurrent // operations don't step on each other. lock.Lock() if returnErr == nil { returnErr = err } else { returnErr = cli.NewMultiError(returnErr, err) } lock.Unlock() } cache.Unlock(key) wg.Done() case <-done: return } } }(in) } for _, dep := range conf.Imports { if !conf.HasIgnore(dep.Name) { wg.Add(1) in <- dep } } if resolveTest { for _, dep := range conf.DevImports { if !conf.HasIgnore(dep.Name) { wg.Add(1) in <- dep } } } wg.Wait() // Close goroutines setting the version for i := 0; i < concurrentWorkers; i++ { done <- struct{}{} } // close(done) // close(in) return returnErr }
// Export from the cache to the vendor directory func (i *Installer) Export(conf *cfg.Config) error { tempDir, err := ioutil.TempDir(gpath.Tmp, "glide-vendor") if err != nil { return err } defer func() { err = os.RemoveAll(tempDir) if err != nil { msg.Err(err.Error()) } }() vp := filepath.Join(tempDir, "vendor") err = os.MkdirAll(vp, 0755) msg.Info("Exporting resolved dependencies...") done := make(chan struct{}, concurrentWorkers) in := make(chan *cfg.Dependency, concurrentWorkers) var wg sync.WaitGroup var lock sync.Mutex var returnErr error for ii := 0; ii < concurrentWorkers; ii++ { go func(ch <-chan *cfg.Dependency) { for { select { case dep := <-ch: loc := dep.Remote() key, err := cache.Key(loc) if err != nil { msg.Die(err.Error()) } cache.Lock(key) cdir := filepath.Join(cache.Location(), "src", key) repo, err := dep.GetRepo(cdir) if err != nil { msg.Die(err.Error()) } msg.Info("--> Exporting %s", dep.Name) if err := repo.ExportDir(filepath.Join(vp, filepath.ToSlash(dep.Name))); err != nil { msg.Err("Export failed for %s: %s\n", dep.Name, err) // Capture the error while making sure the concurrent // operations don't step on each other. lock.Lock() if returnErr == nil { returnErr = err } else { returnErr = cli.NewMultiError(returnErr, err) } lock.Unlock() } cache.Unlock(key) wg.Done() case <-done: return } } }(in) } for _, dep := range conf.Imports { if !conf.HasIgnore(dep.Name) { err = os.MkdirAll(filepath.Join(vp, filepath.ToSlash(dep.Name)), 0755) if err != nil { lock.Lock() if returnErr == nil { returnErr = err } else { returnErr = cli.NewMultiError(returnErr, err) } lock.Unlock() } wg.Add(1) in <- dep } } if i.ResolveTest { for _, dep := range conf.DevImports { if !conf.HasIgnore(dep.Name) { err = os.MkdirAll(filepath.Join(vp, filepath.ToSlash(dep.Name)), 0755) if err != nil { lock.Lock() if returnErr == nil { returnErr = err } else { returnErr = cli.NewMultiError(returnErr, err) } lock.Unlock() } wg.Add(1) in <- dep } } } wg.Wait() // Close goroutines setting the version for ii := 0; ii < concurrentWorkers; ii++ { done <- struct{}{} } if returnErr != nil { return returnErr } msg.Info("Replacing existing vendor dependencies") err = os.RemoveAll(i.VendorPath()) if err != nil { return err } err = os.Rename(vp, i.VendorPath()) // When there are different physical devices we cannot rename cross device. // Fall back to manual copy. if err != nil && strings.Contains(err.Error(), "cross-device link") { msg.Debug("Cross link err, trying manual copy: %s", err) err = gpath.CopyDir(vp, i.VendorPath()) } return err }
// VcsUpdate updates to a particular checkout based on the VCS setting. func VcsUpdate(dep *cfg.Dependency, force 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. if dep.Pin != "" { msg.Debug("Dependency %s has already been pinned. Fetching updates skipped.", dep.Name) return nil } if updated.Check(dep.Name) { msg.Debug("%s was already updated, skipping.", dep.Name) return nil } updated.Add(dep.Name) if filterArchOs(dep) { msg.Info("%s is not used for %s/%s.\n", dep.Name, runtime.GOOS, runtime.GOARCH) return nil } key, err := cp.Key(dep.Remote()) if err != nil { msg.Die("Cache key generation error: %s", err) } location := cp.Location() dest := filepath.Join(location, "src", key) // If destination doesn't exist we need to perform an initial checkout. if _, err := os.Stat(dest); os.IsNotExist(err) { msg.Info("--> Fetching %s.", dep.Name) if err = VcsGet(dep); err != nil { msg.Warn("Unable to checkout %s\n", dep.Name) return err } } else { // At this point we have a directory for the package. msg.Info("--> Fetching updates for %s.", dep.Name) // When the directory is not empty and has no VCS directory it's // a vendored files situation. empty, err := gpath.IsDirectoryEmpty(dest) if err != nil { return err } _, err = v.DetectVcsFromFS(dest) if empty == true && err == v.ErrCannotDetectVCS { msg.Warn("Cached version of %s is an empty directory. Fetching a new copy of the dependency.", dep.Name) msg.Debug("Removing empty directory %s", dest) err := os.RemoveAll(dest) if err != nil { return err } if err = VcsGet(dep); err != nil { msg.Warn("Unable to checkout %s\n", dep.Name) return err } } else { repo, err := dep.GetRepo(dest) // Tried to checkout a repo to a path that does not work. Either the // type or endpoint has changed. Force is being passed in so the old // location can be removed and replaced with the new one. // Warning, any changes in the old location will be deleted. // TODO: Put dirty checking in on the existing local checkout. if (err == v.ErrWrongVCS || err == v.ErrWrongRemote) && force == true { newRemote := dep.Remote() msg.Warn("Replacing %s with contents from %s\n", dep.Name, newRemote) rerr := os.RemoveAll(dest) if rerr != nil { return rerr } if err = VcsGet(dep); err != nil { msg.Warn("Unable to checkout %s\n", dep.Name) return err } repo, err = dep.GetRepo(dest) if err != nil { return err } } else if err != nil { return err } else if repo.IsDirty() { return fmt.Errorf("%s contains uncommitted changes. Skipping update", dep.Name) } ver := dep.Reference if ver == "" { ver = defaultBranch(repo) } // Check if the current version is a tag or commit id. If it is // and that version is already checked out we can skip updating // which is faster than going out to the Internet to perform // an update. if ver != "" { version, err := repo.Version() if err != nil { return err } ib, err := isBranch(ver, repo) if err != nil { return err } // If the current version equals the ref and it's not a // branch it's a tag or commit id so we can skip // performing an update. if version == ver && !ib { msg.Debug("%s is already set to version %s. Skipping update.", dep.Name, dep.Reference) return nil } } if err := repo.Update(); err != nil { msg.Warn("Download failed.\n") return err } } } return nil }
// VcsVersion set the VCS version for a checkout. func VcsVersion(dep *cfg.Dependency) 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. if dep.Pin != "" { msg.Debug("Dependency %s has already been pinned. Setting version skipped.", dep.Name) return nil } key, err := cp.Key(dep.Remote()) if err != nil { msg.Die("Cache key generation error: %s", err) } location := cp.Location() cwd := filepath.Join(location, "src", key) // If there is no reference configured there is nothing to set. if dep.Reference == "" { // Before exiting update the pinned version repo, err := dep.GetRepo(cwd) if err != nil { return err } dep.Pin, err = repo.Version() if err != nil { return err } return nil } // When the directory is not empty and has no VCS directory it's // a vendored files situation. empty, err := gpath.IsDirectoryEmpty(cwd) if err != nil { return err } _, err = v.DetectVcsFromFS(cwd) if empty == false && err == v.ErrCannotDetectVCS { return fmt.Errorf("Cache directory missing VCS information for %s", dep.Name) } repo, err := dep.GetRepo(cwd) if err != nil { return err } ver := dep.Reference // References in Git can begin with a ^ which is similar to semver. // If there is a ^ prefix we assume it's a semver constraint rather than // part of the git/VCS commit id. if repo.IsReference(ver) && !strings.HasPrefix(ver, "^") { msg.Info("--> Setting version for %s to %s.\n", dep.Name, ver) } else { // Create the constraint first to make sure it's valid before // working on the repo. constraint, err := semver.NewConstraint(ver) // Make sure the constriant is valid. At this point it's not a valid // reference so if it's not a valid constrint we can exit early. if err != nil { msg.Warn("The reference '%s' is not valid\n", ver) return err } // Get the tags and branches (in that order) refs, err := getAllVcsRefs(repo) if err != nil { return err } // Convert and filter the list to semver.Version instances semvers := getSemVers(refs) // Sort semver list sort.Sort(sort.Reverse(semver.Collection(semvers))) found := false for _, v := range semvers { if constraint.Check(v) { found = true // If the constrint passes get the original reference ver = v.Original() break } } if found { msg.Info("--> Detected semantic version. Setting version for %s to %s.", dep.Name, ver) } else { msg.Warn("--> Unable to find semantic version for constraint %s %s", dep.Name, ver) } } if err := repo.UpdateVersion(ver); err != nil { return err } dep.Pin, err = repo.Version() if err != nil { return err } return nil }