]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go/internal/modget: support the suffix '@patch' in 'go get'
authorBryan C. Mills <bcmills@google.com>
Fri, 15 Mar 2019 18:58:40 +0000 (14:58 -0400)
committerBryan C. Mills <bcmills@google.com>
Mon, 1 Apr 2019 16:37:07 +0000 (16:37 +0000)
As of this change, an explicit '@patch' suffix is to '-u=patch' as
'@latest' is to '-u'.

RELNOTE='go get' in module mode now supports the version suffix '@patch'.

Fixes #26812

Change-Id: Ib5eee40de640440f7470d37a574b311ef8a67f67
Reviewed-on: https://go-review.googlesource.com/c/go/+/167747
Run-TryBot: Bryan C. Mills <bcmills@google.com>
Reviewed-by: Jay Conrod <jayconrod@google.com>
14 files changed:
src/cmd/go/alldocs.go
src/cmd/go/internal/modget/get.go
src/cmd/go/internal/modload/load.go
src/cmd/go/testdata/mod/patch.example.com_depofdirectpatch_v1.0.0.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/patch.example.com_depofdirectpatch_v1.0.1.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/patch.example.com_direct_v1.0.0.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/patch.example.com_direct_v1.0.1.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/patch.example.com_direct_v1.1.0.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/patch.example.com_indirect_v1.0.0.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/patch.example.com_indirect_v1.0.1.txt [new file with mode: 0644]
src/cmd/go/testdata/mod/patch.example.com_indirect_v1.1.0.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_upgrade_patch.txt [deleted file]
src/cmd/go/testdata/script/mod_upgrade_patch_mod.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_upgrade_patch_pkg.txt [new file with mode: 0644]

index f42635f6a87f1ee4f298abb4855e23c22101eb52..f02df514b7a82c953fa0ad5de9f6d669c9ac5a3b 100644 (file)
 // For modules stored in source control repositories, the version suffix can
 // also be a commit hash, branch identifier, or other syntax known to the
 // source control system, as in 'go get golang.org/x/text@master'.
-// The version suffix @latest explicitly requests the default behavior
-// described above.
 //
 // If a module under consideration is already a dependency of the current
 // development module, then get will update the required version.
 // dependency should be removed entirely, downgrading or removing modules
 // depending on it as needed.
 //
+// The version suffix @latest explicitly requests the latest minor release of the
+// given path.
+//
+// The suffix @patch requests the latest patch release: if the path is already in
+// the build list, the selected version will have the same minor version.
+// If the path is not already in the build list, @patch is equivalent to @latest.
+//
 // Although get defaults to using the latest version of the module containing
 // a named package, it does not use the latest version of that module's
 // dependencies. Instead it prefers to use the specific dependency versions
 // patch releases when available. Continuing the previous example,
 // 'go get -u A' will use the latest A with B v1.3.1 (not B v1.2.3).
 //
-// The -u=patch flag (not -u patch) instructs get to update dependencies
-// to use newer patch releases when available. Continuing the previous example,
-// 'go get -u=patch A' will use the latest A with B v1.2.4 (not B v1.2.3).
+// The -u=patch flag (not -u patch) also instructs get to update dependencies,
+// but changes the default to select patch releases.
+// Continuing the previous example,
+// 'go get -u=patch A@latest' will use the latest A with B v1.2.4 (not B v1.2.3),
+// while 'go get -u=patch A' will use a patch release of A instead.
 //
 // In general, adding a new dependency may require upgrading
 // existing dependencies to keep a working build, and 'go get' does
index 17a0ed45e21b4ed780d763bf03a639e0b0674bd6..40bbd50746319ba0894b3f96515a5b968cde113e 100644 (file)
@@ -21,7 +21,6 @@ import (
        "cmd/go/internal/work"
        "fmt"
        "os"
-       pathpkg "path"
        "path/filepath"
        "strings"
 )
@@ -49,8 +48,6 @@ suffix to the package argument, as in 'go get golang.org/x/text@v0.3.0'.
 For modules stored in source control repositories, the version suffix can
 also be a commit hash, branch identifier, or other syntax known to the
 source control system, as in 'go get golang.org/x/text@master'.
-The version suffix @latest explicitly requests the default behavior
-described above.
 
 If a module under consideration is already a dependency of the current
 development module, then get will update the required version.
@@ -59,6 +56,13 @@ downgrades the dependency. The version suffix @none indicates that the
 dependency should be removed entirely, downgrading or removing modules
 depending on it as needed.
 
+The version suffix @latest explicitly requests the latest minor release of the
+given path.
+
+The suffix @patch requests the latest patch release: if the path is already in
+the build list, the selected version will have the same minor version.
+If the path is not already in the build list, @patch is equivalent to @latest.
+
 Although get defaults to using the latest version of the module containing
 a named package, it does not use the latest version of that module's
 dependencies. Instead it prefers to use the specific dependency versions
@@ -72,9 +76,11 @@ The -u flag instructs get to update dependencies to use newer minor or
 patch releases when available. Continuing the previous example,
 'go get -u A' will use the latest A with B v1.3.1 (not B v1.2.3).
 
-The -u=patch flag (not -u patch) instructs get to update dependencies
-to use newer patch releases when available. Continuing the previous example,
-'go get -u=patch A' will use the latest A with B v1.2.4 (not B v1.2.3).
+The -u=patch flag (not -u patch) also instructs get to update dependencies,
+but changes the default to select patch releases.
+Continuing the previous example,
+'go get -u=patch A@latest' will use the latest A with B v1.2.4 (not B v1.2.3),
+while 'go get -u=patch A' will use a patch release of A instead.
 
 In general, adding a new dependency may require upgrading
 existing dependencies to keep a working build, and 'go get' does
@@ -165,6 +171,9 @@ func (v *upgradeFlag) Set(s string) error {
        if s == "false" {
                s = ""
        }
+       if s == "true" {
+               s = "latest"
+       }
        *v = upgradeFlag(s)
        return nil
 }
@@ -180,12 +189,12 @@ func init() {
 
 // A task holds the state for processing a single get argument (path@vers).
 type task struct {
-       arg             string // original argument
-       index           int
+       arg             string           // original argument
        path            string           // package path part of arg
        forceModulePath bool             // path must be interpreted as a module path
        vers            string           // version part of arg
        m               module.Version   // module version indicated by argument
+       prevM           module.Version   // module version from initial build list
        req             []module.Version // m's requirement list (not upgraded)
 }
 
@@ -196,7 +205,7 @@ func runGet(cmd *base.Command, args []string) {
        }
 
        switch getU {
-       case "", "patch", "true":
+       case "", "latest", "patch":
                // ok
        default:
                base.Fatalf("go get: unknown upgrade flag -u=%s", getU)
@@ -230,6 +239,7 @@ func runGet(cmd *base.Command, args []string) {
        // and a list of install targets (for the "go install" at the end).
        var tasks []*task
        var install []string
+       var needModule []*task
        for _, arg := range search.CleanPatterns(args) {
                // Argument is module query path@vers, or else path with implicit @latest.
                path := arg
@@ -245,6 +255,12 @@ func runGet(cmd *base.Command, args []string) {
                        install = append(install, path)
                }
 
+               // If the user runs 'go get -u=patch some/module', update some/module to a
+               // patch release, not a minor version.
+               if vers == "" && getU != "" {
+                       vers = string(getU)
+               }
+
                // Deciding which module to upgrade/downgrade for a particular argument is difficult.
                // Patterns only make it more difficult.
                // We impose restrictions to avoid needing to interlace pattern expansion,
@@ -271,25 +287,43 @@ func runGet(cmd *base.Command, args []string) {
                //      - Import paths without patterns are left as is, for resolution by getQuery (eventually modload.Import).
                //
                if search.IsRelativePath(path) {
-                       // Check that this relative pattern only matches directories in the current module,
-                       // and then record the current module as the target.
-                       dir := path
-                       if i := strings.Index(path, "..."); i >= 0 {
-                               dir, _ = pathpkg.Split(path[:i])
-                       }
-                       abs, err := filepath.Abs(dir)
-                       if err != nil {
-                               base.Errorf("go get %s: %v", arg, err)
-                               continue
+                       t := &task{arg: arg, path: modload.Target.Path, vers: "", prevM: modload.Target, forceModulePath: true}
+
+                       // If the path is relative, always upgrade the entire main module.
+                       // (TODO(golang.org/issue/26902): maybe we should upgrade the modules
+                       // containing the dependencies of the requested packages instead.)
+                       //
+                       // If the path is explicit, at least check that it is a package in the main module.
+                       if len(args) > 0 {
+                               if *getM {
+                                       base.Errorf("go get %s: -m requires a module path, but a relative path must be a package in the main module", arg)
+                                       continue
+                               }
+
+                               pkgPath := modload.DirImportPath(filepath.FromSlash(path))
+                               if pkgs := modload.TargetPackages(pkgPath); len(pkgs) == 0 {
+                                       if strings.Contains(path, "...") {
+                                               fmt.Fprintf(os.Stderr, "go get %s: warning: pattern patched no packages", arg)
+                                       } else {
+                                               abs, err := filepath.Abs(path)
+                                               if err != nil {
+                                                       abs = path
+                                               }
+                                               base.Errorf("go get %s: path %s is not in module rooted at %s", arg, abs, modload.ModRoot())
+                                       }
+                                       continue
+                               }
                        }
-                       if !str.HasFilePathPrefix(abs, modload.ModRoot()) {
-                               base.Errorf("go get %s: directory %s is outside module root %s", arg, abs, modload.ModRoot())
-                               continue
+
+                       switch vers {
+                       case "", "latest", "patch":
+                               tasks = append(tasks, t)
+                       default:
+                               base.Errorf("go get %s: can't request explicit version of path in main module", arg)
                        }
-                       // TODO: Check if abs is inside a nested module.
-                       tasks = append(tasks, &task{arg: arg, path: modload.Target.Path, vers: ""})
                        continue
                }
+
                if path == "all" {
                        // TODO: If *getM, should this be the module pattern "all"?
 
@@ -306,30 +340,19 @@ func runGet(cmd *base.Command, args []string) {
                                m := modload.PackageModule(pkg)
                                if m.Path != "" && !seen[m] {
                                        seen[m] = true
-                                       tasks = append(tasks, &task{arg: arg, path: m.Path, vers: "latest", forceModulePath: true})
+                                       tasks = append(tasks, &task{arg: arg, path: m.Path, vers: vers, prevM: m, forceModulePath: true})
                                }
                        }
                        continue
                }
-               if search.IsMetaPackage(path) {
-                       // Already handled "all", so this must be "std" or "cmd",
-                       // which are entirely in the standard library.
-                       if path != arg {
-                               base.Errorf("go get %s: cannot use pattern %q with explicit version", arg, arg)
-                       }
-                       if *getM {
-                               base.Errorf("go get %s: cannot use pattern %q with -m", arg, arg)
-                               continue
-                       }
-                       continue
-               }
+
                if strings.Contains(path, "...") {
                        // Apply to modules in build list matched by pattern (golang.org/x/...), if any.
                        match := search.MatchPattern(path)
                        matched := false
                        for _, m := range modload.BuildList() {
                                if match(m.Path) || str.HasPathPrefix(path, m.Path) {
-                                       tasks = append(tasks, &task{arg: arg, path: m.Path, vers: vers, forceModulePath: true})
+                                       tasks = append(tasks, &task{arg: arg, path: m.Path, vers: vers, prevM: m, forceModulePath: true})
                                        matched = true
                                }
                        }
@@ -345,10 +368,66 @@ func runGet(cmd *base.Command, args []string) {
                                continue
                        }
                }
-               tasks = append(tasks, &task{arg: arg, path: path, vers: vers})
+               t := &task{arg: arg, path: path, vers: vers}
+               if vers == "patch" {
+                       if *getM {
+                               for _, m := range modload.BuildList() {
+                                       if m.Path == path {
+                                               t.prevM = m
+                                               break
+                                       }
+                               }
+                               tasks = append(tasks, t)
+                       } else {
+                               // We need to know the module containing t so that we can restrict the patch to its minor version.
+                               needModule = append(needModule, t)
+                       }
+               } else {
+                       // The requested version of path doesn't depend on the existing version,
+                       // so don't bother resolving it.
+                       tasks = append(tasks, t)
+               }
        }
        base.ExitIfErrors()
 
+       if len(needModule) > 0 {
+               paths := make([]string, len(needModule))
+               for i, t := range needModule {
+                       paths[i] = t.path
+               }
+               matches := modload.ImportPaths(paths)
+               if len(matches) != len(paths) {
+                       base.Fatalf("go get: internal error: ImportPaths resolved %d paths to %d matches", len(paths), len(matches))
+               }
+
+               for i, match := range matches {
+                       t := needModule[i]
+                       if len(match.Pkgs) == 0 {
+                               // Let modload.Query resolve the path during task processing.
+                               tasks = append(tasks, t)
+                               continue
+                       }
+
+                       allStd := true
+                       for _, pkg := range match.Pkgs {
+                               m := modload.PackageModule(pkg)
+                               if m.Path == "" {
+                                       // pkg is in the standard library.
+                               } else {
+                                       allStd = false
+                                       tasks = append(tasks, &task{arg: t.arg, path: pkg, vers: t.vers, prevM: m})
+                               }
+                       }
+                       if allStd {
+                               if *getM {
+                                       base.Errorf("go get %s: cannot use pattern %q with -m", t.arg, t.arg)
+                               } else if t.path != t.arg {
+                                       base.Errorf("go get %s: cannot use pattern %q with explicit version", t.arg, t.arg)
+                               }
+                       }
+               }
+       }
+
        // Now we've reduced the upgrade/downgrade work to a list of path@vers pairs (tasks).
        // Resolve each one in parallel.
        reqs := modload.Reqs()
@@ -363,7 +442,7 @@ func runGet(cmd *base.Command, args []string) {
                        t.m = module.Version{Path: t.path, Version: "none"}
                        return
                }
-               m, err := getQuery(t.path, t.vers, t.forceModulePath)
+               m, err := getQuery(t.path, t.vers, t.prevM, t.forceModulePath)
                if err != nil {
                        base.Errorf("go get %v: %v", t.arg, err)
                        return
@@ -412,7 +491,6 @@ func runGet(cmd *base.Command, args []string) {
                upgraded, err := mvs.UpgradeAll(upgradeTarget, &upgrader{
                        Reqs:    modload.Reqs(),
                        targets: named,
-                       patch:   getU == "patch",
                        tasks:   byPath,
                })
                if err != nil {
@@ -554,9 +632,16 @@ func runGet(cmd *base.Command, args []string) {
 // to determine the underlying module version being requested.
 // If forceModulePath is set, getQuery must interpret path
 // as a module path.
-func getQuery(path, vers string, forceModulePath bool) (module.Version, error) {
-       if vers == "" {
+func getQuery(path, vers string, prevM module.Version, forceModulePath bool) (module.Version, error) {
+       switch vers {
+       case "":
                vers = "latest"
+       case "patch":
+               if prevM.Version == "" {
+                       vers = "latest"
+               } else {
+                       vers = semver.MajorMinor(prevM.Version)
+               }
        }
 
        // First choice is always to assume path is a module path.
@@ -625,7 +710,7 @@ func (u *upgrader) Upgrade(m module.Version) (module.Version, error) {
        // only ever returns untagged versions,
        // which is not what we want.
        query := "latest"
-       if u.patch {
+       if getU == "patch" {
                // For patch upgrade, query "v1.2".
                query = semver.MajorMinor(m.Version)
        }
index 57c2dd25a622b53b61c4e1e72a1e0910ca74a6e5..d55e0c54034b9828dd3c5372c5070a6cd3a424fa 100644 (file)
@@ -339,7 +339,7 @@ func loadAll(testAll bool) []string {
        if !testAll {
                loaded.testRoots = true
        }
-       all := TargetPackages()
+       all := TargetPackages("...")
        loaded.load(func() []string { return all })
        WriteGoMod()
 
@@ -357,10 +357,11 @@ func loadAll(testAll bool) []string {
 // Only "ignore" and malformed build tag requirements are considered false.
 var anyTags = map[string]bool{"*": true}
 
-// TargetPackages returns the list of packages in the target (top-level) module,
-// under all build tag settings.
-func TargetPackages() []string {
-       return matchPackages("...", anyTags, false, []module.Version{Target})
+// TargetPackages returns the list of packages in the target (top-level) module
+// matching pattern, which may be relative to the working directory, under all
+// build tag settings.
+func TargetPackages(pattern string) []string {
+       return matchPackages(pattern, anyTags, false, []module.Version{Target})
 }
 
 // BuildList returns the module build list,
diff --git a/src/cmd/go/testdata/mod/patch.example.com_depofdirectpatch_v1.0.0.txt b/src/cmd/go/testdata/mod/patch.example.com_depofdirectpatch_v1.0.0.txt
new file mode 100644 (file)
index 0000000..40616c6
--- /dev/null
@@ -0,0 +1,11 @@
+patch.example.com/depofdirectpatch v1.0.0
+written by hand
+
+-- .mod --
+module patch.example.com/depofdirectpatch
+-- .info --
+{"Version":"v1.0.0"}
+-- go.mod --
+module patch.example.com/depofdirectpatch
+-- depofdirectpatch.go --
+package depofdirectpatch
diff --git a/src/cmd/go/testdata/mod/patch.example.com_depofdirectpatch_v1.0.1.txt b/src/cmd/go/testdata/mod/patch.example.com_depofdirectpatch_v1.0.1.txt
new file mode 100644 (file)
index 0000000..e075028
--- /dev/null
@@ -0,0 +1,11 @@
+patch.example.com/depofdirectpatch v1.0.1
+written by hand
+
+-- .mod --
+module patch.example.com/depofdirectpatch
+-- .info --
+{"Version":"v1.0.1"}
+-- go.mod --
+module patch.example.com/depofdirectpatch
+-- depofdirectpatch.go --
+package depofdirectpatch
diff --git a/src/cmd/go/testdata/mod/patch.example.com_direct_v1.0.0.txt b/src/cmd/go/testdata/mod/patch.example.com_direct_v1.0.0.txt
new file mode 100644 (file)
index 0000000..1e775fb
--- /dev/null
@@ -0,0 +1,21 @@
+patch.example.com/direct v1.0.0
+written by hand
+
+-- .mod --
+module patch.example.com/direct
+
+require (
+       patch.example.com/indirect v1.0.0
+)
+-- .info --
+{"Version":"v1.0.0"}
+-- go.mod --
+module patch.example.com/direct
+
+require (
+       patch.example.com/indirect v1.0.0
+)
+-- direct.go --
+package direct
+
+import _ "patch.example.com/indirect"
diff --git a/src/cmd/go/testdata/mod/patch.example.com_direct_v1.0.1.txt b/src/cmd/go/testdata/mod/patch.example.com_direct_v1.0.1.txt
new file mode 100644 (file)
index 0000000..64912b7
--- /dev/null
@@ -0,0 +1,27 @@
+patch.example.com/direct v1.0.1
+written by hand
+
+-- .mod --
+module patch.example.com/direct
+
+require (
+       patch.example.com/indirect v1.0.0
+       patch.example.com/depofdirectpatch v1.0.0
+)
+-- .info --
+{"Version":"v1.0.1"}
+-- go.mod --
+module patch.example.com/direct
+
+require (
+       patch.example.com/indirect v1.0.0
+       patch.example.com/depofdirectpatch v1.0.0
+)
+-- direct.go --
+package direct
+
+import _ "patch.example.com/indirect"
+-- usedepofdirectpatch/unused.go --
+package usedepofdirectpatch
+
+import _ "patch.example.com/depofdirectpatch"
diff --git a/src/cmd/go/testdata/mod/patch.example.com_direct_v1.1.0.txt b/src/cmd/go/testdata/mod/patch.example.com_direct_v1.1.0.txt
new file mode 100644 (file)
index 0000000..406e3b9
--- /dev/null
@@ -0,0 +1,21 @@
+patch.example.com/direct v1.1.0
+written by hand
+
+-- .mod --
+module patch.example.com/direct
+
+require (
+       patch.example.com/indirect v1.0.0
+)
+-- .info --
+{"Version":"v1.1.0"}
+-- go.mod --
+module patch.example.com/direct
+
+require (
+       patch.example.com/indirect v1.0.0
+)
+-- direct.go --
+package direct
+
+import _ "patch.example.com/indirect"
diff --git a/src/cmd/go/testdata/mod/patch.example.com_indirect_v1.0.0.txt b/src/cmd/go/testdata/mod/patch.example.com_indirect_v1.0.0.txt
new file mode 100644 (file)
index 0000000..ea7f5e2
--- /dev/null
@@ -0,0 +1,11 @@
+patch.example.com/indirect v1.0.0
+written by hand
+
+-- .mod --
+module patch.example.com/indirect
+-- .info --
+{"Version":"v1.0.0"}
+-- go.mod --
+module patch.example.com/indirect
+-- direct.go --
+package indirect
diff --git a/src/cmd/go/testdata/mod/patch.example.com_indirect_v1.0.1.txt b/src/cmd/go/testdata/mod/patch.example.com_indirect_v1.0.1.txt
new file mode 100644 (file)
index 0000000..8c6cf8e
--- /dev/null
@@ -0,0 +1,11 @@
+patch.example.com/indirect v1.0.1
+written by hand
+
+-- .mod --
+module patch.example.com/indirect
+-- .info --
+{"Version":"v1.0.1"}
+-- go.mod --
+module patch.example.com/indirect
+-- direct.go --
+package indirect
diff --git a/src/cmd/go/testdata/mod/patch.example.com_indirect_v1.1.0.txt b/src/cmd/go/testdata/mod/patch.example.com_indirect_v1.1.0.txt
new file mode 100644 (file)
index 0000000..f7229d4
--- /dev/null
@@ -0,0 +1,11 @@
+patch.example.com/indirect v1.1.0
+written by hand
+
+-- .mod --
+module patch.example.com/indirect
+-- .info --
+{"Version":"v1.1.0"}
+-- go.mod --
+module patch.example.com/indirect
+-- direct.go --
+package indirect
diff --git a/src/cmd/go/testdata/script/mod_upgrade_patch.txt b/src/cmd/go/testdata/script/mod_upgrade_patch.txt
deleted file mode 100644 (file)
index 3c27cdb..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-env GO111MODULE=on
-
-go list -m all
-stdout '^rsc.io/quote v1.4.0'
-stdout '^rsc.io/sampler v1.0.0'
-
-# get -u=patch rsc.io/quote should take latest quote & patch update its deps
-go get -m -u=patch rsc.io/quote
-go list -m all
-stdout '^rsc.io/quote v1.5.2'
-stdout '^rsc.io/sampler v1.3.1'
-stdout '^golang.org/x/text v0.0.0-'
-
-# get -u=patch quote@v1.2.0 should take that version of quote & patch update its deps
-go get -m -u=patch rsc.io/quote@v1.2.0
-go list -m all
-stdout '^rsc.io/quote v1.2.0'
-stdout '^rsc.io/sampler v1.3.1'
-stdout '^golang.org/x/text v0.0.0-'
-
-# get -u=patch with no args applies to all deps
-go get -m -u=patch
-go list -m all
-stdout '^rsc.io/quote v1.2.1'
-
--- go.mod --
-module x
-require rsc.io/quote v1.4.0
-
diff --git a/src/cmd/go/testdata/script/mod_upgrade_patch_mod.txt b/src/cmd/go/testdata/script/mod_upgrade_patch_mod.txt
new file mode 100644 (file)
index 0000000..0853c37
--- /dev/null
@@ -0,0 +1,85 @@
+env GO111MODULE=on
+
+# Initially, we are at v1.0.0 for all dependencies.
+cp go.mod go.mod.orig
+go list -m all
+stdout '^patch.example.com/direct v1.0.0'
+stdout '^patch.example.com/indirect v1.0.0'
+! stdout '^patch.example.com/depofdirectpatch'
+
+# get -m -u=patch, with no arguments, should patch-update all dependencies,
+# pulling in transitive dependencies and also patching those.
+#
+# TODO(golang.org/issue/26902): We should not update transitive dependencies
+# that don't affect the transitive import graph of the main module in any way.
+cp go.mod.orig go.mod
+go get -m -u=patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.1'
+stdout '^patch.example.com/depofdirectpatch v1.0.1' # TODO: leave at v1.0.0
+
+# 'get -m all@patch' should be equivalent to 'get -u=patch -m all'
+cp go.mod.orig go.mod
+go get -m all@patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.1'
+stdout '^patch.example.com/depofdirectpatch v1.0.0'
+
+# Requesting the direct dependency with -u=patch but without an explicit version
+# should patch-update it and its dependencies.
+cp go.mod.orig go.mod
+go get -m -u=patch patch.example.com/direct
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.1'
+stdout '^patch.example.com/depofdirectpatch v1.0.1' # TODO: leave at v1.0.0
+
+# Requesting only the indirect dependency should not update the direct one.
+cp go.mod.orig go.mod
+go get -m -u=patch patch.example.com/indirect
+go list -m all
+stdout '^patch.example.com/direct v1.0.0'
+stdout '^patch.example.com/indirect v1.0.1'
+! stdout '^patch.example.com/depofdirectpatch'
+
+# @patch should apply only to the specific module.
+# but the result must reflect its upgraded requirements.
+cp go.mod.orig go.mod
+go get -m patch.example.com/direct@patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.0'
+stdout '^patch.example.com/depofdirectpatch v1.0.0'
+
+# An explicit @patch should override a general -u.
+cp go.mod.orig go.mod
+go get -m -u patch.example.com/direct@patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.1.0'
+stdout '^patch.example.com/depofdirectpatch v1.0.1'
+
+# An explicit @latest should override a general -u=patch.
+cp go.mod.orig go.mod
+go get -m -u=patch patch.example.com/direct@latest
+go list -m all
+stdout '^patch.example.com/direct v1.1.0'
+stdout '^patch.example.com/indirect v1.0.1'
+! stdout '^patch.example.com/depofdirectpatch'
+
+# Standard-library modules cannot be upgraded explicitly.
+cp go.mod.orig go.mod
+! go get -m std@patch
+stderr 'explicit requirement on standard-library module std not allowed'
+
+
+-- go.mod --
+module x
+
+require patch.example.com/direct v1.0.0
+
+-- main.go --
+package x
+import _ "patch.example.com/direct"
diff --git a/src/cmd/go/testdata/script/mod_upgrade_patch_pkg.txt b/src/cmd/go/testdata/script/mod_upgrade_patch_pkg.txt
new file mode 100644 (file)
index 0000000..8aedaef
--- /dev/null
@@ -0,0 +1,88 @@
+env GO111MODULE=on
+
+# Initially, we are at v1.0.0 for all dependencies.
+cp go.mod go.mod.orig
+go list -m all
+stdout '^patch.example.com/direct v1.0.0'
+stdout '^patch.example.com/indirect v1.0.0'
+! stdout '^patch.example.com/depofdirectpatch'
+
+# get -u=patch, with no arguments, should patch-update all dependencies,
+# pulling in transitive dependencies and also patching those.
+#
+# TODO(golang.org/issue/26902): We should not update dependencies
+# that don't affect the transitive import graph of the main module in any way.
+cp go.mod.orig go.mod
+go get -u=patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.1'
+stdout '^patch.example.com/depofdirectpatch v1.0.1' # TODO: leave at v1.0.0
+
+# 'get all@patch' should be equivalent to 'get -u=patch all'
+cp go.mod.orig go.mod
+go get all@patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.1'
+stdout '^patch.example.com/depofdirectpatch v1.0.0'
+
+# Requesting the direct dependency with -u=patch but without an explicit version
+# should patch-update it and its dependencies.
+cp go.mod.orig go.mod
+go get -u=patch patch.example.com/direct
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.1'
+stdout '^patch.example.com/depofdirectpatch v1.0.1' # TODO: leave at v1.0.0
+
+# Requesting only the indirect dependency should not update the direct one.
+cp go.mod.orig go.mod
+go get -u=patch patch.example.com/indirect
+go list -m all
+stdout '^patch.example.com/direct v1.0.0'
+stdout '^patch.example.com/indirect v1.0.1'
+! stdout '^patch.example.com/depofdirectpatch'
+
+# @patch should apply only to the specific module,
+# but the result must reflect its upgraded requirements.
+cp go.mod.orig go.mod
+go get patch.example.com/direct@patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.0.0'
+stdout '^patch.example.com/depofdirectpatch v1.0.0'
+
+# An explicit @patch should override a general -u.
+cp go.mod.orig go.mod
+go get -u patch.example.com/direct@patch
+go list -m all
+stdout '^patch.example.com/direct v1.0.1'
+stdout '^patch.example.com/indirect v1.1.0'
+stdout '^patch.example.com/depofdirectpatch v1.0.1'
+
+# An explicit @latest should override a general -u=patch.
+cp go.mod.orig go.mod
+go get -u=patch patch.example.com/direct@latest
+go list -m all
+stdout '^patch.example.com/direct v1.1.0'
+stdout '^patch.example.com/indirect v1.0.1'
+! stdout '^patch.example.com/depofdirectpatch'
+
+# Standard-library packages cannot be upgraded explicitly.
+cp go.mod.orig go.mod
+! go get cmd/vet@patch
+stderr 'cannot use pattern .* with explicit version'
+
+# However, standard-library packages without explicit versions are fine.
+go get -u=patch -d cmd/get
+
+
+-- go.mod --
+module x
+
+require patch.example.com/direct v1.0.0
+
+-- main.go --
+package x
+import _ "patch.example.com/direct"