From 5e58ae43bedeae5964e668755049088938320740 Mon Sep 17 00:00:00 2001 From: Jay Conrod Date: Wed, 18 Nov 2020 17:07:30 -0500 Subject: [PATCH] cmd/go: report changes and resolved versions in 'go get' Fixes #33284 Change-Id: I33daa5eb518985bc7308f29655e04c57e244b479 Reviewed-on: https://go-review.googlesource.com/c/go/+/269018 Run-TryBot: Jay Conrod TryBot-Result: Go Bot Trust: Jay Conrod Reviewed-by: Bryan C. Mills --- src/cmd/go/internal/modget/get.go | 131 +++++++++++------- .../go/testdata/script/mod_get_changes.txt | 70 ++++++++++ 2 files changed, 148 insertions(+), 53 deletions(-) create mode 100644 src/cmd/go/testdata/script/mod_get_changes.txt diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go index f2fafa85cb..2413fd20bc 100644 --- a/src/cmd/go/internal/modget/get.go +++ b/src/cmd/go/internal/modget/get.go @@ -45,7 +45,9 @@ import ( "cmd/go/internal/search" "cmd/go/internal/work" + "golang.org/x/mod/modfile" "golang.org/x/mod/module" + "golang.org/x/mod/semver" ) var CmdGet = &base.Command{ @@ -462,10 +464,19 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) { // what's changing and gives more examples. } + if !modload.HasModRoot() { + return + } + // Everything succeeded. Update go.mod. + oldReqs := reqsFromGoMod(modload.ModFile()) + modload.AllowWriteGoMod() modload.WriteGoMod() modload.DisallowWriteGoMod() + + newReqs := reqsFromGoMod(modload.ModFile()) + r.reportChanges(oldReqs, newReqs) } // parseArgs parses command-line arguments and reports errors. @@ -1563,63 +1574,69 @@ func (r *resolver) checkPackagesAndRetractions(ctx context.Context, pkgPatterns } } -// reportChanges logs resolved version changes to os.Stderr. -func (r *resolver) reportChanges(queries []*query) { - for _, q := range queries { - if q.version == "none" { - continue - } +// reportChanges logs version changes to os.Stderr. +// +// reportChanges only logs changes to modules named on the command line and to +// explicitly required modules in go.mod. Most changes to indirect requirements +// are not relevant to the user and are not logged. +// +// reportChanges should be called after WriteGoMod. +func (r *resolver) reportChanges(oldReqs, newReqs []module.Version) { + type change struct { + path, old, new string + } + changes := make(map[string]change) - if q.pattern == "all" { - // To reduce noise for "all", describe module version changes rather than - // package versions. - seen := make(map[module.Version]bool) - for _, m := range q.resolved { - if seen[m] { - continue - } - seen[m] = true - - before := r.initialSelected(m.Path) - if before == m.Version { - continue // m was resolved, but not changed - } - - was := "" - if before != "" { - was = fmt.Sprintf(" (was %s)", before) - } - fmt.Fprintf(os.Stderr, "go: %v added %s %s%s\n", q, m.Path, m.Version, was) - } - continue - } - - for _, m := range q.resolved { - before := r.initialSelected(m.Path) - if before == m.Version { - continue // m was resolved, but not changed - } - - was := "" - if before != "" { - was = fmt.Sprintf(" (was %s)", before) - } - switch { - case q.isWildcard(): - if q.matchesPath(m.Path) { - fmt.Fprintf(os.Stderr, "go: matched %v as %s %s%s\n", q, m.Path, m.Version, was) - } else { - fmt.Fprintf(os.Stderr, "go: matched %v in %s %s%s\n", q, m.Path, m.Version, was) - } - case q.matchesPackages: - fmt.Fprintf(os.Stderr, "go: found %v in %s %s%s\n", q, m.Path, m.Version, was) - default: - fmt.Fprintf(os.Stderr, "go: found %v in %s %s%s\n", q, m.Path, m.Version, was) - } + // Collect changes in modules matched by command line arguments. + for path, reason := range r.resolvedVersion { + old := r.initialVersion[path] + new := reason.version + if old != new && (old != "" || new != "none") { + changes[path] = change{path, old, new} } } - // TODO(#33284): Also print relevant upgrades. + // Collect changes to explicit requirements in go.mod. + for _, req := range oldReqs { + path := req.Path + old := req.Version + new := r.buildListVersion[path] + if old != new { + changes[path] = change{path, old, new} + } + } + for _, req := range newReqs { + path := req.Path + old := r.initialVersion[path] + new := req.Version + if old != new { + changes[path] = change{path, old, new} + } + } + + sortedChanges := make([]change, 0, len(changes)) + for _, c := range changes { + sortedChanges = append(sortedChanges, c) + } + sort.Slice(sortedChanges, func(i, j int) bool { + return sortedChanges[i].path < sortedChanges[j].path + }) + for _, c := range sortedChanges { + if c.old == "" { + fmt.Fprintf(os.Stderr, "go get: added %s %s\n", c.path, c.new) + } else if c.new == "none" || c.new == "" { + fmt.Fprintf(os.Stderr, "go get: removed %s %s\n", c.path, c.old) + } else if semver.Compare(c.new, c.old) > 0 { + fmt.Fprintf(os.Stderr, "go get: upgraded %s %s => %s\n", c.path, c.old, c.new) + } else { + fmt.Fprintf(os.Stderr, "go get: downgraded %s %s => %s\n", c.path, c.old, c.new) + } + } + + // TODO(golang.org/issue/33284): attribute changes to command line arguments. + // For modules matched by command line arguments, this probably isn't + // necessary, but it would be useful for unmatched direct dependencies of + // the main module. } // resolve records that module m must be at its indicated version (which may be @@ -1700,6 +1717,14 @@ func (r *resolver) updateBuildList(ctx context.Context, additions []module.Versi return true } +func reqsFromGoMod(f *modfile.File) []module.Version { + reqs := make([]module.Version, len(f.Require)) + for i, r := range f.Require { + reqs[i] = r.Mod + } + return reqs +} + // isNoSuchModuleVersion reports whether err indicates that the requested module // does not exist at the requested version, either because the module does not // exist at all or because it does not include that specific version. diff --git a/src/cmd/go/testdata/script/mod_get_changes.txt b/src/cmd/go/testdata/script/mod_get_changes.txt new file mode 100644 index 0000000000..3287b2a609 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_get_changes.txt @@ -0,0 +1,70 @@ +# When adding a requirement, 'go get' prints a message for the requirement +# and for changed explicit dependencies. 'go get' does not print messages +# for changed indirect dependencies. +go list -m all +! stdout golang.org/x/text +go get -d rsc.io/quote@v1.5.2 +stderr '^go get: added rsc.io/quote v1.5.2$' +stderr '^go get: upgraded rsc.io/sampler v1.0.0 => v1.3.0$' +! stderr '^go get.*golang.org/x/text' +go list -m all +stdout golang.org/x/text +cmp go.mod go.mod.upgrade + +# When removing a requirement, 'go get' prints a message for the requiremnent +# and for changed explicit dependencies. 'go get' does not print messages +# for changed indirect dependencies. +go get -d rsc.io/sampler@none +stderr '^go get: downgraded rsc.io/quote v1.5.2 => v1.3.0$' +stderr '^go get: removed rsc.io/sampler v1.3.0$' +! stderr '^go get.*golang.org/x/text' +cmp go.mod go.mod.downgrade + +# When removing or downgrading a requirement, 'go get' also prints a message +# for explicit dependencies removed as a consequence. +cp go.mod.usequote go.mod +go get -d rsc.io/quote@v1.5.1 +stderr '^go get: downgraded rsc.io/quote v1.5.2 => v1.5.1$' +stderr '^go get: removed usequote v0.0.0$' + +-- go.mod -- +module m + +go 1.16 + +require rsc.io/sampler v1.0.0 +-- go.sum -- +rsc.io/sampler v1.0.0 h1:SRJnjyQ07sAtq6G4RcfJEmz8JxqLyj3PoGXG2VhbDWo= +rsc.io/sampler v1.0.0/go.mod h1:cqxpM3ZVz9VtirqxZPmrWzkQ+UkiNiGtkrN+B+i8kx8= +-- go.mod.upgrade -- +module m + +go 1.16 + +require ( + rsc.io/quote v1.5.2 // indirect + rsc.io/sampler v1.3.0 +) +-- go.mod.downgrade -- +module m + +go 1.16 + +require ( + golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect + rsc.io/quote v1.3.0 // indirect +) +-- go.mod.usequote -- +module m + +go 1.16 + +require usequote v0.0.0 + +replace usequote => ./usequote +-- usequote/go.mod -- +module usequote + +go 1.16 + +require rsc.io/quote v1.5.2