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()
+}