Udating the wizard to be less intrusive
diff --git a/action/config_wizard.go b/action/config_wizard.go new file mode 100644 index 0000000..16886bb --- /dev/null +++ b/action/config_wizard.go
@@ -0,0 +1,362 @@ +package action + +import ( + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + + "github.com/Masterminds/glide/cache" + "github.com/Masterminds/glide/cfg" + "github.com/Masterminds/glide/msg" + gpath "github.com/Masterminds/glide/path" + "github.com/Masterminds/semver" + "github.com/Masterminds/vcs" +) + +// ConfigWizard reads configuration from a glide.yaml file and attempts to suggest +// improvements. The wizard is interactive. +func ConfigWizard(base string) { + _, err := gpath.Glide() + glidefile := gpath.GlideFile + if err != nil { + msg.Info("Unable to find a glide.yaml file. Would you like to create one now? Yes (Y) or No (N)") + bres := msg.PromptUntilYorN() + if bres { + // Guess deps + conf := guessDeps(base, false) + // Write YAML + if err := conf.WriteFile(glidefile); err != nil { + msg.Die("Could not save %s: %s", glidefile, err) + } + } else { + msg.Err("Unable to find configuration file. Please create configuration information to continue.") + } + } + + conf := EnsureConfig() + + err = cache.Setup() + if err != nil { + msg.Die("Problem setting up cache: %s", err) + } + + msg.Info("Looking for dependencies to make suggestions on") + msg.Info("--> Scanning for dependencies not using verion ranges") + msg.Info("--> Scanning for dependencies using commit ids") + var deps []*cfg.Dependency + for _, dep := range conf.Imports { + if wizardLookInto(dep) { + deps = append(deps, dep) + } + } + + msg.Info("Gathering information on each dependency") + msg.Info("--> This may take a moment. Especially on a codebase with many dependencies") + msg.Info("--> Gathering release information for dependencies") + msg.Info("--> Looking for dependency imports where versions are commit ids") + for _, dep := range deps { + wizardFindVersions(dep) + } + + var changes int + for _, dep := range deps { + var remote string + if dep.Repository != "" { + remote = dep.Repository + } else { + remote = "https://" + dep.Name + } + + // First check, ask if the tag should be used instead of the commit id for it. + cur := cache.MemCurrent(remote) + if cur != "" && cur != dep.Reference { + wizardSugOnce() + var dres bool + asked, use, val := wizardOnce("current") + if !use { + dres = wizardAskCurrent(cur, dep) + } + if !asked { + as := wizardRemember() + wizardSetOnce("current", as, dres) + } + + if asked && use { + dres = val.(bool) + } + + if dres { + msg.Info("Updating %s to use the tag %s instead of commit id %s", dep.Name, cur, dep.Reference) + dep.Reference = cur + changes++ + } + } + + // Second check, if no version is being used and there's a semver release ask about latest. + memlatest := cache.MemLatest(remote) + if dep.Reference == "" && memlatest != "" { + wizardSugOnce() + var dres bool + asked, use, val := wizardOnce("latest") + if !use { + dres = wizardAskLatest(memlatest, dep) + } + if !asked { + as := wizardRemember() + wizardSetOnce("latest", as, dres) + } + + if asked && use { + dres = val.(bool) + } + + if dres { + msg.Info("Updating %s to use the release %s instead of no release", dep.Name, cur) + dep.Reference = cur + changes++ + } + } + + // Third check, if the version is semver offer to use a range instead. + sv, err := semver.NewVersion(dep.Reference) + if err == nil { + wizardSugOnce() + var res string + asked, use, val := wizardOnce("range") + if !use { + res = wizardAskRange(sv, dep) + } + if !asked { + as := wizardRemember() + wizardSetOnce("range", as, res) + } + + if asked && use { + res = val.(string) + } + + if res == "m" { + r := "^" + sv.String() + msg.Info("Updating %s to use the range %s instead of commit id %s", dep.Name, r, dep.Reference) + dep.Reference = r + changes++ + } else if res == "p" { + r := "~" + sv.String() + msg.Info("Updating %s to use the range %s instead of commit id %s", dep.Name, r, dep.Reference) + dep.Reference = r + changes++ + } + } + } + + if changes > 0 { + msg.Info("Configuration changes have been made. Would you like to write these") + msg.Info("changes to your configuration file? Yes (Y) or No (N)") + dres := msg.PromptUntilYorN() + if dres { + msg.Info("Writing updates to configuration file (%s)", glidefile) + if err := conf.WriteFile(glidefile); err != nil { + msg.Die("Could not save %s: %s", glidefile, err) + } + msg.Info("You can now edit the glide.yaml file.:") + msg.Info("--> For more information on versions and ranges see https://glide.sh/docs/versions/") + msg.Info("--> For details on additional metadata see https://glide.sh/docs/glide.yaml/") + } else { + msg.Warn("Change not written to configuration file") + } + } else { + msg.Info("No proposed changes found. Have a nice day.") + } +} + +var wizardOnceVal = make(map[string]interface{}) +var wizardOnceDo = make(map[string]bool) +var wizardOnceAsked = make(map[string]bool) + +var wizardSuggeseOnce bool + +func wizardSugOnce() { + if !wizardSuggeseOnce { + msg.Info("Here are some suggestions...") + } + wizardSuggeseOnce = true +} + +// Returns if it's you should prompt, if not prompt if you should use stored value, +// and stored value if it has one. +func wizardOnce(name string) (bool, bool, interface{}) { + return wizardOnceAsked[name], wizardOnceDo[name], wizardOnceVal[name] +} + +func wizardSetOnce(name string, prompt bool, val interface{}) { + wizardOnceAsked[name] = true + wizardOnceDo[name] = prompt + wizardOnceVal[name] = val +} + +func wizardRemember() bool { + msg.Info("Would you like to remember the previous decision and apply it to future") + msg.Info("dependencies? Yes (Y) or No (N)") + return msg.PromptUntilYorN() +} + +func wizardAskRange(ver *semver.Version, d *cfg.Dependency) string { + vstr := ver.String() + msg.Info("The package %s appears to use semantic versions (http://semver.org).", d.Name) + msg.Info("Would you like to track the latest minor or patch releases (major.minor.path)?") + msg.Info("Tracking minor version releases would use '>= %s, < %d.0.0' ('^%s'). Tracking patch version", vstr, ver.Major()+1, vstr) + msg.Info("releases would use '>= %s, < %d.%d.0' ('~%s'). For more information on Glide versions", vstr, ver.Major(), ver.Minor()+1, vstr) + msg.Info("and ranges see https://glide.sh/docs/versions") + msg.Info("Minor (M), Patch (P), or Skip Ranges (S)?") + res, err := msg.PromptUntil([]string{"minor", "m", "patch", "p", "skip ranges", "s"}) + if err != nil { + msg.Die("Error processing response: %s", err) + } + if res == "m" || res == "minor" { + return "m" + } else if res == "p" || res == "patch" { + return "p" + } + + return "s" +} + +func wizardAskCurrent(cur string, d *cfg.Dependency) bool { + msg.Info("The package %s is currently set to use the version %s.", d.Name, d.Reference) + msg.Info("There is an equivalent semantic version (http://semver.org) release of %s. Would", cur) + msg.Info("you like to use that instead? Yes (Y) or No (N)") + return msg.PromptUntilYorN() +} + +func wizardAskLatest(latest string, d *cfg.Dependency) bool { + msg.Info("The package %s appears to have Semantic Version releases (http://semver.org). ", d.Name) + msg.Info("The latestrelease is %s. You are currently not using a release. Would you like", latest) + msg.Info("to use this release? Yes (Y) or No (N)") + return msg.PromptUntilYorN() +} + +func wizardLookInto(d *cfg.Dependency) bool { + _, err := semver.NewConstraint(d.Reference) + + // The existing version is already a valid semver constraint so we skip suggestions. + if err == nil { + return false + } + + return true +} + +// Note, this really needs a simpler name. +var createGitParseVersion = regexp.MustCompile(`(?m-s)(?:tags)/(\S+)$`) + +func wizardFindVersions(d *cfg.Dependency) { + l, err := cache.Location() + if err != nil { + msg.Debug("Problem detecting cache location: %s", err) + } + 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) + } + + local := filepath.Join(l, "src", key) + repo, err := vcs.NewRepo(remote, local) + if err != nil { + msg.Debug("Problem getting repo instance: %s", err) + } + + 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) + } +} + +func findCurrentBranch(repo vcs.Repo) string { + msg.Debug("Attempting to find current branch for %s", repo.Remote()) + // Svn and Bzr don't have default branches. + if repo.Vcs() == vcs.Svn || repo.Vcs() == vcs.Bzr { + return "" + } + + if repo.Vcs() == vcs.Git || repo.Vcs() == vcs.Hg { + ver, err := repo.Current() + if err != nil { + msg.Debug("Unable to find current branch for %s, error: %s", repo.Remote(), err) + return "" + } + return ver + } + + return "" +}
diff --git a/action/create.go b/action/create.go index fa72e6b..4d1c0bb 100644 --- a/action/create.go +++ b/action/create.go
@@ -2,31 +2,20 @@ import ( "os" - "os/exec" "path/filepath" - "regexp" "sort" "strings" - "sync" - "github.com/Masterminds/glide/cache" "github.com/Masterminds/glide/cfg" "github.com/Masterminds/glide/dependency" "github.com/Masterminds/glide/gb" "github.com/Masterminds/glide/godep" - "github.com/Masterminds/glide/gom" "github.com/Masterminds/glide/gpm" "github.com/Masterminds/glide/msg" gpath "github.com/Masterminds/glide/path" "github.com/Masterminds/glide/util" - "github.com/Masterminds/semver" - "github.com/Masterminds/vcs" ) -// TODO(mattfarina): This file could really use to be cleaned up. It's functional -// but not all that elegant. This mess of code really needs someone to come -// along and clean it up. Pretty please. - // Create creates/initializes a new Glide repository. // // This will fail if a glide.yaml already exists. @@ -36,21 +25,36 @@ // If skipImport is set to true, this will not attempt to import from an existing // GPM, Godep, or GB project if one should exist. However, it will still attempt // to read the local source to determine required packages. -func Create(base string, skipImport, noInteract, skipVerSug bool) { +func Create(base string, skipImport, nonInteractive bool) { glidefile := gpath.GlideFile // Guard against overwrites. guardYAML(glidefile) // Guess deps - conf := guessDeps(base, skipImport, noInteract, skipVerSug) + conf := guessDeps(base, skipImport) // Write YAML - msg.Info("Writing glide.yaml file") + msg.Info("Writing configuration file (%s)", glidefile) if err := conf.WriteFile(glidefile); err != nil { msg.Die("Could not save %s: %s", glidefile, err) } - msg.Info("You can now edit the glide.yaml file. Consider:") - msg.Info("--> Using versions and ranges. See https://glide.sh/docs/versions/") - msg.Info("--> Adding additional metadata. See https://glide.sh/docs/glide.yaml/") + + var res bool + if !nonInteractive { + msg.Info("Would you like Glide to help you find ways to improve your glide.yaml configuration?") + msg.Info("If you want to revisit this step you can use the config-wizard command at any time.") + msg.Info("Yes (Y) or No (N)?") + res = msg.PromptUntilYorN() + if res { + ConfigWizard(base) + } + } + + if !res { + msg.Info("You can now edit the glide.yaml file. Consider:") + msg.Info("--> Using versions and ranges. See https://glide.sh/docs/versions/") + msg.Info("--> Adding additional metadata. See https://glide.sh/docs/glide.yaml/") + msg.Info("--> Running the config-wizard command to improve the versions in your configuration") + } } // guardYAML fails if the given file already exists. @@ -69,7 +73,7 @@ // // FIXME: This function is likely a one-off that has a more standard alternative. // It's also long and could use a refactor. -func guessDeps(base string, skipImport, noInteract, skipVerSug bool) *cfg.Config { +func guessDeps(base string, skipImport bool) *cfg.Config { buildContext, err := util.GetBuildContext() if err != nil { msg.Die("Failed to build an import context: %s", err) @@ -85,17 +89,18 @@ // Import by looking at other package managers and looking over the // entire directory structure. - var deps cfg.Dependencies // Attempt to import from other package managers. if !skipImport { - deps = guessImportDeps(base) - if len(deps) == 0 { - msg.Info("No dependencies found to import") - } + guessImportDeps(base, config) } - msg.Info("Scanning code to look for dependencies") + importLen := len(config.Imports) + if importLen == 0 { + msg.Info("Scanning code to look for dependencies") + } else { + msg.Info("Scanning code to look for dependencies not found in import") + } // Resolve dependencies by looking at the tree. r, err := dependency.NewResolver(base) @@ -118,194 +123,39 @@ vpath = vpath + string(os.PathSeparator) } - var count int - var all string - var allOnce bool for _, pa := range sortable { n := strings.TrimPrefix(pa, vpath) root, subpkg := util.NormalizeName(n) if !config.HasDependency(root) && root != config.Name { - count++ - d := deps.Get(root) - if d == nil { - d = &cfg.Dependency{ - Name: root, - } - msg.Info("--> Found reference to %s", n) - } else { - msg.Info("--> Found imported reference to %s", n) + msg.Info("--> Found reference to %s\n", n) + d := &cfg.Dependency{ + Name: root, } - - all, allOnce = guessAskVersion(noInteract, all, allOnce, skipVerSug, d) - - if subpkg != "" { - if !d.HasSubpackage(subpkg) { - d.Subpackages = append(d.Subpackages, subpkg) - } - msg.Verbose("--> Noting sub-package %s to %s", subpkg, root) + if len(subpkg) > 0 { + d.Subpackages = []string{subpkg} } - config.Imports = append(config.Imports, d) } else if config.HasDependency(root) { if len(subpkg) > 0 { subpkg = strings.TrimPrefix(subpkg, "/") d := config.Imports.Get(root) if !d.HasSubpackage(subpkg) { + msg.Info("--> Adding sub-package %s to %s\n", subpkg, root) d.Subpackages = append(d.Subpackages, subpkg) } - msg.Verbose("--> Noting sub-package %s to %s", subpkg, root) } } } - if !skipImport && len(deps) > count { - var res string - if noInteract { - res = "y" - } else { - msg.Info("%d unused imported dependencies found. These are likely transitive dependencies ", len(deps)-count) - msg.Info("(dependencies of your dependencies). Would you like to track them in your") - msg.Info("glide.yaml file? Note, Glide will automatically scan your codebase to detect") - msg.Info("the complete dependency tree and import the complete tree. If your dependencies") - msg.Info("do not track dependency version information some version information may be lost.") - msg.Info("Yes (Y) or No (N)?") - res, err = msg.PromptUntil([]string{"y", "yes", "n", "no"}) - if err != nil { - msg.Die("Error processing response: %s", err) - } - } - if res == "y" || res == "yes" { - msg.Info("Including additional imports in the glide.yaml file") - for _, dep := range deps { - found := config.Imports.Get(dep.Name) - if found == nil { - config.Imports = append(config.Imports, dep) - if dep.Reference != "" { - all, allOnce = guessAskVersion(noInteract, all, allOnce, skipVerSug, dep) - msg.Info("--> Adding %s at version %s", dep.Name, dep.Reference) - } else { - msg.Info("--> Adding %s", dep.Name) - } - } - } - } + if len(config.Imports) == importLen && importLen != 0 { + msg.Info("--> Code scanning found no additional imports") } return config } -func guessAskVersion(noInteract bool, all string, allonce, skipVerSug bool, d *cfg.Dependency) (string, bool) { - if !noInteract && !skipVerSug && d.Reference == "" { - // If scanning has not happened already, for example this isn't an - // import, try it now. - var loc string - if d.Repository != "" { - loc = d.Repository - } else { - loc = "https://" + d.Name - } - if !guessVersionCache.checked(loc) { - createGuessVersion(loc, d.Reference) - } - - semv := guessVersionCache.get(loc) - if semv != "" { - msg.Info("The package %s appears to have Semantic Version releases. The latest", d.Name) - msg.Info("release is %s. Would you like to use this release? Yes (Y) or No (N)", semv) - res, err2 := msg.PromptUntil([]string{"y", "yes", "n", "no"}) - if err2 != nil { - msg.Die("Error processing response: %s", err2) - } - - if res == "y" || res == "yes" { - d.Reference = semv - } - } - - } - - if !noInteract && d.Reference != "" { - var changedVer bool - ver, err := semver.NewVersion(d.Reference) - if err != nil && !skipVerSug { - var loc string - if d.Repository != "" { - loc = d.Repository - } else { - loc = "https://" + d.Name - } - - semv := guessVersionCache.get(loc) - if semv != "" { - msg.Info("The package %s appears to have Semantic Version releases but the imported data", d.Name) - msg.Info("is not using them. The latest release is %s but the imported data is using %s.", semv, d.Reference) - msg.Info("Would you like to use the latest release version instead? Yes (Y) or No (N)") - res, err2 := msg.PromptUntil([]string{"y", "yes", "n", "no"}) - if err2 != nil { - msg.Die("Error processing response: %s", err2) - } - - if res == "y" || res == "yes" { - d.Reference = semv - changedVer = true - } - } - } - - if changedVer { - ver, err = semver.NewVersion(d.Reference) - } - if err == nil { - if all == "" { - vstr := ver.String() - msg.Info("Imported dependency %s (%s) appears to use semantic versions (http://semver.org).", d.Name, d.Reference) - msg.Info("Would you like Glide to track the latest minor or patch releases (major.minor.path)?") - msg.Info("Tracking minor version releases would use '>= %s, < %d.0.0' ('^%s'). Tracking patch version", vstr, ver.Major()+1, vstr) - msg.Info("releases would use '>= %s, < %d.%d.0' ('~%s'). For more information on Glide versions", vstr, ver.Major(), ver.Minor()+1, vstr) - msg.Info("and ranges see https://glide.sh/docs/versions") - msg.Info("Minor (M), Patch (P), or Skip Ranges (S)?") - res, err := msg.PromptUntil([]string{"minor", "m", "patch", "p", "skip ranges", "s"}) - if err != nil { - msg.Die("Error processing response: %s", err) - } - if res == "m" || res == "minor" { - d.Reference = "^" + vstr - } else if res == "p" || res == "patch" { - d.Reference = "~" + vstr - } - - if !allonce { - msg.Info("Would you like to same response (%s) for future dependencies? Yes (Y) or No (N)", res) - res2, err := msg.PromptUntil([]string{"y", "yes", "n", "no"}) - if err != nil { - msg.Die("Error processing response: %s", err) - } - if res2 == "yes" || res2 == "y" { - return res, true - } - - return "", true - } - - } else { - if all == "m" || all == "minor" { - d.Reference = "^" + ver.String() - } else if all == "p" || all == "patch" { - d.Reference = "~" + ver.String() - } - } - - return all, allonce - } - - return all, allonce - } - - return all, allonce -} - -func guessImportDeps(base string) cfg.Dependencies { +func guessImportDeps(base string, config *cfg.Config) { msg.Info("Attempting to import from other package managers (use --skip-import to skip)") deps := []*cfg.Dependency{} absBase, err := filepath.Abs(base) @@ -315,7 +165,7 @@ if d, ok := guessImportGodep(absBase); ok { msg.Info("Importing Godep configuration") - msg.Warn("--> Godep uses commit id versions. Consider using Semantic Versions with Glide") + msg.Warn("Godep uses commit id versions. Consider using Semantic Versions with Glide") deps = d } else if d, ok := guessImportGPM(absBase); ok { msg.Info("Importing GPM configuration") @@ -323,41 +173,17 @@ } else if d, ok := guessImportGB(absBase); ok { msg.Info("Importing GB configuration") deps = d - } else if d, ok := guessImportGom(absBase); ok { - msg.Info("Importing GB configuration") - deps = d } - if len(deps) > 0 { - msg.Info("--> Attempting to detect versions from imported commit ids") - } - - var wg sync.WaitGroup - for _, i := range deps { - wg.Add(1) - go func(dep *cfg.Dependency) { - var remote string - if dep.Repository != "" { - remote = dep.Repository - } else { - remote = "https://" + dep.Name - } - ver := createGuessVersion(remote, dep.Reference) - if ver != dep.Reference { - msg.Verbose("--> Found imported reference to %s at version %s", dep.Name, ver) - dep.Reference = ver - } + if i.Reference == "" { + msg.Info("--> Found imported reference to %s", i.Name) + } else { + msg.Info("--> Found imported reference to %s at revision %s", i.Name, i.Reference) + } - msg.Debug("--> Found imported reference to %s at revision %s", dep.Name, dep.Reference) - - wg.Done() - }(i) + config.Imports = append(config.Imports, i) } - - wg.Wait() - - return deps } func guessImportGodep(dir string) ([]*cfg.Dependency, bool) { @@ -386,165 +212,3 @@ return d, true } - -func guessImportGom(dir string) ([]*cfg.Dependency, bool) { - d, err := gom.Parse(dir) - if err != nil || len(d) == 0 { - return []*cfg.Dependency{}, false - } - - return d, true -} - -// Note, this really needs a simpler name. -var createGitParseVersion = regexp.MustCompile(`(?m-s)(?:tags)/(\S+)$`) - -var guessVersionCache = newVersionCache() - -func createGuessVersion(remote, id string) string { - err := cache.Setup() - if err != nil { - msg.Debug("Problem setting up cache: %s", err) - } - l, err := cache.Location() - if err != nil { - msg.Debug("Problem detecting cache location: %s", err) - } - key, err := cache.Key(remote) - if err != nil { - msg.Debug("Problem generating cache key for %s: %s", remote, err) - } - - local := filepath.Join(l, "src", key) - repo, err := vcs.NewRepo(remote, local) - if err != nil { - msg.Debug("Problem getting repo instance: %s", err) - } - - // 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 repo.Vcs() == vcs.Git { - out, err := exec.Command("git", "ls-remote", remote).CombinedOutput() - if err == nil { - guessVersionCache.touchChecked(remote) - cc = false - lines := strings.Split(string(out), "\n") - res := "" - - // TODO(mattfarina): Detect if the found version is semver and use - // that one instead of the first found. - for _, i := range lines { - ti := strings.TrimSpace(i) - if found := createGitParseVersion.FindString(ti); found != "" { - tg := strings.TrimPrefix(strings.TrimSuffix(found, "^{}"), "tags/") - if strings.HasPrefix(ti, id) { - res = tg - } - - ver, err := semver.NewVersion(tg) - if err == nil { - if guessVersionCache.get(remote) == "" { - guessVersionCache.put(remote, tg) - } else { - ver2, err := semver.NewVersion(guessVersionCache.get(remote)) - if err == nil { - if ver.GreaterThan(ver2) { - guessVersionCache.put(remote, tg) - } - } - } - } - } - } - - if res != "" { - return res - } - } - } - - if cc { - cache.Lock(key) - 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.TagsFromCommit(id) - if err != nil { - msg.Debug("Problem getting tags for commit: %s", err) - } - cache.Unlock(key) - if len(tgs) > 0 { - return tgs[0] - } - } - - return id -} - -func findCurrentBranch(repo vcs.Repo) string { - msg.Debug("Attempting to find current branch for %s", repo.Remote()) - // Svn and Bzr don't have default branches. - if repo.Vcs() == vcs.Svn || repo.Vcs() == vcs.Bzr { - return "" - } - - if repo.Vcs() == vcs.Git || repo.Vcs() == vcs.Hg { - ver, err := repo.Current() - if err != nil { - msg.Debug("Unable to find current branch for %s, error: %s", repo.Remote(), err) - return "" - } - return ver - } - - return "" -} - -type versionCache struct { - sync.RWMutex - cache map[string]string - cd map[string]bool -} - -func newVersionCache() *versionCache { - return &versionCache{ - cache: make(map[string]string), - cd: make(map[string]bool), - } -} - -func (v *versionCache) put(name, version string) { - v.Lock() - defer v.Unlock() - v.cache[name] = version - v.cd[name] = true -} - -func (v *versionCache) checked(name string) bool { - v.RLock() - defer v.RUnlock() - return v.cd[name] -} - -func (v *versionCache) touchChecked(name string) { - v.Lock() - defer v.Unlock() - v.cd[name] = true -} - -func (v *versionCache) get(name string) string { - v.RLock() - defer v.RUnlock() - return v.cache[name] -}
diff --git a/cache/memory.go b/cache/memory.go new file mode 100644 index 0000000..ff2a0b8 --- /dev/null +++ b/cache/memory.go
@@ -0,0 +1,137 @@ +package cache + +import ( + "sync" + + "github.com/Masterminds/glide/msg" + "github.com/Masterminds/semver" +) + +// Provide an in memory cache of imported project information. + +var defaultMemCache = newMemCache() + +// MemPut put a version into the in memory cache for a name. +// This will silently ignore non-semver and make sure the latest +// is stored. +func MemPut(name, version string) { + defaultMemCache.put(name, version) +} + +// MemTouched returns true if the cache was touched for a name. +func MemTouched(name string) bool { + return defaultMemCache.touched(name) +} + +// MemTouch notes if a name has been looked at. +func MemTouch(name string) { + defaultMemCache.touch(name) +} + +// MemLatest returns the latest, that is most recent, semver release. This +// may be a blank string if no put value +func MemLatest(name string) string { + return defaultMemCache.getLatest(name) +} + +// MemSetCurrent is used to set the current version in use. +func MemSetCurrent(name, version string) { + defaultMemCache.setCurrent(name, version) +} + +// MemCurrent is used to get the current version in use. +func MemCurrent(name string) string { + return defaultMemCache.current(name) +} + +// An in memory cache. +type memCache struct { + sync.RWMutex + latest map[string]string + t map[string]bool + versions map[string][]string + c map[string]string +} + +func newMemCache() *memCache { + return &memCache{ + latest: make(map[string]string), + t: make(map[string]bool), + versions: make(map[string][]string), + c: make(map[string]string), + } +} + +func (m *memCache) setCurrent(name, version string) { + m.Lock() + defer m.Unlock() + + if m.c[name] == "" { + m.c[name] = version + } else { + // If we already have a version try to see if the new or old one is + // semver and use that one. + _, err := semver.NewVersion(m.c[name]) + if err != nil { + _, err2 := semver.NewVersion(version) + if err2 == nil { + m.c[name] = version + } + } + } +} + +func (m *memCache) current(name string) string { + m.RLock() + defer m.RUnlock() + return m.c[name] +} + +func (m *memCache) put(name, version string) { + m.Lock() + defer m.Unlock() + m.t[name] = true + sv, err := semver.NewVersion(version) + if err != nil { + msg.Debug("Ignoring %s version %s: %s", name, version, err) + return + } + + latest, found := m.latest[name] + if found { + lv, err := semver.NewVersion(latest) + if err != nil { + if sv.GreaterThan(lv) { + m.latest[name] = version + } + } + } + + found = false + for _, v := range m.versions[name] { + if v == version { + found = true + } + } + if !found { + m.versions[name] = append(m.versions[name], version) + } +} + +func (m *memCache) touch(name string) { + m.Lock() + defer m.Unlock() + m.t[name] = true +} + +func (m *memCache) touched(name string) bool { + m.RLock() + defer m.RUnlock() + return m.t[name] +} + +func (m *memCache) getLatest(name string) string { + m.RLock() + defer m.RUnlock() + return m.latest[name] +}
diff --git a/glide.go b/glide.go index 2e01e1c..5bd2894 100644 --- a/glide.go +++ b/glide.go
@@ -154,13 +154,20 @@ Name: "non-interactive", Usage: "Disable interactive prompts.", }, - cli.BoolFlag{ - Name: "skip-version-suggestions", - Usage: "When imported commit ids are found that don't map to versions skip suggesting a version.", - }, }, Action: func(c *cli.Context) { - action.Create(".", c.Bool("skip-import"), c.Bool("non-interactive"), c.Bool("skip-version-suggestions")) + action.Create(".", c.Bool("skip-import"), c.Bool("non-interactive")) + }, + }, + { + Name: "config-wizard", + ShortName: "cw", + Usage: "Wizard that makes optional suggestions to improve config in a glide.yaml file.", + Description: `Glide will analyze a projects glide.yaml file and the imported + projects to find ways the glide.yaml file can potentially be improved. It + will then interactively make suggestions that you can skip or accept.`, + Action: func(c *cli.Context) { + action.ConfigWizard(".") }, }, {
diff --git a/msg/msg.go b/msg/msg.go index bb8b919..34213fd 100644 --- a/msg/msg.go +++ b/msg/msg.go
@@ -287,3 +287,27 @@ func PromptUntil(opts []string) (string, error) { return Default.PromptUntil(opts) } + +// PromptUntilYorN provides a prompt until the user chooses yes or no. This is +// not case sensitive and they can input other options such as Y or N. +// In the response Yes is bool true and No is bool false. +func (m *Messenger) PromptUntilYorN() bool { + res, err := m.PromptUntil([]string{"y", "yes", "n", "no"}) + if err != nil { + m.Die("Error processing response: %s", err) + } + + if res == "y" || res == "yes" { + return true + } + + return false +} + +// PromptUntilYorN provides a prompt until the user chooses yes or no. This is +// not case sensitive and they can input other options such as Y or N. +// In the response Yes is bool true and No is bool false. +// Uses the default setup. +func PromptUntilYorN() bool { + return Default.PromptUntilYorN() +}