]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: report changes and resolved versions in 'go get'
authorJay Conrod <jayconrod@google.com>
Wed, 18 Nov 2020 22:07:30 +0000 (17:07 -0500)
committerJay Conrod <jayconrod@google.com>
Fri, 20 Nov 2020 16:42:46 +0000 (16:42 +0000)
Fixes #33284

Change-Id: I33daa5eb518985bc7308f29655e04c57e244b479
Reviewed-on: https://go-review.googlesource.com/c/go/+/269018
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Jay Conrod <jayconrod@google.com>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
src/cmd/go/internal/modget/get.go
src/cmd/go/testdata/script/mod_get_changes.txt [new file with mode: 0644]

index f2fafa85cb0da9308f8247cb62f1ef6c4a4ae768..2413fd20bcf96c535f27efec28610cc50ac021af 100644 (file)
@@ -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
-               }
-
-               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
-                               }
+// 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)
 
-                               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
+       // 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}
                }
+       }
 
-               for _, m := range q.resolved {
-                       before := r.initialSelected(m.Path)
-                       if before == m.Version {
-                               continue // m was resolved, but not changed
-                       }
+       // 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}
+               }
+       }
 
-                       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)
-                       }
+       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(#33284): Also print relevant upgrades.
+       // 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 (file)
index 0000000..3287b2a
--- /dev/null
@@ -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