]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: detect inconsistent 'go get' version requests
authorRuss Cox <rsc@golang.org>
Wed, 18 Jul 2018 21:15:32 +0000 (17:15 -0400)
committerRuss Cox <rsc@golang.org>
Fri, 20 Jul 2018 15:30:35 +0000 (15:30 +0000)
If x v1.0.0 requires y v1.2.0, then
go get x@v1.0.0 y@v1.0.0 needs to fail gracefully.

Fixes #25917.

Change-Id: I9b426af23a30310fcb0c3545a8d97feb58b8ddbe
Reviewed-on: https://go-review.googlesource.com/124800
Reviewed-by: Bryan C. Mills <bcmills@google.com>
src/cmd/go/internal/modget/get.go
src/cmd/go/internal/modload/init.go
src/cmd/go/testdata/script/mod_get_downgrade.txt

index 1fd697048b1d8e08628557dd11923cf8c1540001..361d4808f3ab5118fa3ac02efb5ddec84335a6ff 100644 (file)
@@ -206,9 +206,15 @@ func runGet(cmd *base.Command, args []string) {
 
        modload.LoadBuildList()
 
+       // Do not allow any updating of go.mod until we've applied
+       // all the requested changes and checked that the result matches
+       // what was requested.
+       modload.DisallowWriteGoMod()
+
        // A task holds the state for processing a single get argument (path@vers).
        type task struct {
-               arg             string           // original argument
+               arg             string // original argument
+               index           int
                path            string           // package path part of arg
                forceModulePath bool             // path must be interpreted as a module path
                vers            string           // version part of arg
@@ -429,6 +435,7 @@ func runGet(cmd *base.Command, args []string) {
                        base.Fatalf("go get: %v", err)
                }
                required = upgraded[1:] // slice off upgradeTarget
+               base.ExitIfErrors()
        }
 
        // Put together the final build list as described above (1) (2) (3).
@@ -441,8 +448,9 @@ func runGet(cmd *base.Command, args []string) {
        list = append(list, required...)
        modload.SetBuildList(list)
        modload.ReloadBuildList() // note: does not update go.mod
+       base.ExitIfErrors()
 
-       // Apply any needed downgrades.
+       // Scan for and apply any needed downgrades.
        var down []module.Version
        for _, m := range modload.BuildList() {
                t := byPath[m.Path]
@@ -458,8 +466,64 @@ func runGet(cmd *base.Command, args []string) {
                modload.SetBuildList(list)
                modload.ReloadBuildList() // note: does not update go.mod
        }
+       base.ExitIfErrors()
+
+       // Scan for any upgrades lost by the downgrades.
+       lost := make(map[string]string)
+       for _, m := range modload.BuildList() {
+               t := byPath[m.Path]
+               if t != nil && semver.Compare(m.Version, t.m.Version) != 0 {
+                       lost[m.Path] = m.Version
+               }
+       }
+       if len(lost) > 0 {
+               desc := func(m module.Version) string {
+                       s := m.Path + "@" + m.Version
+                       t := byPath[m.Path]
+                       if t != nil && t.arg != s {
+                               s += " from " + t.arg
+                       }
+                       return s
+               }
+               downByPath := make(map[string]module.Version)
+               for _, d := range down {
+                       downByPath[d.Path] = d
+               }
+               var buf strings.Builder
+               fmt.Fprintf(&buf, "go get: inconsistent versions:")
+               for _, t := range tasks {
+                       if lost[t.m.Path] == "" {
+                               continue
+                       }
+                       // We lost t because its build list requires a newer version of something in down.
+                       // Figure out exactly what.
+                       // Repeatedly constructing the build list is inefficient
+                       // if there are MANY command-line arguments,
+                       // but at least all the necessary requirement lists are cached at this point.
+                       list, err := mvs.BuildList(t.m, reqs)
+                       if err != nil {
+                               base.Fatalf("go get: %v", err)
+                       }
+
+                       fmt.Fprintf(&buf, "\n\t%s", desc(t.m))
+                       sep := " requires"
+                       for _, m := range list {
+                               if down, ok := downByPath[m.Path]; ok && semver.Compare(down.Version, m.Version) < 0 {
+                                       fmt.Fprintf(&buf, "%s %s@%s (not %s)", sep, m.Path, m.Version, desc(down))
+                                       sep = ","
+                               }
+                       }
+                       if sep != "," {
+                               // We have no idea why this happened.
+                               // At least report the problem.
+                               fmt.Fprintf(&buf, " ended up at %v unexpectedly (please report at golang.org/issue/new)", lost[t.m.Path])
+                       }
+               }
+               base.Fatalf("%v", buf.String())
+       }
 
        // Everything succeeded. Update go.mod.
+       modload.AllowWriteGoMod()
        modload.WriteGoMod()
 
        // If -m was specified, we're done after the module work. No download, no build.
index dfab6578a968b565b7a9dd4c2c5e396615e2d4a1..759b5a768c490de4321a23eeae9955193e5d61b0 100644 (file)
@@ -465,8 +465,27 @@ func findImportComment(file string) string {
        return path
 }
 
+var allowWriteGoMod = true
+
+// DisallowWriteGoMod causes future calls to WriteGoMod to do nothing at all.
+func DisallowWriteGoMod() {
+       allowWriteGoMod = false
+}
+
+// AllowWriteGoMod undoes the effect of DisallowWriteGoMod:
+// future calls to WriteGoMod will update go.mod if needed.
+// Note that any past calls have been discarded, so typically
+// a call to AlowWriteGoMod should be followed by a call to WriteGoMod.
+func AllowWriteGoMod() {
+       allowWriteGoMod = true
+}
+
 // WriteGoMod writes the current build list back to go.mod.
 func WriteGoMod() {
+       if !allowWriteGoMod {
+               return
+       }
+
        modfetch.WriteGoSum()
 
        if loaded != nil {
index e687403bd97f0370c2edc6e848473a6bb27563fa..5a00d6d51df0da75471ce142da863f7e535477a8 100644 (file)
@@ -11,6 +11,17 @@ go get rsc.io/sampler@none
 go list -m all
 stdout 'rsc.io/quote v1.3.0'
 
+# downgrade should report inconsistencies and not change go.mod
+go get rsc.io/quote@v1.5.1
+go list -m all
+stdout 'rsc.io/quote v1.5.1'
+stdout 'rsc.io/sampler v1.3.0'
+! go get rsc.io/sampler@v1.0.0 rsc.io/quote@v1.5.2 golang.org/x/text@none
+stderr 'go get: inconsistent versions:\n\trsc.io/quote@v1.5.2 requires golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c \(not golang.org/x/text@none\), rsc.io/sampler@v1.3.0 \(not rsc.io/sampler@v1.0.0\)'
+go list -m all
+stdout 'rsc.io/quote v1.5.1'
+stdout 'rsc.io/sampler v1.3.0'
+
 -- go.mod --
 module x
 require rsc.io/quote v1.5.1