]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/go/internal/modget: resolve paths at the requested versions
authorBryan C. Mills <bcmills@google.com>
Fri, 18 Sep 2020 16:10:58 +0000 (12:10 -0400)
committerBryan C. Mills <bcmills@google.com>
Thu, 5 Nov 2020 17:52:17 +0000 (17:52 +0000)
Previously, we resolved each argument to 'go get' to a package path or
module path based on what was in the build list at existing versions,
even if the argument specified a different version explicitly. That
resulted in bugs like #37438, in which we variously resolved the wrong
version or guessed the wrong argument type for what is unambiguously a
package argument at the requested version.

We were also using a two-step upgrade/downgrade algorithm, which could
not only upgrade more that is strictly necessary, but could also
unintentionally upgrade *above* the requested versions during the
downgrade step.

This change instead uses an iterative approach, with an explicit
disambiguation step for the (rare) cases where an argument could match
the same package path in multiple modules. We use a hook in the
package loader to halt package loading as soon as an incorrect version
is found — preventing over-resolving — and verify that the result
after applying downgrades successfully obtained the requested versions
of all modules.

Making 'go get' be correct and usable is especially important now that
we are defaulting to read-only mode (#40728), for which we are
recommending 'go get' more heavily.

While I'm in here refactoring, I'm also reworking the API boundary
between the modget and modload packages. Previously, the modget
package edited the build list directly, and the modload package
accepted the edited build list without validation. For lazy loading
(#36460), the modload package will need to maintain additional
metadata about the requirement graph, so it needs tighter control over
the changes to the build list.

As of this change, modget no longer invokes MVS directly, but instead
goes through the modload package. The resulting API gives clearer
reasons in case of updates, which we can use to emit more useful
errors.

Fixes #37438
Updates #36460
Updates #40728

Change-Id: I596f0020f3795870dec258147e6fc26a3292c93a
Reviewed-on: https://go-review.googlesource.com/c/go/+/263267
Trust: Bryan C. Mills <bcmills@google.com>
Trust: Jay Conrod <jayconrod@google.com>
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
42 files changed:
doc/go1.16.html
src/cmd/go/alldocs.go
src/cmd/go/internal/modget/get.go
src/cmd/go/internal/modget/mvs.go [deleted file]
src/cmd/go/internal/modget/query.go [new file with mode: 0644]
src/cmd/go/internal/modload/buildlist.go
src/cmd/go/internal/modload/import.go
src/cmd/go/internal/modload/load.go
src/cmd/go/internal/modload/mvs.go
src/cmd/go/internal/modload/query.go
src/cmd/go/internal/modload/search.go
src/cmd/go/testdata/script/get_update_all.txt
src/cmd/go/testdata/script/mod_bad_domain.txt
src/cmd/go/testdata/script/mod_dot.txt
src/cmd/go/testdata/script/mod_download.txt
src/cmd/go/testdata/script/mod_get_ambiguous_arg.txt
src/cmd/go/testdata/script/mod_get_ambiguous_import.txt
src/cmd/go/testdata/script/mod_get_ambiguous_pkg.txt
src/cmd/go/testdata/script/mod_get_downgrade.txt
src/cmd/go/testdata/script/mod_get_downgrade_missing.txt
src/cmd/go/testdata/script/mod_get_extra.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_get_issue37438.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_get_main.txt
src/cmd/go/testdata/script/mod_get_moved.txt
src/cmd/go/testdata/script/mod_get_newcycle.txt
src/cmd/go/testdata/script/mod_get_patch.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_get_patchbound.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_get_patchcycle.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_get_patchmod.txt
src/cmd/go/testdata/script/mod_get_patterns.txt
src/cmd/go/testdata/script/mod_get_replaced.txt
src/cmd/go/testdata/script/mod_get_split.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_get_wild.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_invalid_version.txt
src/cmd/go/testdata/script/mod_load_badchain.txt
src/cmd/go/testdata/script/mod_outside.txt
src/cmd/go/testdata/script/mod_query_empty.txt
src/cmd/go/testdata/script/mod_query_exclude.txt
src/cmd/go/testdata/script/mod_retract_pseudo_base.txt
src/cmd/go/testdata/script/mod_sumdb.txt
src/cmd/go/testdata/script/mod_sumdb_file_path.txt
src/cmd/go/testdata/script/mod_upgrade_patch.txt

index 5ae85e6c293bccb2a0cfcb300053a6df1437706e..99e8e3c980d23e3d99d6c67576794611e44d8388 100644 (file)
@@ -110,6 +110,16 @@ Do not send CLs removing the interior tags from such phrases.
   See <code>go</code> <code>help</code> <code>environment</code> for details.
 </p>
 
+<h4 id="go-get"><code>go</code> <code>get</code></h4>
+
+<p><!-- golang.org/cl/263267 -->
+  <code>go</code> <code>get</code> <code>example.com/mod@patch</code> now
+  requires that some version of <code>example.com/mod</code> already be
+  required by the main module.
+  (However, <code>go</code> <code>get</code> <code>-u=patch</code> continues
+  to patch even newly-added dependencies.)
+</p>
+
 <h4 id="all-pattern">The <code>all</code> pattern</h4>
 
 <p><!-- golang.org/cl/240623 -->
index 23d44ddc708399ebbfb3b8f4b17927d3a1423446..ada85b1324152b426426a08481b8a16a1b70e0c8 100644 (file)
 // 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
+// The version suffix @latest explicitly requests the latest minor release ofthe
 // module named by the given path. The suffix @upgrade is like @latest but
 // will not downgrade a module if it is already required at a revision or
 // pre-release version newer than the latest released version. The suffix
 // @patch requests the latest patch release: the latest released version
 // with the same major and minor version numbers as the currently required
 // version. Like @upgrade, @patch will not downgrade a module already required
-// at a newer version. If the path is not already required, @upgrade and @patch
-// are equivalent to @latest.
+// at a newer version. If the path is not already required, @upgrade is
+// equivalent to @latest, and @patch is disallowed.
 //
 // 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
index 6d83af710fe19779a4b496acfa8eee243ee34789..bb59191c734583415f6bd5f63a580e9ab2a3cd78 100644 (file)
@@ -5,12 +5,32 @@
 // Package modget implements the module-aware ``go get'' command.
 package modget
 
+// The arguments to 'go get' are patterns with optional version queries, with
+// the version queries defaulting to "upgrade".
+//
+// The patterns are normally interpreted as package patterns. However, if a
+// pattern cannot match a package, it is instead interpreted as a *module*
+// pattern. For version queries such as "upgrade" and "patch" that depend on the
+// selected version of a module (or of the module containing a package),
+// whether a pattern denotes a package or module may change as updates are
+// applied (see the example in mod_get_patchmod.txt).
+//
+// There are a few other ambiguous cases to resolve, too. A package can exist in
+// two different modules at the same version: for example, the package
+// example.com/foo might be found in module example.com and also in module
+// example.com/foo, and those modules may have independent v0.1.0 tags — so the
+// input 'example.com/foo@v0.1.0' could syntactically refer to the variant of
+// the package loaded from either module! (See mod_get_ambiguous_pkg.txt.)
+// If the argument is ambiguous, the user can often disambiguate by specifying
+// explicit versions for *all* of the potential module paths involved.
+
 import (
        "context"
        "errors"
        "fmt"
        "os"
        "path/filepath"
+       "reflect"
        "runtime"
        "sort"
        "strings"
@@ -21,12 +41,11 @@ import (
        "cmd/go/internal/imports"
        "cmd/go/internal/load"
        "cmd/go/internal/modload"
-       "cmd/go/internal/mvs"
+       "cmd/go/internal/par"
        "cmd/go/internal/search"
        "cmd/go/internal/work"
 
        "golang.org/x/mod/module"
-       "golang.org/x/mod/semver"
 )
 
 var CmdGet = &base.Command{
@@ -70,15 +89,15 @@ 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
+The version suffix @latest explicitly requests the latest minor release ofthe
 module named by the given path. The suffix @upgrade is like @latest but
 will not downgrade a module if it is already required at a revision or
 pre-release version newer than the latest released version. The suffix
 @patch requests the latest patch release: the latest released version
 with the same major and minor version numbers as the currently required
 version. Like @upgrade, @patch will not downgrade a module already required
-at a newer version. If the path is not already required, @upgrade and @patch
-are equivalent to @latest.
+at a newer version. If the path is not already required, @upgrade is
+equivalent to @latest, and @patch is disallowed.
 
 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
@@ -188,18 +207,24 @@ var (
 )
 
 // upgradeFlag is a custom flag.Value for -u.
-type upgradeFlag string
+type upgradeFlag struct {
+       rawVersion string
+       version    string
+}
 
 func (*upgradeFlag) IsBoolFlag() bool { return true } // allow -u
 
 func (v *upgradeFlag) Set(s string) error {
        if s == "false" {
-               s = ""
+               v.version = ""
+               v.rawVersion = ""
+       } else if s == "true" {
+               v.version = "upgrade"
+               v.rawVersion = ""
+       } else {
+               v.version = s
+               v.rawVersion = s
        }
-       if s == "true" {
-               s = "upgrade"
-       }
-       *v = upgradeFlag(s)
        return nil
 }
 
@@ -212,64 +237,12 @@ func init() {
        CmdGet.Flag.Var(&getU, "u", "")
 }
 
-// A getArg holds a parsed positional argument for go get (path@vers).
-type getArg struct {
-       // raw is the original argument, to be printed in error messages.
-       raw string
-
-       // path is the part of the argument before "@" (or the whole argument
-       // if there is no "@"). path specifies the modules or packages to get.
-       path string
-
-       // vers is the part of the argument after "@" or an implied
-       // "upgrade" or "patch" if there is no "@". vers specifies the
-       // module version to get.
-       vers string
-}
-
-func (arg getArg) String() string { return arg.raw }
-
-// querySpec describes a query for a specific module. path may be a
-// module path, package path, or package pattern. vers is a version
-// query string from a command line argument.
-type querySpec struct {
-       // path is a module path, package path, or package pattern that
-       // specifies which module to query.
-       path string
-
-       // vers specifies what version of the module to get.
-       vers string
-
-       // forceModulePath is true if path should be interpreted as a module path.
-       // If forceModulePath is true, prevM must be set.
-       forceModulePath bool
-
-       // prevM is the previous version of the module. prevM is needed
-       // to determine the minor version number if vers is "patch". It's also
-       // used to avoid downgrades from prerelease versions newer than
-       // "latest" and "patch". If prevM is set, forceModulePath must be true.
-       prevM module.Version
-}
-
-// query holds the state for a query made for a specific module.
-// After a query is performed, we know the actual module path and
-// version and whether any packages were matched by the query path.
-type query struct {
-       querySpec
-
-       // arg is the command line argument that matched the specified module.
-       arg string
-
-       // m is the module path and version found by the query.
-       m module.Version
-}
-
 func runGet(ctx context.Context, cmd *base.Command, args []string) {
-       switch getU {
+       switch getU.version {
        case "", "upgrade", "patch":
                // ok
        default:
-               base.Fatalf("go get: unknown upgrade flag -u=%s", getU)
+               base.Fatalf("go get: unknown upgrade flag -u=%s", getU.rawVersion)
        }
        if *getF {
                fmt.Fprintf(os.Stderr, "go get: -f flag is a no-op when using modules\n")
@@ -294,311 +267,105 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
        // 'go get' is expected to do this, unlike other commands.
        modload.AllowMissingModuleImports()
 
-       getArgs := parseArgs(args)
-
-       buildList := modload.LoadAllModules(ctx)
-       buildList = buildList[:len(buildList):len(buildList)] // copy on append
-       selectedVersion := make(map[string]string)
-       for _, m := range buildList {
-               selectedVersion[m.Path] = m.Version
-       }
-       queries := classifyArgs(ctx, selectedVersion, getArgs)
+       modload.LoadModFile(ctx) // Initializes modload.Target.
 
-       // Query modules referenced by command line arguments at requested versions.
-       // We need to do this before loading packages since patterns that refer to
-       // packages in unknown modules can't be expanded. This also avoids looking
-       // up new modules while loading packages, only to downgrade later.
-       queryCache := make(map[querySpec]*query)
-       byPath := runQueries(ctx, queryCache, queries, nil)
-
-       // Add missing modules to the build list.
-       // We call SetBuildList here and elsewhere, since newUpgrader,
-       // LoadPackages, and other functions read the global build list.
-       for _, q := range queries {
-               if _, ok := selectedVersion[q.m.Path]; !ok && q.m.Version != "none" {
-                       buildList = append(buildList, q.m)
-               }
-       }
-       selectedVersion = nil // out of date now; rebuilt later when needed
-       modload.SetBuildList(buildList)
-
-       // Upgrade modules specifically named on the command line. This is our only
-       // chance to upgrade modules without root packages (modOnly below).
-       // This also skips loading packages at an old version, only to upgrade
-       // and reload at a new version.
-       upgrade := make(map[string]*query)
-       for path, q := range byPath {
-               if q.path == q.m.Path && q.m.Version != "none" {
-                       upgrade[path] = q
-               }
-       }
-       buildList, err := mvs.UpgradeAll(modload.Target, newUpgrader(upgrade, nil))
-       if err != nil {
-               base.Fatalf("go get: %v", err)
-       }
-       modload.SetBuildList(buildList)
-       base.ExitIfErrors()
-       prevBuildList := buildList
-
-       // Build a set of module paths that we don't plan to load packages from.
-       // This includes explicitly requested modules that don't have a root package
-       // and modules with a target version of "none".
-       var wg sync.WaitGroup
-       var modOnlyMu sync.Mutex
-       modOnly := make(map[string]*query)
-       for _, q := range queries {
-               if q.m.Version == "none" {
-                       modOnlyMu.Lock()
-                       modOnly[q.m.Path] = q
-                       modOnlyMu.Unlock()
-                       continue
-               }
-               if q.path == q.m.Path {
-                       wg.Add(1)
-                       go func(q *query) {
-                               if hasPkg, err := modload.ModuleHasRootPackage(ctx, q.m); err != nil {
-                                       base.Errorf("go get: %v", err)
-                               } else if !hasPkg {
-                                       modOnlyMu.Lock()
-                                       modOnly[q.m.Path] = q
-                                       modOnlyMu.Unlock()
-                               }
-                               wg.Done()
-                       }(q)
-               }
-       }
-       wg.Wait()
-       base.ExitIfErrors()
+       queries := parseArgs(ctx, args)
 
-       // Build a list of arguments that may refer to packages.
-       var pkgPatterns []string
-       var pkgGets []getArg
-       for _, arg := range getArgs {
-               if modOnly[arg.path] == nil && arg.vers != "none" {
-                       pkgPatterns = append(pkgPatterns, arg.path)
-                       pkgGets = append(pkgGets, arg)
-               }
-       }
+       r := newResolver(ctx, queries)
+       r.performLocalQueries(ctx)
+       r.performPathQueries(ctx)
 
-       // Load packages and upgrade the modules that provide them. We do this until
-       // we reach a fixed point, since modules providing packages may change as we
-       // change versions. This must terminate because the module graph is finite,
-       // and the load and upgrade operations may only add and upgrade modules
-       // in the build list.
-       var matches []*search.Match
        for {
-               var seenPkgs map[string]bool
-               seenQuery := make(map[querySpec]bool)
-               var queries []*query
-               addQuery := func(q *query) {
-                       if !seenQuery[q.querySpec] {
-                               seenQuery[q.querySpec] = true
-                               queries = append(queries, q)
-                       }
-               }
-
-               if len(pkgPatterns) > 0 {
-                       // Don't load packages if pkgPatterns is empty. Both
-                       // modload.LoadPackages and ModulePackages convert an empty list
-                       // of patterns to []string{"."}, which is not what we want.
-                       loadOpts := modload.PackageOpts{
-                               Tags:                     imports.AnyTags(),
-                               ResolveMissingImports:    true, // dubious; see https://golang.org/issue/32567
-                               LoadTests:                *getT,
-                               SilenceErrors:            true, // Errors may be fixed by subsequent upgrades or downgrades.
-                               SilenceUnmatchedWarnings: true, // We will warn after iterating below.
-                       }
-                       matches, _ = modload.LoadPackages(ctx, loadOpts, pkgPatterns...)
-                       seenPkgs = make(map[string]bool)
-                       for i, match := range matches {
-                               arg := pkgGets[i]
-
-                               if len(match.Pkgs) == 0 {
-                                       // If the pattern did not match any packages, look up a new module.
-                                       // If the pattern doesn't match anything on the last iteration,
-                                       // we'll print a warning after the outer loop.
-                                       if !match.IsLocal() && !match.IsLiteral() && arg.path != "all" {
-                                               addQuery(&query{querySpec: querySpec{path: arg.path, vers: arg.vers}, arg: arg.raw})
-                                       } else {
-                                               for _, err := range match.Errs {
-                                                       base.Errorf("go get: %v", err)
-                                               }
-                                       }
-                                       continue
-                               }
-
-                               allStd := true
-                               for _, pkg := range match.Pkgs {
-                                       if !seenPkgs[pkg] {
-                                               seenPkgs[pkg] = true
-                                               if _, _, err := modload.Lookup("", false, pkg); err != nil {
-                                                       allStd = false
-                                                       base.Errorf("go get %s: %v", arg.raw, err)
-                                                       continue
-                                               }
-                                       }
-                                       m := modload.PackageModule(pkg)
-                                       if m.Path == "" {
-                                               // pkg is in the standard library.
-                                               continue
-                                       }
-                                       allStd = false
-                                       if m.Path == modload.Target.Path {
-                                               // pkg is in the main module.
-                                               continue
-                                       }
-                                       addQuery(&query{querySpec: querySpec{path: m.Path, vers: arg.vers, forceModulePath: true, prevM: m}, arg: arg.raw})
-                               }
-                               if allStd && arg.path != arg.raw {
-                                       base.Errorf("go get %s: cannot use pattern %q with explicit version", arg.raw, arg.raw)
-                               }
-                       }
-               }
-               base.ExitIfErrors()
-
-               // Query target versions for modules providing packages matched by
-               // command line arguments.
-               byPath = runQueries(ctx, queryCache, queries, modOnly)
-
-               // Handle upgrades. This is needed for arguments that didn't match
-               // modules or matched different modules from a previous iteration. It
-               // also upgrades modules providing package dependencies if -u is set.
-               buildList, err := mvs.UpgradeAll(modload.Target, newUpgrader(byPath, seenPkgs))
-               if err != nil {
-                       base.Fatalf("go get: %v", err)
+               r.performWildcardQueries(ctx)
+               r.performPatternAllQueries(ctx)
+
+               if changed := r.resolveCandidates(ctx, queries, nil); changed {
+                       // 'go get' arguments can be (and often are) package patterns rather than
+                       // (just) modules. A package can be provided by any module with a prefix
+                       // of its import path, and a wildcard can even match packages in modules
+                       // with totally different paths. Because of these effects, and because any
+                       // change to the selected version of a module can bring in entirely new
+                       // module paths as dependencies, we need to reissue queries whenever we
+                       // change the build list.
+                       //
+                       // The result of any version query for a given module — even "upgrade" or
+                       // "patch" — is always relative to the build list at the start of
+                       // the 'go get' command, not an intermediate state, and is therefore
+                       // dederministic and therefore cachable, and the constraints on the
+                       // selected version of each module can only narrow as we iterate.
+                       //
+                       // "all" is functionally very similar to a wildcard pattern. The set of
+                       // packages imported by the main module does not change, and the query
+                       // result for the module containing each such package also does not change
+                       // (it is always relative to the initial build list, before applying
+                       // queries). So the only way that the result of an "all" query can change
+                       // is if some matching package moves from one module in the build list
+                       // to another, which should not happen very often.
+                       continue
                }
-               modload.SetBuildList(buildList)
-               base.ExitIfErrors()
 
-               // Stop if no changes have been made to the build list.
-               buildList = modload.LoadedModules()
-               eq := len(buildList) == len(prevBuildList)
-               for i := 0; eq && i < len(buildList); i++ {
-                       eq = buildList[i] == prevBuildList[i]
-               }
-               if eq {
-                       break
+               // When we load imports, we detect the following conditions:
+               //
+               // - missing transitive depencies that need to be resolved from outside the
+               //   current build list (note that these may add new matches for existing
+               //   pattern queries!)
+               //
+               // - transitive dependencies that didn't match any other query,
+               //   but need to be upgraded due to the -u flag
+               //
+               // - ambiguous import errors.
+               //   TODO(#27899): Try to resolve ambiguous import errors automatically.
+               upgrades := r.findAndUpgradeImports(ctx, queries)
+               if changed := r.resolveCandidates(ctx, nil, upgrades); changed {
+                       continue
                }
-               prevBuildList = buildList
-       }
 
-       // Handle downgrades.
-       var down []module.Version
-       for _, m := range modload.LoadedModules() {
-               q := byPath[m.Path]
-               if q != nil && semver.Compare(m.Version, q.m.Version) > 0 {
-                       down = append(down, module.Version{Path: m.Path, Version: q.m.Version})
-               }
-       }
-       if len(down) > 0 {
-               buildList, err := mvs.Downgrade(modload.Target, modload.Reqs(), down...)
-               if err != nil {
-                       base.Fatalf("go: %v", err)
+               r.findMissingWildcards(ctx)
+               if changed := r.resolveCandidates(ctx, r.wildcardQueries, nil); changed {
+                       continue
                }
 
-               // TODO(bcmills) What should happen here under lazy loading?
-               // Downgrading may intentionally violate the lazy-loading invariants.
-
-               modload.SetBuildList(buildList)
-               modload.ReloadBuildList() // note: does not update go.mod
-               base.ExitIfErrors()
+               break
        }
 
-       // Scan for any upgrades lost by the downgrades.
-       var lostUpgrades []*query
-       if len(down) > 0 {
-               selectedVersion = make(map[string]string)
-               for _, m := range modload.LoadedModules() {
-                       selectedVersion[m.Path] = m.Version
-               }
-               for _, q := range byPath {
-                       if v, ok := selectedVersion[q.m.Path]; q.m.Version != "none" && (!ok || semver.Compare(v, q.m.Version) != 0) {
-                               lostUpgrades = append(lostUpgrades, q)
-                       }
-               }
-               sort.Slice(lostUpgrades, func(i, j int) bool {
-                       return lostUpgrades[i].m.Path < lostUpgrades[j].m.Path
-               })
-       }
-       if len(lostUpgrades) > 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:")
-               reqs := modload.Reqs()
-               for _, q := range lostUpgrades {
-                       // We lost q 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 := buildListForLostUpgrade(q.m, reqs)
-                       if err != nil {
-                               base.Fatalf("go: %v", err)
-                       }
+       r.checkWildcardVersions(ctx)
 
-                       fmt.Fprintf(&buf, "\n\t%s", desc(q.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.
-                               if v := selectedVersion[q.m.Path]; v == "" {
-                                       fmt.Fprintf(&buf, " removed unexpectedly")
-                               } else {
-                                       fmt.Fprintf(&buf, " ended up at %s unexpectedly", v)
-                               }
-                               fmt.Fprintf(&buf, " (please report at golang.org/issue/new)")
-                       }
+       var pkgPatterns []string
+       for _, q := range queries {
+               if q.matchesPackages {
+                       pkgPatterns = append(pkgPatterns, q.pattern)
                }
-               base.Fatalf("%v", buf.String())
        }
-
-       if len(pkgPatterns) > 0 || len(args) == 0 {
-               // Before we write the updated go.mod file, reload the requested packages to
-               // check for errors.
-               loadOpts := modload.PackageOpts{
-                       Tags:      imports.AnyTags(),
-                       LoadTests: *getT,
-
-                       // Only print warnings after the last iteration, and only if we aren't going
-                       // to build (to avoid doubled warnings).
-                       //
-                       // Only local patterns in the main module, such as './...', can be unmatched.
-                       // (See the mod_get_nopkgs test for more detail.)
-                       SilenceUnmatchedWarnings: !*getD,
+       if len(pkgPatterns) > 0 {
+               // We skipped over missing-package errors earlier: we want to resolve
+               // pathSets ourselves, but at that point we don't have enough context
+               // to log the package-import chains leading to the error. Reload the package
+               // import graph one last time to report any remaining unresolved
+               // dependencies.
+               pkgOpts := modload.PackageOpts{
+                       LoadTests:             *getT,
+                       ResolveMissingImports: false,
+                       AllowErrors:           false,
                }
-               modload.LoadPackages(ctx, loadOpts, pkgPatterns...)
+               modload.LoadPackages(ctx, pkgOpts, pkgPatterns...)
+               base.ExitIfErrors()
        }
 
-       // If -d was specified, we're done after the module work.
-       // We've already downloaded modules by loading packages above.
+       // We've already downloaded modules (and identified direct and indirect
+       // dependencies) by loading packages in findAndUpgradeImports.
+       // So if -d is set, we're done after the module work.
+       //
        // Otherwise, we need to build and install the packages matched by
-       // command line arguments. This may be a different set of packages,
-       // since we only build packages for the target platform.
+       // command line arguments.
        // Note that 'go get -u' without arguments is equivalent to
        // 'go get -u .', so we'll typically build the package in the current
        // directory.
-       if !*getD && len(pkgPatterns) > 0 {
-               work.BuildInit()
-               pkgs := load.PackagesForBuild(ctx, pkgPatterns)
-               work.InstallPackages(ctx, pkgPatterns, pkgs)
+       if !*getD {
+               if len(pkgPatterns) > 0 {
+                       work.BuildInit()
+                       pkgs := load.PackagesForBuild(ctx, pkgPatterns)
+                       work.InstallPackages(ctx, pkgPatterns, pkgs)
+               }
        }
 
        // Everything succeeded. Update go.mod.
@@ -609,9 +376,10 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
        // Report warnings if any retracted versions are in the build list.
        // This must be done after writing go.mod to avoid spurious '// indirect'
        // comments. These functions read and write global state.
-       // TODO(golang.org/issue/40775): ListModules resets modload.loader, which
-       // contains information about direct dependencies that WriteGoMod uses.
-       // Refactor to avoid these kinds of global side effects.
+       //
+       // TODO(golang.org/issue/40775): ListModules (called from reportRetractions)
+       // resets modload.loader, which contains information about direct dependencies
+       // that WriteGoMod uses. Refactor to avoid these kinds of global side effects.
        reportRetractions(ctx)
 }
 
@@ -619,375 +387,1197 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
 //
 // The command-line arguments are of the form path@version or simply path, with
 // implicit @upgrade. path@none is "downgrade away".
-func parseArgs(rawArgs []string) []getArg {
+func parseArgs(ctx context.Context, rawArgs []string) []*query {
        defer base.ExitIfErrors()
 
-       var gets []getArg
-       for _, raw := range search.CleanPatterns(rawArgs) {
-               // Argument is path or path@vers.
-               path := raw
-               vers := ""
-               if i := strings.Index(raw, "@"); i >= 0 {
-                       path, vers = raw[:i], raw[i+1:]
-               }
-               if strings.Contains(vers, "@") || raw != path && vers == "" {
-                       base.Errorf("go get %s: invalid module version syntax", raw)
+       var queries []*query
+       for _, arg := range search.CleanPatterns(rawArgs) {
+               q, err := newQuery(arg)
+               if err != nil {
+                       base.Errorf("go get: %v", err)
                        continue
                }
 
                // Guard against 'go get x.go', a common mistake.
                // Note that package and module paths may end with '.go', so only print an error
                // if the argument has no version and either has no slash or refers to an existing file.
-               if strings.HasSuffix(raw, ".go") && vers == "" {
-                       if !strings.Contains(raw, "/") {
-                               base.Errorf("go get %s: arguments must be package or module paths", raw)
+               if strings.HasSuffix(q.raw, ".go") && q.rawVersion == "" {
+                       if !strings.Contains(q.raw, "/") {
+                               base.Errorf("go get %s: arguments must be package or module paths", q.raw)
                                continue
                        }
-                       if fi, err := os.Stat(raw); err == nil && !fi.IsDir() {
-                               base.Errorf("go get: %s exists as a file, but 'go get' requires package arguments", raw)
+                       if fi, err := os.Stat(q.raw); err == nil && !fi.IsDir() {
+                               base.Errorf("go get: %s exists as a file, but 'go get' requires package arguments", q.raw)
                                continue
                        }
                }
 
-               // If no version suffix is specified, assume @upgrade.
-               // If -u=patch was specified, assume @patch instead.
-               if vers == "" {
-                       if getU != "" {
-                               vers = string(getU)
+               queries = append(queries, q)
+       }
+
+       return queries
+}
+
+// reportRetractions prints warnings if any modules in the build list are
+// retracted.
+func reportRetractions(ctx context.Context) {
+       // Query for retractions of modules in the build list.
+       // Use modload.ListModules, since that provides information in the same format
+       // as 'go list -m'. Don't query for "all", since that's not allowed outside a
+       // module.
+       buildList := modload.LoadedModules()
+       args := make([]string, 0, len(buildList))
+       for _, m := range buildList {
+               if m.Version == "" {
+                       // main module or dummy target module
+                       continue
+               }
+               args = append(args, m.Path+"@"+m.Version)
+       }
+       listU := false
+       listVersions := false
+       listRetractions := true
+       mods := modload.ListModules(ctx, args, listU, listVersions, listRetractions)
+       retractPath := ""
+       for _, mod := range mods {
+               if len(mod.Retracted) > 0 {
+                       if retractPath == "" {
+                               retractPath = mod.Path
                        } else {
-                               vers = "upgrade"
+                               retractPath = "<module>"
                        }
+                       rationale := modload.ShortRetractionRationale(mod.Retracted[0])
+                       fmt.Fprintf(os.Stderr, "go: warning: %s@%s is retracted: %s\n", mod.Path, mod.Version, rationale)
+               }
+       }
+       if modload.HasModRoot() && retractPath != "" {
+               fmt.Fprintf(os.Stderr, "go: run 'go get %s@latest' to switch to the latest unretracted version\n", retractPath)
+       }
+}
+
+type resolver struct {
+       localQueries      []*query // queries for absolute or relative paths
+       pathQueries       []*query // package path literal queries in original order
+       wildcardQueries   []*query // path wildcard queries in original order
+       patternAllQueries []*query // queries with the pattern "all"
+
+       // Indexed "none" queries. These are also included in the slices above;
+       // they are indexed here to speed up noneForPath.
+       nonesByPath   map[string]*query // path-literal "@none" queries indexed by path
+       wildcardNones []*query          // wildcard "@none" queries
+
+       // resolvedVersion maps each module path to the version of that module that
+       // must be selected in the final build list, along with the first query
+       // that resolved the module to that version (the “reason”).
+       resolvedVersion map[string]versionReason
+
+       buildList                 []module.Version
+       buildListResolvedVersions int               // len(resolvedVersion) when buildList was computed
+       buildListVersion          map[string]string // index of buildList (module path → version)
+
+       initialVersion map[string]string // index of the initial build list at the start of 'go get'
+
+       missing []pathSet // candidates for missing transitive dependencies
+
+       work *par.Queue
+
+       queryModuleCache   par.Cache
+       queryPackagesCache par.Cache
+       queryPatternCache  par.Cache
+       matchInModuleCache par.Cache
+}
+
+type versionReason struct {
+       version string
+       reason  *query
+}
+
+func newResolver(ctx context.Context, queries []*query) *resolver {
+       buildList := modload.LoadAllModules(ctx)
+       initialVersion := make(map[string]string, len(buildList))
+       for _, m := range buildList {
+               initialVersion[m.Path] = m.Version
+       }
+
+       r := &resolver{
+               work:             par.NewQueue(runtime.GOMAXPROCS(0)),
+               resolvedVersion:  map[string]versionReason{},
+               buildList:        buildList,
+               buildListVersion: initialVersion,
+               initialVersion:   initialVersion,
+               nonesByPath:      map[string]*query{},
+       }
+
+       for _, q := range queries {
+               if q.pattern == "all" {
+                       r.patternAllQueries = append(r.patternAllQueries, q)
+               } else if q.patternIsLocal {
+                       r.localQueries = append(r.localQueries, q)
+               } else if q.isWildcard() {
+                       r.wildcardQueries = append(r.wildcardQueries, q)
+               } else {
+                       r.pathQueries = append(r.pathQueries, q)
+               }
+
+               if q.version == "none" {
+                       // Index "none" queries to make noneForPath more efficient.
+                       if q.isWildcard() {
+                               r.wildcardNones = append(r.wildcardNones, q)
+                       } else {
+                               // All "<path>@none" queries for the same path are identical; we only
+                               // need to index one copy.
+                               r.nonesByPath[q.pattern] = q
+                       }
+               }
+       }
+
+       return r
+}
+
+// initialSelected returns the version of the module with the given path that
+// was selected at the start of this 'go get' invocation.
+func (r *resolver) initialSelected(mPath string) (version string) {
+       v, ok := r.initialVersion[mPath]
+       if !ok {
+               return "none"
+       }
+       return v
+}
+
+// selected returns the version of the module with the given path that is
+// selected in the resolver's current build list.
+func (r *resolver) selected(mPath string) (version string) {
+       v, ok := r.buildListVersion[mPath]
+       if !ok {
+               return "none"
+       }
+       return v
+}
+
+// noneForPath returns a "none" query matching the given module path,
+// or found == false if no such query exists.
+func (r *resolver) noneForPath(mPath string) (nq *query, found bool) {
+       if nq = r.nonesByPath[mPath]; nq != nil {
+               return nq, true
+       }
+       for _, nq := range r.wildcardNones {
+               if nq.matchesPath(mPath) {
+                       return nq, true
+               }
+       }
+       return nil, false
+}
+
+// queryModule wraps modload.Query, substituting r.checkAllowedor to decide
+// allowed versions.
+func (r *resolver) queryModule(ctx context.Context, mPath, query string, selected func(string) string) (module.Version, error) {
+       current := r.initialSelected(mPath)
+       rev, err := modload.Query(ctx, mPath, query, current, r.checkAllowedOr(query, selected))
+       if err != nil {
+               return module.Version{}, err
+       }
+       return module.Version{Path: mPath, Version: rev.Version}, nil
+}
+
+// queryPackage wraps modload.QueryPackage, substituting r.checkAllowedOr to
+// decide allowed versions.
+func (r *resolver) queryPackages(ctx context.Context, pattern, query string, selected func(string) string) (pkgMods []module.Version, err error) {
+       results, err := modload.QueryPackages(ctx, pattern, query, selected, r.checkAllowedOr(query, selected))
+       if len(results) > 0 {
+               pkgMods = make([]module.Version, 0, len(results))
+               for _, qr := range results {
+                       pkgMods = append(pkgMods, qr.Mod)
+               }
+       }
+       return pkgMods, err
+}
+
+// queryPattern wraps modload.QueryPattern, substituting r.checkAllowedOr to
+// decide allowed versions.
+func (r *resolver) queryPattern(ctx context.Context, pattern, query string, selected func(string) string) (pkgMods []module.Version, mod module.Version, err error) {
+       results, modOnly, err := modload.QueryPattern(ctx, pattern, query, selected, r.checkAllowedOr(query, selected))
+       if len(results) > 0 {
+               pkgMods = make([]module.Version, 0, len(results))
+               for _, qr := range results {
+                       pkgMods = append(pkgMods, qr.Mod)
                }
+       }
+       if modOnly != nil {
+               mod = modOnly.Mod
+       }
+       return pkgMods, mod, err
+}
+
+// checkAllowedOr is like modload.CheckAllowed, but it always allows the requested
+// and current versions (even if they are retracted or otherwise excluded).
+func (r *resolver) checkAllowedOr(requested string, selected func(string) string) modload.AllowedFunc {
+       return func(ctx context.Context, m module.Version) error {
+               if m.Version == requested {
+                       return modload.CheckExclusions(ctx, m)
+               }
+               if (requested == "upgrade" || requested == "patch") && m.Version == selected(m.Path) {
+                       return nil
+               }
+               return modload.CheckAllowed(ctx, m)
+       }
+}
 
-               gets = append(gets, getArg{raw: raw, path: path, vers: vers})
+// matchInModule is a caching wrapper around modload.MatchInModule.
+func (r *resolver) matchInModule(ctx context.Context, pattern string, m module.Version) (packages []string, err error) {
+       type key struct {
+               pattern string
+               m       module.Version
+       }
+       type entry struct {
+               packages []string
+               err      error
        }
 
-       return gets
+       e := r.matchInModuleCache.Do(key{pattern, m}, func() interface{} {
+               match := modload.MatchInModule(ctx, pattern, m, imports.AnyTags())
+               if len(match.Errs) > 0 {
+                       return entry{match.Pkgs, match.Errs[0]}
+               }
+               return entry{match.Pkgs, nil}
+       }).(entry)
+
+       return e.packages, e.err
 }
 
-// classifyArgs determines which arguments refer to packages and which refer to
-// modules, and creates queries to look up modules at target versions before
-// loading packages.
+// queryNone adds a candidate set to q for each module matching q.pattern.
+// Each candidate set has only one possible module version: the matched
+// module at version "none".
 //
-// This is an imprecise process, but it helps reduce unnecessary
-// queries and package loading. It's also necessary for handling
-// patterns like golang.org/x/tools/..., which can't be expanded
-// during package loading until they're in the build list.
-func classifyArgs(ctx context.Context, selectedVersion map[string]string, args []getArg) []*query {
-       defer base.ExitIfErrors()
+// We interpret arguments to 'go get' as packages first, and fall back to
+// modules second. However, no module exists at version "none", and therefore no
+// package exists at that version either: we know that the argument cannot match
+// any packages, and thus it must match modules instead.
+func (r *resolver) queryNone(ctx context.Context, q *query) {
+       if search.IsMetaPackage(q.pattern) {
+               panic(fmt.Sprintf("internal error: queryNone called with pattern %q", q.pattern))
+       }
 
-       queries := make([]*query, 0, len(args))
-
-       for _, arg := range args {
-               path := arg.path
-               switch {
-               case filepath.IsAbs(path) || search.IsRelativePath(path):
-                       // Absolute paths like C:\foo and relative paths like ../foo...
-                       // are restricted to matching packages in the main module. If the path
-                       // is explicit and contains no wildcards (...), check that it is a
-                       // package in the main module. If the path contains wildcards but
-                       // matches no packages, we'll warn after package loading.
-                       if !strings.Contains(path, "...") {
-                               m := search.NewMatch(path)
-                               if pkgPath := modload.DirImportPath(path); pkgPath != "." {
-                                       m = modload.TargetPackages(ctx, pkgPath)
-                               }
-                               if len(m.Pkgs) == 0 {
-                                       for _, err := range m.Errs {
-                                               base.Errorf("go get %s: %v", arg, err)
-                                       }
+       if !q.isWildcard() {
+               q.pathOnce(q.pattern, func() pathSet {
+                       if modload.HasModRoot() && q.pattern == modload.Target.Path {
+                               // The user has explicitly requested to downgrade their own module to
+                               // version "none". This is not an entirely unreasonable request: it
+                               // could plausibly mean “downgrade away everything that depends on any
+                               // explicit version of the main module”, or “downgrade away the
+                               // package with the same path as the main module, found in a module
+                               // with a prefix of the main module's path”.
+                               //
+                               // However, neither of those behaviors would be consistent with the
+                               // plain meaning of the query. To try to reduce confusion, reject the
+                               // query explicitly.
+                               return errSet(&modload.QueryMatchesMainModuleError{Pattern: q.pattern, Query: q.version})
+                       }
 
-                                       abs, err := filepath.Abs(path)
-                                       if err != nil {
-                                               abs = path
-                                       }
-                                       base.Errorf("go get %s: path %s is not a package in module rooted at %s", arg, abs, modload.ModRoot())
-                                       continue
+                       return pathSet{mod: module.Version{Path: q.pattern, Version: "none"}}
+               })
+       }
+
+       for _, curM := range r.buildList {
+               if !q.matchesPath(curM.Path) {
+                       continue
+               }
+               q.pathOnce(curM.Path, func() pathSet {
+                       if modload.HasModRoot() && curM == modload.Target {
+                               return errSet(&modload.QueryMatchesMainModuleError{Pattern: q.pattern, Query: q.version})
+                       }
+                       return pathSet{mod: module.Version{Path: curM.Path, Version: "none"}}
+               })
+       }
+}
+
+func (r *resolver) performLocalQueries(ctx context.Context) {
+       for _, q := range r.localQueries {
+               q.pathOnce(q.pattern, func() pathSet {
+                       absDetail := ""
+                       if !filepath.IsAbs(q.pattern) {
+                               if absPath, err := filepath.Abs(q.pattern); err == nil {
+                                       absDetail = fmt.Sprintf(" (%s)", absPath)
                                }
                        }
 
-                       if arg.path != arg.raw {
-                               base.Errorf("go get %s: can't request explicit version of path in main module", arg)
-                               continue
+                       // Absolute paths like C:\foo and relative paths like ../foo... are
+                       // restricted to matching packages in the main module.
+                       pkgPattern := modload.DirImportPath(q.pattern)
+                       if pkgPattern == "." {
+                               return errSet(fmt.Errorf("%s%s is not within module rooted at %s", q.pattern, absDetail, modload.ModRoot()))
                        }
 
-               case strings.Contains(path, "..."):
-                       // Wait until we load packages to look up modules.
-                       // We don't know yet whether any modules in the build list provide
-                       // packages matching the pattern. For example, suppose
-                       // golang.org/x/tools and golang.org/x/tools/playground are separate
-                       // modules, and only golang.org/x/tools is in the build list. If the
-                       // user runs 'go get golang.org/x/tools/playground/...', we should
-                       // add a requirement for golang.org/x/tools/playground. We should not
-                       // upgrade golang.org/x/tools.
+                       match := modload.MatchInModule(ctx, pkgPattern, modload.Target, imports.AnyTags())
+                       if len(match.Errs) > 0 {
+                               return pathSet{err: match.Errs[0]}
+                       }
 
-               case path == "all":
-                       // If there is no main module, "all" is not meaningful.
-                       if !modload.HasModRoot() {
-                               base.Errorf(`go get %s: cannot match "all": working directory is not part of a module`, arg)
+                       if len(match.Pkgs) == 0 {
+                               if !q.isWildcard() {
+                                       return errSet(fmt.Errorf("%s%s is not a package in module rooted at %s", q.pattern, absDetail, modload.ModRoot()))
+                               }
+                               search.WarnUnmatched([]*search.Match{match})
+                               return pathSet{}
                        }
-                       // Don't query modules until we load packages. We'll automatically
-                       // look up any missing modules.
 
-               case search.IsMetaPackage(path):
-                       base.Errorf("go get %s: explicit requirement on standard-library module %s not allowed", path, path)
+                       return pathSet{pkgMods: []module.Version{modload.Target}}
+               })
+       }
+}
+
+// performWildcardQueries populates the candidates for each query whose pattern
+// is a wildcard.
+//
+// The candidates for a given module path matching (or containing a package
+// matching) a wildcard query depend only on the initial build list, but the set
+// of modules may be expanded by other queries, so wildcard queries need to be
+// re-evaluated whenever a potentially-matching module path is added to the
+// build list.
+func (r *resolver) performWildcardQueries(ctx context.Context) {
+       for _, q := range r.wildcardQueries {
+               q := q
+               r.work.Add(func() {
+                       if q.version == "none" {
+                               r.queryNone(ctx, q)
+                       } else {
+                               r.queryWildcard(ctx, q)
+                       }
+               })
+       }
+       <-r.work.Idle()
+}
+
+// queryWildcard adds a candidate set to q for each module for which:
+//     - some version of the module is already in the build list, and
+//     - that module exists at some version matching q.version, and
+//     - either the module path itself matches q.pattern, or some package within
+//       the module at q.version matches q.pattern.
+func (r *resolver) queryWildcard(ctx context.Context, q *query) {
+       // For wildcard patterns, modload.QueryPattern only identifies modules
+       // matching the prefix of the path before the wildcard. However, the build
+       // list may already contain other modules with matching packages, and we
+       // should consider those modules to satisfy the query too.
+       // We want to match any packages in existing dependencies, but we only want to
+       // resolve new dependencies if nothing else turns up.
+       for _, curM := range r.buildList {
+               if !q.canMatchInModule(curM.Path) {
                        continue
+               }
+               q.pathOnce(curM.Path, func() pathSet {
+                       if _, hit := r.noneForPath(curM.Path); hit {
+                               // This module is being removed, so it will no longer be in the build list
+                               // (and thus will no longer match the pattern).
+                               return pathSet{}
+                       }
 
-               default:
-                       // The argument is a package or module path.
-                       if modload.HasModRoot() {
-                               if m := modload.TargetPackages(ctx, path); len(m.Pkgs) != 0 {
-                                       // The path is in the main module. Nothing to query.
-                                       if arg.vers != "upgrade" && arg.vers != "patch" {
-                                               base.Errorf("go get %s: can't request explicit version of path in main module", arg)
-                                       }
-                                       continue
+                       if curM.Path == modload.Target.Path && !versionOkForMainModule(q.version) {
+                               if q.matchesPath(curM.Path) {
+                                       return errSet(&modload.QueryMatchesMainModuleError{
+                                               Pattern: q.pattern,
+                                               Query:   q.version,
+                                       })
                                }
+
+                               packages, err := r.matchInModule(ctx, q.pattern, curM)
+                               if err != nil {
+                                       return errSet(err)
+                               }
+                               if len(packages) > 0 {
+                                       return errSet(&modload.QueryMatchesPackagesInMainModuleError{
+                                               Pattern:  q.pattern,
+                                               Query:    q.version,
+                                               Packages: packages,
+                                       })
+                               }
+
+                               return r.tryWildcard(ctx, q, curM)
                        }
 
-                       first := path
-                       if i := strings.IndexByte(first, '/'); i >= 0 {
-                               first = path
+                       m, err := r.queryModule(ctx, curM.Path, q.version, r.initialSelected)
+                       if err != nil {
+                               if !isNoSuchModuleVersion(err) {
+                                       // We can't tell whether a matching version exists.
+                                       return errSet(err)
+                               }
+                               // There is no version of curM.Path matching the query.
+
+                               // We haven't checked whether curM contains any matching packages at its
+                               // currently-selected version, or whether curM.Path itself matches q. If
+                               // either of those conditions holds, *and* no other query changes the
+                               // selected version of curM, then we will fail in checkWildcardVersions.
+                               // (This could be an error, but it's too soon to tell.)
+                               //
+                               // However, even then the transitive requirements of some other query
+                               // may downgrade this module out of the build list entirely, in which
+                               // case the pattern will no longer include it and it won't be an error.
+                               //
+                               // Either way, punt on the query rather than erroring out just yet.
+                               return pathSet{}
                        }
-                       if !strings.Contains(first, ".") {
-                               // The path doesn't have a dot in the first component and cannot be
-                               // queried as a module. It may be a package in the standard library,
-                               // which is fine, so don't report an error unless we encounter
-                               // a problem loading packages.
+
+                       return r.tryWildcard(ctx, q, m)
+               })
+       }
+
+       // Even if no modules matched, we shouldn't query for a new module to provide
+       // the pattern yet: some other query may yet induce a new requirement that
+       // will match the wildcard. Instead, we'll check in findMissingWildcards.
+}
+
+// tryWildcard returns a pathSet for module m matching query q.
+// If m does not actually match q, tryWildcard returns an empty pathSet.
+func (r *resolver) tryWildcard(ctx context.Context, q *query, m module.Version) pathSet {
+       mMatches := q.matchesPath(m.Path)
+       packages, err := r.matchInModule(ctx, q.pattern, m)
+       if err != nil {
+               return errSet(err)
+       }
+       if len(packages) > 0 {
+               return pathSet{pkgMods: []module.Version{m}}
+       }
+       if mMatches {
+               return pathSet{mod: m}
+       }
+       return pathSet{}
+}
+
+// findMissingWildcards adds a candidate set for each query in r.wildcardQueries
+// that has not yet resolved to any version containing packages.
+func (r *resolver) findMissingWildcards(ctx context.Context) {
+       for _, q := range r.wildcardQueries {
+               if q.version == "none" || q.matchesPackages {
+                       continue // q is not “missing”
+               }
+               r.work.Add(func() {
+                       q.pathOnce(q.pattern, func() pathSet {
+                               pkgMods, mod, err := r.queryPattern(ctx, q.pattern, q.version, r.initialSelected)
+                               if err != nil {
+                                       if isNoSuchPackageVersion(err) && len(q.resolved) > 0 {
+                                               // q already resolved one or more modules but matches no packages.
+                                               // That's ok: this pattern is just a module pattern, and we don't
+                                               // need to add any more modules to satisfy it.
+                                               return pathSet{}
+                                       }
+                                       return errSet(err)
+                               }
+
+                               return pathSet{pkgMods: pkgMods, mod: mod}
+                       })
+               })
+       }
+       <-r.work.Idle()
+}
+
+// checkWildcardVersions reports an error if any module in the build list has a
+// path (or contains a package) matching a query with a wildcard pattern, but
+// has a selected version that does *not* match the query.
+func (r *resolver) checkWildcardVersions(ctx context.Context) {
+       defer base.ExitIfErrors()
+
+       for _, q := range r.wildcardQueries {
+               for _, curM := range r.buildList {
+                       if !q.canMatchInModule(curM.Path) {
                                continue
                        }
+                       if !q.matchesPath(curM.Path) {
+                               packages, err := r.matchInModule(ctx, q.pattern, curM)
+                               if len(packages) == 0 {
+                                       if err != nil {
+                                               reportError(q, err)
+                                       }
+                                       continue // curM is not relevant to q.
+                               }
+                       }
 
-                       // If we're querying "upgrade" or "patch", we need to know the current
-                       // version of the module. For "upgrade", we want to avoid accidentally
-                       // downgrading from a newer prerelease. For "patch", we need to query
-                       // the correct minor version.
-                       // Here, we check if "path" is the name of a module in the build list
-                       // (other than the main module) and set prevM if so. If "path" isn't
-                       // a module in the build list, the current version doesn't matter
-                       // since it's either an unknown module or a package within a module
-                       // that we'll discover later.
-                       q := &query{querySpec: querySpec{path: arg.path, vers: arg.vers}, arg: arg.raw}
-                       if v, ok := selectedVersion[path]; ok {
-                               if path == modload.Target.Path {
-                                       // TODO(bcmills): This is held over from a previous version of the get
-                                       // implementation. Why was it a special case?
-                               } else {
-                                       q.prevM = module.Version{Path: path, Version: v}
-                                       q.forceModulePath = true
+                       rev, err := r.queryModule(ctx, curM.Path, q.version, r.initialSelected)
+                       if err != nil {
+                               reportError(q, err)
+                               continue
+                       }
+                       if rev.Version == curM.Version {
+                               continue // curM already matches q.
+                       }
+
+                       if !q.matchesPath(curM.Path) {
+                               m := module.Version{Path: curM.Path, Version: rev.Version}
+                               packages, err := r.matchInModule(ctx, q.pattern, m)
+                               if err != nil {
+                                       reportError(q, err)
+                                       continue
+                               }
+                               if len(packages) == 0 {
+                                       // curM at its original version contains a path matching q.pattern,
+                                       // but at rev.Version it does not, so (somewhat paradoxically) if
+                                       // we changed the version of curM it would no longer match the query.
+                                       var version interface{} = m
+                                       if rev.Version != q.version {
+                                               version = fmt.Sprintf("%s@%s (%s)", m.Path, q.version, m.Version)
+                                       }
+                                       reportError(q, fmt.Errorf("%v matches packages in %v but not %v: specify a different version for module %s", q, curM, version, m.Path))
+                                       continue
                                }
                        }
-                       queries = append(queries, q)
+
+                       // Since queryModule succeeded and either curM or one of the packages it
+                       // contains matches q.pattern, we should have either selected the version
+                       // of curM matching q, or reported a conflict error (and exited).
+                       // If we're still here and the version doesn't match,
+                       // something has gone very wrong.
+                       reportError(q, fmt.Errorf("internal error: selected %v instead of %v", curM, rev.Version))
                }
        }
+}
 
-       return queries
+// performPathQueries populates the candidates for each query whose pattern is
+// a path literal.
+//
+// The candidate packages and modules for path literals depend only on the
+// initial build list, not the current build list, so we only need to query path
+// literals once.
+func (r *resolver) performPathQueries(ctx context.Context) {
+       for _, q := range r.pathQueries {
+               q := q
+               r.work.Add(func() {
+                       if q.version == "none" {
+                               r.queryNone(ctx, q)
+                       } else {
+                               r.queryPath(ctx, q)
+                       }
+               })
+       }
+       <-r.work.Idle()
 }
 
-// runQueries looks up modules at target versions in parallel. Results will be
-// cached. If the same module is referenced by multiple queries at different
-// versions (including earlier queries in the modOnly map), an error will be
-// reported. A map from module paths to queries is returned, which includes
-// queries and modOnly.
-func runQueries(ctx context.Context, cache map[querySpec]*query, queries []*query, modOnly map[string]*query) map[string]*query {
+// queryPath adds a candidate set to q for the package with path q.pattern.
+// The candidate set consists of all modules that could provide q.pattern
+// and have a version matching q, plus (if it exists) the module whose path
+// is itself q.pattern (at a matching version).
+func (r *resolver) queryPath(ctx context.Context, q *query) {
+       q.pathOnce(q.pattern, func() pathSet {
+               if search.IsMetaPackage(q.pattern) || q.isWildcard() {
+                       panic(fmt.Sprintf("internal error: queryPath called with pattern %q", q.pattern))
+               }
+               if q.version == "none" {
+                       panic(`internal error: queryPath called with version "none"`)
+               }
 
-       runQuery := func(q *query) {
-               if q.vers == "none" {
-                       // Wait for downgrade step.
-                       q.m = module.Version{Path: q.path, Version: "none"}
-                       return
+               if search.IsStandardImportPath(q.pattern) {
+                       stdOnly := module.Version{}
+                       packages, _ := r.matchInModule(ctx, q.pattern, stdOnly)
+                       if len(packages) > 0 {
+                               if q.rawVersion != "" {
+                                       return errSet(fmt.Errorf("can't request explicit version %q of standard library package %s", q.version, q.pattern))
+                               }
+
+                               q.matchesPackages = true
+                               return pathSet{} // No module needed for standard library.
+                       }
                }
-               m, err := getQuery(ctx, q.path, q.vers, q.prevM, q.forceModulePath)
+
+               pkgMods, mod, err := r.queryPattern(ctx, q.pattern, q.version, r.initialSelected)
                if err != nil {
-                       base.Errorf("go get %s: %v", q.arg, err)
+                       return errSet(err)
                }
-               q.m = m
+               return pathSet{pkgMods: pkgMods, mod: mod}
+       })
+}
+
+// performPatternAllQueries populates the candidates for each query whose
+// pattern is "all".
+//
+// The candidate modules for a given package in "all" depend only on the initial
+// build list, but we cannot follow the dependencies of a given package until we
+// know which candidate is selected — and that selection may depend on the
+// results of other queries. We need to re-evaluate the "all" queries whenever
+// the module for one or more packages in "all" are resolved.
+func (r *resolver) performPatternAllQueries(ctx context.Context) {
+       if len(r.patternAllQueries) == 0 {
+               return
        }
 
-       type token struct{}
-       sem := make(chan token, runtime.GOMAXPROCS(0))
-       for _, q := range queries {
-               if cached := cache[q.querySpec]; cached != nil {
-                       *q = *cached
-               } else {
-                       sem <- token{}
-                       go func(q *query) {
-                               runQuery(q)
-                               <-sem
-                       }(q)
+       findPackage := func(ctx context.Context, path string, m module.Version) (versionOk bool) {
+               versionOk = true
+               for _, q := range r.patternAllQueries {
+                       q.pathOnce(path, func() pathSet {
+                               pkgMods, err := r.queryPackages(ctx, path, q.version, r.initialSelected)
+                               if len(pkgMods) != 1 || pkgMods[0] != m {
+                                       // There are candidates other than m for the given path, so we can't
+                                       // be certain that m will actually be the module selected to provide
+                                       // the package. Don't load its dependencies just yet, because they
+                                       // might no longer be dependencies after we resolve the correct
+                                       // version.
+                                       versionOk = false
+                               }
+                               return pathSet{pkgMods: pkgMods, err: err}
+                       })
                }
+               return versionOk
        }
 
-       // Fill semaphore channel to wait for goroutines to finish.
-       for n := cap(sem); n > 0; n-- {
-               sem <- token{}
+       r.loadPackages(ctx, []string{"all"}, findPackage)
+
+       // Since we built up the candidate lists concurrently, they may be in a
+       // nondeterministic order. We want 'go get' to be fully deterministic,
+       // including in which errors it chooses to report, so sort the candidates
+       // into a deterministic-but-arbitrary order.
+       for _, q := range r.patternAllQueries {
+               sort.Slice(q.candidates, func(i, j int) bool {
+                       return q.candidates[i].path < q.candidates[j].path
+               })
        }
+}
 
-       // Add to cache after concurrent section to avoid races...
+// findAndUpgradeImports returns a pathSet for each package that is not yet
+// in the build list but is transitively imported by the packages matching the
+// given queries (which must already have been resolved).
+//
+// If the getU flag ("-u") is set, findAndUpgradeImports also returns a
+// pathSet for each module that is not constrained by any other
+// command-line argument and has an available matching upgrade.
+func (r *resolver) findAndUpgradeImports(ctx context.Context, queries []*query) (upgrades []pathSet) {
+       patterns := make([]string, 0, len(queries))
        for _, q := range queries {
-               cache[q.querySpec] = q
+               if q.matchesPackages {
+                       patterns = append(patterns, q.pattern)
+               }
+       }
+       if len(patterns) == 0 {
+               return nil
        }
 
-       base.ExitIfErrors()
+       // mu guards concurrent writes to upgrades, which will be sorted
+       // (to restore determinism) after loading.
+       var mu sync.Mutex
 
-       byPath := make(map[string]*query)
-       check := func(q *query) {
-               if prev, ok := byPath[q.m.Path]; prev != nil && prev.m != q.m {
-                       base.Errorf("go get: conflicting versions for module %s: %s and %s", q.m.Path, prev.m.Version, q.m.Version)
-                       byPath[q.m.Path] = nil // sentinel to stop errors
-                       return
-               } else if !ok {
-                       byPath[q.m.Path] = q
+       findPackage := func(ctx context.Context, path string, m module.Version) (versionOk bool) {
+               version := "latest"
+               if m.Path != "" {
+                       if getU.version == "" {
+                               // The user did not request that we upgrade transitive dependencies.
+                               return true
+                       }
+                       if _, ok := r.resolvedVersion[m.Path]; ok {
+                               // We cannot upgrade m implicitly because its version is determined by
+                               // an explicit pattern argument.
+                               return true
+                       }
+                       version = getU.version
+               }
+
+               // Unlike other queries, the "-u" flag upgrades relative to the build list
+               // after applying changes so far, not the initial build list.
+               // This is for two reasons:
+               //
+               //      - The "-u" flag intentionally applies to transitive dependencies,
+               //        which may not be known or even resolved in advance of applying
+               //        other version changes.
+               //
+               //      - The "-u" flag, unlike other arguments, does not cause version
+               //        conflicts with other queries. (The other query always wins.)
+
+               pkgMods, err := r.queryPackages(ctx, path, version, r.selected)
+               for _, u := range pkgMods {
+                       if u == m {
+                               // The selected package version is already upgraded appropriately; there
+                               // is no need to change it.
+                               return true
+                       }
+               }
+
+               if err != nil {
+                       if isNoSuchPackageVersion(err) || (m.Path == "" && module.CheckPath(path) != nil) {
+                               // We can't find the package because it doesn't — or can't — even exist
+                               // in any module at the latest version. (Note that invalid module paths
+                               // could in general exist due to replacements, so we at least need to
+                               // run the query to check those.)
+                               //
+                               // There is no version change we can make to fix the package, so leave
+                               // it unresolved. Either some other query (perhaps a wildcard matching a
+                               // newly-added dependency for some other missing package) will fill in
+                               // the gaps, or we will report an error (with a better import stack) in
+                               // the final LoadPackages call.
+                               return true
+                       }
                }
+
+               mu.Lock()
+               upgrades = append(upgrades, pathSet{pkgMods: pkgMods, err: err})
+               mu.Unlock()
+               return false
        }
-       for _, q := range queries {
-               check(q)
+
+       r.loadPackages(ctx, patterns, findPackage)
+
+       // Since we built up the candidate lists concurrently, they may be in a
+       // nondeterministic order. We want 'go get' to be fully deterministic,
+       // including in which errors it chooses to report, so sort the candidates
+       // into a deterministic-but-arbitrary order.
+       sort.Slice(upgrades, func(i, j int) bool {
+               return upgrades[i].path < upgrades[j].path
+       })
+       return upgrades
+}
+
+// loadPackages loads the packages matching the given patterns, invoking the
+// findPackage function for each package that may require a change to the
+// build list.
+//
+// loadPackages invokes the findPackage function for each package loaded from a
+// module outside the main module. If the module or version that supplies that
+// package needs to be changed due to a query, findPackage may return false
+// and the imports of that package will not be loaded.
+//
+// loadPackages also invokes the findPackage function for each imported package
+// that is neither present in the standard library nor in any module in the
+// build list.
+func (r *resolver) loadPackages(ctx context.Context, patterns []string, findPackage func(ctx context.Context, path string, m module.Version) (versionOk bool)) {
+       opts := modload.PackageOpts{
+               Tags:          imports.AnyTags(),
+               LoadTests:     *getT,
+               SilenceErrors: true, // May be fixed by subsequent upgrades or downgrades.
        }
-       for _, q := range modOnly {
-               check(q)
+
+       opts.AllowPackage = func(ctx context.Context, path string, m module.Version) error {
+               if m.Path == "" || m == modload.Target {
+                       // Packages in the standard library and main module are already at their
+                       // latest (and only) available versions.
+                       return nil
+               }
+               if ok := findPackage(ctx, path, m); !ok {
+                       return errVersionChange
+               }
+               return nil
        }
-       base.ExitIfErrors()
 
-       return byPath
+       _, pkgs := modload.LoadPackages(ctx, opts, patterns...)
+       for _, path := range pkgs {
+               const (
+                       parentPath  = ""
+                       parentIsStd = false
+               )
+               _, _, err := modload.Lookup(parentPath, parentIsStd, path)
+               if err == nil {
+                       continue
+               }
+               if errors.Is(err, errVersionChange) {
+                       // We already added candidates during loading.
+                       continue
+               }
+
+               var (
+                       importMissing *modload.ImportMissingError
+                       ambiguous     *modload.AmbiguousImportError
+               )
+               if !errors.As(err, &importMissing) && !errors.As(err, &ambiguous) {
+                       // The package, which is a dependency of something we care about, has some
+                       // problem that we can't resolve with a version change.
+                       // Leave the error for the final LoadPackages call.
+                       continue
+               }
+
+               path := path
+               r.work.Add(func() {
+                       findPackage(ctx, path, module.Version{})
+               })
+       }
+       <-r.work.Idle()
 }
 
-// getQuery evaluates the given (package or module) path and version
-// to determine the underlying module version being requested.
-// If forceModulePath is set, getQuery must interpret path
-// as a module path.
-func getQuery(ctx context.Context, path, vers string, prevM module.Version, forceModulePath bool) (module.Version, error) {
-       if (prevM.Version != "") != forceModulePath {
-               // We resolve package patterns by calling QueryPattern, which does not
-               // accept a previous version and therefore cannot take it into account for
-               // the "latest" or "patch" queries.
-               // If we are resolving a package path or pattern, the caller has already
-               // resolved any existing packages to their containing module(s), and
-               // will set both prevM.Version and forceModulePath for those modules.
-               // The only remaining package patterns are those that are not already
-               // provided by the build list, which are indicated by
-               // an empty prevM.Version.
-               base.Fatalf("go get: internal error: prevM may be set if and only if forceModulePath is set")
+// errVersionChange is a sentinel error indicating that a module's version needs
+// to be updated before its dependencies can be loaded.
+var errVersionChange = errors.New("version change needed")
+
+// resolveCandidates resolves candidates sets that are attached to the given
+// queries and/or needed to provide the given missing-package dependencies.
+//
+// resolveCandidates starts by resolving one module version from each
+// unambiguous pathSet attached to the given queries.
+//
+// If no unambiguous query results in a change to the build list,
+// resolveCandidates modifies the build list by adding one module version from
+// each pathSet in missing, but does not mark those versions as resolved
+// (so they can still be modified by other queries).
+//
+// If that still does not result in any changes to the build list,
+// resolveCandidates revisits the ambiguous query candidates and resolves them
+// arbitrarily in order to guarantee forward progress.
+//
+// If all pathSets are resolved without any changes to the build list,
+// resolveCandidates returns with changed=false.
+func (r *resolver) resolveCandidates(ctx context.Context, queries []*query, upgrades []pathSet) (changed bool) {
+       defer base.ExitIfErrors()
+
+       // Note: this is O(N²) with the number of pathSets in the worst case.
+       //
+       // We could perhaps get it down to O(N) if we were to index the pathSets
+       // by module path, so that we only revisit a given pathSet when the
+       // version of some module in its containingPackage list has been determined.
+       //
+       // However, N tends to be small, and most candidate sets will include only one
+       // candidate module (so they will be resolved in the first iteration), so for
+       // now we'll stick to the simple O(N²) approach.
+
+       resolved := 0
+       for {
+               prevResolved := resolved
+
+               for _, q := range queries {
+                       unresolved := q.candidates[:0]
+
+                       for _, cs := range q.candidates {
+                               if cs.err != nil {
+                                       reportError(q, cs.err)
+                                       resolved++
+                                       continue
+                               }
+
+                               filtered, isPackage, m, unique := r.disambiguate(cs)
+                               if !unique {
+                                       unresolved = append(unresolved, filtered)
+                                       continue
+                               }
+
+                               if m.Path == "" {
+                                       // The query is not viable. Choose an arbitrary candidate from
+                                       // before filtering and “resolve” it to report a conflict.
+                                       isPackage, m = r.chooseArbitrarily(cs)
+                               }
+                               if isPackage {
+                                       q.matchesPackages = true
+                               }
+                               r.resolve(q, m)
+                               resolved++
+                       }
+
+                       q.candidates = unresolved
+               }
+
+               base.ExitIfErrors()
+               if resolved == prevResolved {
+                       break // No unambiguous candidate remains.
+               }
        }
 
-       // If vers is a query like "latest", we should ignore retracted and excluded
-       // versions. If vers refers to a specific version or commit like "v1.0.0"
-       // or "master", we should only ignore excluded versions.
-       allowed := modload.CheckAllowed
-       if modload.IsRevisionQuery(vers) {
-               allowed = modload.CheckExclusions
-       } else if vers == "upgrade" || vers == "patch" {
-               allowed = checkAllowedOrCurrent(prevM.Version)
+       if changed := r.updateBuildList(ctx, nil); changed {
+               // The build list has changed, so disregard any missing packages: they might
+               // now be determined by requirements in the build list, which we would
+               // prefer to use instead of arbitrary "latest" versions.
+               return true
        }
 
-       // If the query must be a module path, try only that module path.
-       if forceModulePath {
-               if path == modload.Target.Path {
-                       if vers != "latest" {
-                               return module.Version{}, fmt.Errorf("can't get a specific version of the main module")
-                       }
+       // Arbitrarily add a "latest" version that provides each missing package, but
+       // do not mark the version as resolved: we still want to allow the explicit
+       // queries to modify the resulting versions.
+       var tentative []module.Version
+       for _, cs := range upgrades {
+               if cs.err != nil {
+                       base.Errorf("go get: %v", cs.err)
+                       continue
                }
 
-               info, err := modload.Query(ctx, path, vers, prevM.Version, allowed)
-               if err == nil {
-                       if info.Version != vers && info.Version != prevM.Version {
-                               logOncef("go: %s %s => %s", path, vers, info.Version)
+               filtered, _, m, unique := r.disambiguate(cs)
+               if !unique {
+                       _, m = r.chooseArbitrarily(filtered)
+               }
+               if m.Path == "" {
+                       // There is no viable candidate for the missing package.
+                       // Leave it unresolved.
+                       continue
+               }
+               tentative = append(tentative, m)
+       }
+       base.ExitIfErrors()
+       if changed := r.updateBuildList(ctx, tentative); changed {
+               return true
+       }
+
+       // The build list will be the same on the next iteration as it was on this
+       // iteration, so any ambiguous queries will remain so. In order to make
+       // progress, resolve them arbitrarily but deterministically.
+       //
+       // If that results in conflicting versions, the user can re-run 'go get'
+       // with additional explicit versions for the conflicting packages or
+       // modules.
+       for _, q := range queries {
+               for _, cs := range q.candidates {
+                       isPackage, m := r.chooseArbitrarily(cs)
+                       if isPackage {
+                               q.matchesPackages = true
                        }
-                       return module.Version{Path: path, Version: info.Version}, nil
+                       r.resolve(q, m)
+               }
+       }
+       return r.updateBuildList(ctx, nil)
+}
+
+// disambiguate eliminates candidates from cs that conflict with other module
+// versions that have already been resolved. If there is only one (unique)
+// remaining candidate, disambiguate returns that candidate, along with
+// an indication of whether that result interprets cs.path as a package
+//
+// Note: we're only doing very simple disambiguation here. The goal is to
+// reproduce the user's intent, not to find a solution that a human couldn't.
+// In the vast majority of cases, we expect only one module per pathSet,
+// but we want to give some minimal additional tools so that users can add an
+// extra argument or two on the command line to resolve simple ambiguities.
+func (r *resolver) disambiguate(cs pathSet) (filtered pathSet, isPackage bool, m module.Version, unique bool) {
+       if len(cs.pkgMods) == 0 && cs.mod.Path == "" {
+               panic("internal error: resolveIfUnambiguous called with empty pathSet")
+       }
+
+       for _, m := range cs.pkgMods {
+               if _, ok := r.noneForPath(m.Path); ok {
+                       // A query with version "none" forces the candidate module to version
+                       // "none", so we cannot use any other version for that module.
+                       continue
                }
 
-               // If the query was "upgrade" or "patch" and the current version has been
-               // replaced, check to see whether the error was for that same version:
-               // if so, the version was probably replaced because it is invalid,
-               // and we should keep that replacement without complaining.
-               if vers == "upgrade" || vers == "patch" {
-                       var vErr *module.InvalidVersionError
-                       if errors.As(err, &vErr) && vErr.Version == prevM.Version && modload.Replacement(prevM).Path != "" {
-                               return prevM, nil
+               if m.Path == modload.Target.Path {
+                       if m.Version == modload.Target.Version {
+                               return pathSet{}, true, m, true
                        }
+                       // The main module can only be set to its own version.
+                       continue
                }
 
-               return module.Version{}, err
+               vr, ok := r.resolvedVersion[m.Path]
+               if !ok {
+                       // m is a viable answer to the query, but other answers may also
+                       // still be viable.
+                       filtered.pkgMods = append(filtered.pkgMods, m)
+                       continue
+               }
+
+               if vr.version != m.Version {
+                       // Some query forces the candidate module to a version other than this
+                       // one.
+                       //
+                       // The command could be something like
+                       //
+                       //      go get example.com/foo/bar@none example.com/foo/bar/baz@latest
+                       //
+                       // in which case we *cannot* resolve the package from
+                       // example.com/foo/bar (because it is constrained to version
+                       // "none") and must fall through to module example.com/foo@latest.
+                       continue
+               }
+
+               // Some query forces the candidate module *to* the candidate version.
+               // As a result, this candidate is the only viable choice to provide
+               // its package(s): any other choice would result in an ambiguous import
+               // for this path.
+               //
+               // For example, consider the command
+               //
+               //      go get example.com/foo@latest example.com/foo/bar/baz@latest
+               //
+               // If modules example.com/foo and example.com/foo/bar both provide
+               // package example.com/foo/bar/baz, then we *must* resolve the package
+               // from example.com/foo: if we instead resolved it from
+               // example.com/foo/bar, we would have two copies of the package.
+               return pathSet{}, true, m, true
        }
 
-       // If the query may be either a package or a module, try it as a package path.
-       // If it turns out to only exist as a module, we can detect the resulting
-       // PackageNotInModuleError and avoid a second round-trip through (potentially)
-       // all of the configured proxies.
-       results, modOnly, err := modload.QueryPattern(ctx, path, vers, modload.Selected, allowed)
-       if err != nil {
-               return module.Version{}, err
+       if cs.mod.Path != "" {
+               vr, ok := r.resolvedVersion[cs.mod.Path]
+               if !ok || vr.version == cs.mod.Version {
+                       filtered.mod = cs.mod
+               }
        }
-       if len(results) == 0 {
-               // The path doesn't contain a wildcard, but was actually a
-               // module path instead. Return that.
-               return modOnly.Mod, nil
+
+       if len(filtered.pkgMods) == 1 &&
+               (filtered.mod.Path == "" || filtered.mod == filtered.pkgMods[0]) {
+               // Exactly one viable module contains the package with the given path
+               // (by far the common case), so we can resolve it unambiguously.
+               return pathSet{}, true, filtered.pkgMods[0], true
        }
 
-       m := results[0].Mod
-       if m.Path != path {
-               logOncef("go: found %s in %s %s", path, m.Path, m.Version)
-       } else if m.Version != vers {
-               logOncef("go: %s %s => %s", path, vers, m.Version)
+       if len(filtered.pkgMods) == 0 {
+               // All modules that could provide the path as a package conflict with other
+               // resolved arguments. If it can refer to a module instead, return that;
+               // otherwise, this pathSet cannot be resolved (and we will return the
+               // zero module.Version).
+               return pathSet{}, false, filtered.mod, true
        }
-       return m, nil
+
+       // The query remains ambiguous: there are at least two different modules
+       // to which cs.path could refer.
+       return filtered, false, module.Version{}, false
 }
 
-// reportRetractions prints warnings if any modules in the build list are
-// retracted.
-func reportRetractions(ctx context.Context) {
-       // Query for retractions of modules in the build list.
-       // Use modload.ListModules, since that provides information in the same format
-       // as 'go list -m'. Don't query for "all", since that's not allowed outside a
-       // module.
-       buildList := modload.LoadedModules()
-       args := make([]string, 0, len(buildList))
-       for _, m := range buildList {
-               if m.Version == "" {
-                       // main module or dummy target module
-                       continue
+// chooseArbitrarily returns an arbitrary (but deterministic) module version
+// from among those in the given set.
+//
+// chooseArbitrarily prefers module paths that were already in the build list at
+// the start of 'go get', prefers modules that provide packages over those that
+// do not, and chooses the first module meeting those criteria (so biases toward
+// longer paths).
+func (r *resolver) chooseArbitrarily(cs pathSet) (isPackage bool, m module.Version) {
+       // Prefer to upgrade some module that was already in the build list.
+       for _, m := range cs.pkgMods {
+               if r.initialSelected(m.Path) != "none" {
+                       return true, m
                }
-               args = append(args, m.Path+"@"+m.Version)
        }
-       listU := false
-       listVersions := false
-       listRetractions := true
-       mods := modload.ListModules(ctx, args, listU, listVersions, listRetractions)
-       retractPath := ""
-       for _, mod := range mods {
-               if len(mod.Retracted) > 0 {
-                       if retractPath == "" {
-                               retractPath = mod.Path
-                       } else {
-                               retractPath = "<module>"
+
+       // Otherwise, arbitrarily choose the first module that provides the package.
+       if len(cs.pkgMods) > 0 {
+               return true, cs.pkgMods[0]
+       }
+
+       return false, cs.mod
+}
+
+// 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
+                               }
+
+                               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)
                        }
-                       rationale := modload.ShortRetractionRationale(mod.Retracted[0])
-                       logOncef("go: warning: %s@%s is retracted: %s", mod.Path, mod.Version, rationale)
                }
        }
-       if modload.HasModRoot() && retractPath != "" {
-               logOncef("go: run 'go get %s@latest' to switch to the latest unretracted version", retractPath)
-       }
+
+       // TODO(#33284): Also print relevant upgrades.
 }
 
-var loggedLines sync.Map
+// resolve records that module m must be at its indicated version (which may be
+// "none") due to query q. If some other query forces module m to be at a
+// different version, resolve reports a conflict error.
+func (r *resolver) resolve(q *query, m module.Version) {
+       if m.Path == "" {
+               panic("internal error: resolving a module.Version with an empty path")
+       }
+
+       if m.Path == modload.Target.Path && m.Version != modload.Target.Version {
+               reportError(q, &modload.QueryMatchesMainModuleError{
+                       Pattern: q.pattern,
+                       Query:   q.version,
+               })
+               return
+       }
 
-func logOncef(format string, args ...interface{}) {
-       msg := fmt.Sprintf(format, args...)
-       if _, dup := loggedLines.LoadOrStore(msg, true); !dup {
-               fmt.Fprintln(os.Stderr, msg)
+       vr, ok := r.resolvedVersion[m.Path]
+       if ok && vr.version != m.Version {
+               reportConflict(q, m, vr)
+               return
        }
+       r.resolvedVersion[m.Path] = versionReason{m.Version, q}
+       q.resolved = append(q.resolved, m)
 }
 
-// checkAllowedOrCurrent is like modload.CheckAllowed, but always allows the
-// current version (even if it is retracted or otherwise excluded).
-func checkAllowedOrCurrent(current string) modload.AllowedFunc {
-       if current == "" {
-               return modload.CheckAllowed
+// updateBuildList updates the module loader's global build list to be
+// consistent with r.resolvedVersion, and to include additional modules
+// provided that they do not conflict with the resolved versions.
+//
+// If the additional modules conflict with the resolved versions, they will be
+// downgraded to a non-conflicting version (possibly "none").
+func (r *resolver) updateBuildList(ctx context.Context, additions []module.Version) (changed bool) {
+       if len(additions) == 0 && len(r.resolvedVersion) == r.buildListResolvedVersions {
+               return false
        }
 
-       return func(ctx context.Context, m module.Version) error {
-               if m.Version == current {
-                       return nil
+       defer base.ExitIfErrors()
+
+       resolved := make([]module.Version, 0, len(r.resolvedVersion))
+       for mPath, rv := range r.resolvedVersion {
+               if mPath != modload.Target.Path {
+                       resolved = append(resolved, module.Version{Path: mPath, Version: rv.version})
                }
-               return modload.CheckAllowed(ctx, m)
        }
+
+       if err := modload.EditBuildList(ctx, additions, resolved); err != nil {
+               var constraint *modload.ConstraintError
+               if !errors.As(err, &constraint) {
+                       base.Errorf("go get: %v", err)
+                       return false
+               }
+
+               reason := func(m module.Version) string {
+                       rv, ok := r.resolvedVersion[m.Path]
+                       if !ok {
+                               panic(fmt.Sprintf("internal error: can't find reason for requirement on %v", m))
+                       }
+                       return rv.reason.ResolvedString(module.Version{Path: m.Path, Version: rv.version})
+               }
+               for _, c := range constraint.Conflicts {
+                       base.Errorf("go get: %v requires %v, not %v", reason(c.Source), c.Dep, reason(c.Constraint))
+               }
+               return false
+       }
+
+       buildList := modload.LoadAllModules(ctx)
+       r.buildListResolvedVersions = len(r.resolvedVersion)
+       if reflect.DeepEqual(r.buildList, buildList) {
+               return false
+       }
+       r.buildList = buildList
+       r.buildListVersion = make(map[string]string, len(r.buildList))
+       for _, m := range r.buildList {
+               r.buildListVersion[m.Path] = m.Version
+       }
+       return true
+}
+
+// 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.
+func isNoSuchModuleVersion(err error) bool {
+       var noMatch *modload.NoMatchingVersionError
+       return errors.Is(err, os.ErrNotExist) || errors.As(err, &noMatch)
+}
+
+// isNoSuchPackageVersion reports whether err indicates that the requested
+// package does not exist at the requested version, either because no module
+// that could contain it exists at that version, or because every such module
+// that does exist does not actually contain the package.
+func isNoSuchPackageVersion(err error) bool {
+       var noPackage *modload.PackageNotInModuleError
+       return isNoSuchModuleVersion(err) || errors.As(err, &noPackage)
 }
diff --git a/src/cmd/go/internal/modget/mvs.go b/src/cmd/go/internal/modget/mvs.go
deleted file mode 100644 (file)
index e7e0ec8..0000000
+++ /dev/null
@@ -1,202 +0,0 @@
-// Copyright 2020 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package modget
-
-import (
-       "context"
-       "errors"
-
-       "cmd/go/internal/base"
-       "cmd/go/internal/modload"
-       "cmd/go/internal/mvs"
-
-       "golang.org/x/mod/module"
-)
-
-// An upgrader adapts an underlying mvs.Reqs to apply an
-// upgrade policy to a list of targets and their dependencies.
-type upgrader struct {
-       mvs.Reqs
-
-       // cmdline maps a module path to a query made for that module at a
-       // specific target version. Each query corresponds to a module
-       // matched by a command line argument.
-       cmdline map[string]*query
-
-       // upgrade is a set of modules providing dependencies of packages
-       // matched by command line arguments. If -u or -u=patch is set,
-       // these modules are upgraded accordingly.
-       upgrade map[string]bool
-}
-
-// newUpgrader creates an upgrader. cmdline contains queries made at
-// specific versions for modules matched by command line arguments. pkgs
-// is the set of packages matched by command line arguments. If -u or -u=patch
-// is set, modules providing dependencies of pkgs are upgraded accordingly.
-func newUpgrader(cmdline map[string]*query, pkgs map[string]bool) *upgrader {
-       u := &upgrader{
-               Reqs:    modload.Reqs(),
-               cmdline: cmdline,
-       }
-       if getU != "" {
-               u.upgrade = make(map[string]bool)
-
-               // Traverse package import graph.
-               // Initialize work queue with root packages.
-               seen := make(map[string]bool)
-               var work []string
-               add := func(path string) {
-                       if !seen[path] {
-                               seen[path] = true
-                               work = append(work, path)
-                       }
-               }
-               for pkg := range pkgs {
-                       add(pkg)
-               }
-               for len(work) > 0 {
-                       pkg := work[0]
-                       work = work[1:]
-                       m := modload.PackageModule(pkg)
-                       u.upgrade[m.Path] = true
-
-                       // testImports is empty unless test imports were actually loaded,
-                       // i.e., -t was set or "all" was one of the arguments.
-                       imports, testImports := modload.PackageImports(pkg)
-                       for _, imp := range imports {
-                               add(imp)
-                       }
-                       for _, imp := range testImports {
-                               add(imp)
-                       }
-               }
-       }
-       return u
-}
-
-// Required returns the requirement list for m.
-// For the main module, we override requirements with the modules named
-// one the command line, and we include new requirements. Otherwise,
-// we defer to u.Reqs.
-func (u *upgrader) Required(m module.Version) ([]module.Version, error) {
-       rs, err := u.Reqs.Required(m)
-       if err != nil {
-               return nil, err
-       }
-       if m != modload.Target {
-               return rs, nil
-       }
-
-       overridden := make(map[string]bool)
-       for i, m := range rs {
-               if q := u.cmdline[m.Path]; q != nil && q.m.Version != "none" {
-                       rs[i] = q.m
-                       overridden[q.m.Path] = true
-               }
-       }
-       for _, q := range u.cmdline {
-               if !overridden[q.m.Path] && q.m.Path != modload.Target.Path && q.m.Version != "none" {
-                       rs = append(rs, q.m)
-               }
-       }
-       return rs, nil
-}
-
-// Upgrade returns the desired upgrade for m.
-//
-// If m was requested at a specific version on the command line, then
-// Upgrade returns that version.
-//
-// If -u is set and m provides a dependency of a package matched by
-// command line arguments, then Upgrade may provider a newer tagged version.
-// If m is a tagged version, then Upgrade will return the latest tagged
-// version (with the same minor version number if -u=patch).
-// If m is a pseudo-version, then Upgrade returns the latest tagged version
-// only if that version has a time-stamp newer than m. This special case
-// prevents accidental downgrades when already using a pseudo-version
-// newer than the latest tagged version.
-//
-// If none of the above cases apply, then Upgrade returns m.
-func (u *upgrader) Upgrade(m module.Version) (module.Version, error) {
-       // Allow pkg@vers on the command line to override the upgrade choice v.
-       // If q's version is < m.Version, then we're going to downgrade anyway,
-       // and it's cleaner to avoid moving back and forth and picking up
-       // extraneous other newer dependencies.
-       // If q's version is > m.Version, then we're going to upgrade past
-       // m.Version anyway, and again it's cleaner to avoid moving back and forth
-       // picking up extraneous other newer dependencies.
-       if q := u.cmdline[m.Path]; q != nil {
-               return q.m, nil
-       }
-
-       if !u.upgrade[m.Path] {
-               // Not involved in upgrade. Leave alone.
-               return m, nil
-       }
-
-       // Run query required by upgrade semantics.
-       // Note that Query "latest" is not the same as using repo.Latest,
-       // which may return a pseudoversion for the latest commit.
-       // Query "latest" returns the newest tagged version or the newest
-       // prerelease version if there are no non-prereleases, or repo.Latest
-       // if there aren't any tagged versions.
-       // If we're querying "upgrade" or "patch", Query will compare the current
-       // version against the chosen version and will return the current version
-       // if it is newer.
-       info, err := modload.Query(context.TODO(), m.Path, string(getU), m.Version, checkAllowedOrCurrent(m.Version))
-       if err != nil {
-               // Report error but return m, to let version selection continue.
-               // (Reporting the error will fail the command at the next base.ExitIfErrors.)
-
-               // Special case: if the error is for m.Version itself and m.Version has a
-               // replacement, then keep it and don't report the error: the fact that the
-               // version is invalid is likely the reason it was replaced to begin with.
-               var vErr *module.InvalidVersionError
-               if errors.As(err, &vErr) && vErr.Version == m.Version && modload.Replacement(m).Path != "" {
-                       return m, nil
-               }
-
-               // Special case: if the error is "no matching versions" then don't
-               // even report the error. Because Query does not consider pseudo-versions,
-               // it may happen that we have a pseudo-version but during -u=patch
-               // the query v0.0 matches no versions (not even the one we're using).
-               var noMatch *modload.NoMatchingVersionError
-               if !errors.As(err, &noMatch) {
-                       base.Errorf("go get: upgrading %s@%s: %v", m.Path, m.Version, err)
-               }
-               return m, nil
-       }
-
-       if info.Version != m.Version {
-               logOncef("go: %s %s => %s", m.Path, getU, info.Version)
-       }
-       return module.Version{Path: m.Path, Version: info.Version}, nil
-}
-
-// buildListForLostUpgrade returns the build list for the module graph
-// rooted at lost. Unlike mvs.BuildList, the target module (lost) is not
-// treated specially. The returned build list may contain a newer version
-// of lost.
-//
-// buildListForLostUpgrade is used after a downgrade has removed a module
-// requested at a specific version. This helps us understand the requirements
-// implied by each downgrade.
-func buildListForLostUpgrade(lost module.Version, reqs mvs.Reqs) ([]module.Version, error) {
-       return mvs.BuildList(lostUpgradeRoot, &lostUpgradeReqs{Reqs: reqs, lost: lost})
-}
-
-var lostUpgradeRoot = module.Version{Path: "lost-upgrade-root", Version: ""}
-
-type lostUpgradeReqs struct {
-       mvs.Reqs
-       lost module.Version
-}
-
-func (r *lostUpgradeReqs) Required(mod module.Version) ([]module.Version, error) {
-       if mod == lostUpgradeRoot {
-               return []module.Version{r.lost}, nil
-       }
-       return r.Reqs.Required(mod)
-}
diff --git a/src/cmd/go/internal/modget/query.go b/src/cmd/go/internal/modget/query.go
new file mode 100644 (file)
index 0000000..53b60cc
--- /dev/null
@@ -0,0 +1,353 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package modget
+
+import (
+       "fmt"
+       "path/filepath"
+       "regexp"
+       "strings"
+       "sync"
+
+       "cmd/go/internal/base"
+       "cmd/go/internal/modload"
+       "cmd/go/internal/search"
+       "cmd/go/internal/str"
+
+       "golang.org/x/mod/module"
+)
+
+// A query describes a command-line argument and the modules and/or packages
+// to which that argument may resolve..
+type query struct {
+       // raw is the original argument, to be printed in error messages.
+       raw string
+
+       // rawVersion is the portion of raw corresponding to version, if any
+       rawVersion string
+
+       // pattern is the part of the argument before "@" (or the whole argument
+       // if there is no "@"), which may match either packages (preferred) or
+       // modules (if no matching packages).
+       //
+       // The pattern may also be "-u", for the synthetic query representing the -u
+       // (“upgrade”)flag.
+       pattern string
+
+       // patternIsLocal indicates whether pattern is restricted to match only paths
+       // local to the main module, such as absolute filesystem paths or paths
+       // beginning with './'.
+       //
+       // A local pattern must resolve to one or more packages in the main module.
+       patternIsLocal bool
+
+       // version is the part of the argument after "@", or an implied
+       // "upgrade" or "patch" if there is no "@". version specifies the
+       // module version to get.
+       version string
+
+       // matchWildcard, if non-nil, reports whether pattern, which must be a
+       // wildcard (with the substring "..."), matches the given package or module
+       // path.
+       matchWildcard func(path string) bool
+
+       // canMatchWildcard, if non-nil, reports whether the module with the given
+       // path could lexically contain a package matching pattern, which must be a
+       // wildcard.
+       canMatchWildcardInModule func(mPath string) bool
+
+       // conflict is the first query identified as incompatible with this one.
+       // conflict forces one or more of the modules matching this query to a
+       // version that does not match version.
+       conflict *query
+
+       // candidates is a list of sets of alternatives for a path that matches (or
+       // contains packages that match) the pattern. The query can be resolved by
+       // choosing exactly one alternative from each set in the list.
+       //
+       // A path-literal query results in only one set: the path itself, which
+       // may resolve to either a package path or a module path.
+       //
+       // A wildcard query results in one set for each matching module path, each
+       // module for which the matching version contains at least one matching
+       // package, and (if no other modules match) one candidate set for the pattern
+       // overall if no existing match is identified in the build list.
+       //
+       // A query for pattern "all" results in one set for each package transitively
+       // imported by the main module.
+       //
+       // The special query for the "-u" flag results in one set for each
+       // otherwise-unconstrained package that has available upgrades.
+       candidates   []pathSet
+       candidatesMu sync.Mutex
+
+       // pathSeen ensures that only one pathSet is added to the query per
+       // unique path.
+       pathSeen sync.Map
+
+       // resolved contains the set of modules whose versions have been determined by
+       // this query, in the order in which they were determined.
+       //
+       // The resolver examines the candidate sets for each query, resolving one
+       // module per candidate set in a way that attempts to avoid obvious conflicts
+       // between the versions resolved by different queries.
+       resolved []module.Version
+
+       // matchesPackages is true if the resolved modules provide at least one
+       // package mathcing q.pattern.
+       matchesPackages bool
+}
+
+// A pathSet describes the possible options for resolving a specific path
+// to a package and/or module.
+type pathSet struct {
+       // path is a package (if "all" or "-u" or a non-wildcard) or module (if
+       // wildcard) path that could be resolved by adding any of the modules in this
+       // set. For a wildcard pattern that so far matches no packages, the path is
+       // the wildcard pattern itself.
+       //
+       // Each path must occur only once in a query's candidate sets, and the path is
+       // added implicitly to each pathSet returned to pathOnce.
+       path string
+
+       // pkgMods is a set of zero or more modules, each of which contains the
+       // package with the indicated path. Due to the requirement that imports be
+       // unambiguous, only one such module can be in the build list, and all others
+       // must be excluded.
+       pkgMods []module.Version
+
+       // mod is either the zero Version, or a module that does not contain any
+       // packages matching the query but for which the module path itself
+       // matches the query pattern.
+       //
+       // We track this module separately from pkgMods because, all else equal, we
+       // prefer to match a query to a package rather than just a module. Also,
+       // unlike the modules in pkgMods, this module does not inherently exclude
+       // any other module in pkgMods.
+       mod module.Version
+
+       err error
+}
+
+// errSet returns a pathSet containing the given error.
+func errSet(err error) pathSet { return pathSet{err: err} }
+
+// newQuery returns a new query parsed from the raw argument,
+// which must be either path or path@version.
+func newQuery(raw string) (*query, error) {
+       pattern := raw
+       rawVers := ""
+       if i := strings.Index(raw, "@"); i >= 0 {
+               pattern, rawVers = raw[:i], raw[i+1:]
+               if strings.Contains(rawVers, "@") || rawVers == "" {
+                       return nil, fmt.Errorf("invalid module version syntax %q", raw)
+               }
+       }
+
+       // If no version suffix is specified, assume @upgrade.
+       // If -u=patch was specified, assume @patch instead.
+       version := rawVers
+       if version == "" {
+               if getU.version == "" {
+                       version = "upgrade"
+               } else {
+                       version = getU.version
+               }
+       }
+
+       q := &query{
+               raw:            raw,
+               rawVersion:     rawVers,
+               pattern:        pattern,
+               patternIsLocal: filepath.IsAbs(pattern) || search.IsRelativePath(pattern),
+               version:        version,
+       }
+       if strings.Contains(q.pattern, "...") {
+               q.matchWildcard = search.MatchPattern(q.pattern)
+               q.canMatchWildcardInModule = search.TreeCanMatchPattern(q.pattern)
+       }
+       if err := q.validate(); err != nil {
+               return q, err
+       }
+       return q, nil
+}
+
+// validate reports a non-nil error if q is not sensible and well-formed.
+func (q *query) validate() error {
+       if q.patternIsLocal {
+               if q.rawVersion != "" {
+                       return fmt.Errorf("can't request explicit version %q of path %q in main module", q.rawVersion, q.pattern)
+               }
+               return nil
+       }
+
+       if q.pattern == "all" {
+               // If there is no main module, "all" is not meaningful.
+               if !modload.HasModRoot() {
+                       return fmt.Errorf(`cannot match "all": working directory is not part of a module`)
+               }
+               if !versionOkForMainModule(q.version) {
+                       // TODO(bcmills): "all@none" seems like a totally reasonable way to
+                       // request that we remove all module requirements, leaving only the main
+                       // module and standard library. Perhaps we should implement that someday.
+                       return &modload.QueryMatchesMainModuleError{
+                               Pattern: q.pattern,
+                               Query:   q.version,
+                       }
+               }
+       }
+
+       if search.IsMetaPackage(q.pattern) && q.pattern != "all" {
+               if q.pattern != q.raw {
+                       return fmt.Errorf("can't request explicit version of standard-library pattern %q", q.pattern)
+               }
+       }
+
+       return nil
+}
+
+// String returns the original argument from which q was parsed.
+func (q *query) String() string { return q.raw }
+
+// ResolvedString returns a string describing m as a resolved match for q.
+func (q *query) ResolvedString(m module.Version) string {
+       if m.Path != q.pattern {
+               if m.Version != q.version {
+                       return fmt.Sprintf("%v (matching %s@%s)", m, q.pattern, q.version)
+               }
+               return fmt.Sprintf("%v (matching %v)", m, q)
+       }
+       if m.Version != q.version {
+               return fmt.Sprintf("%s@%s (%s)", q.pattern, q.version, m.Version)
+       }
+       return q.String()
+}
+
+// isWildcard reports whether q is a pattern that can match multiple paths.
+func (q *query) isWildcard() bool {
+       return q.matchWildcard != nil || (q.patternIsLocal && strings.Contains(q.pattern, "..."))
+}
+
+// matchesPath reports whether the given path matches q.pattern.
+func (q *query) matchesPath(path string) bool {
+       if q.matchWildcard != nil {
+               return q.matchWildcard(path)
+       }
+       return path == q.pattern
+}
+
+// canMatchInModule reports whether the given module path can potentially
+// contain q.pattern.
+func (q *query) canMatchInModule(mPath string) bool {
+       if q.canMatchWildcardInModule != nil {
+               return q.canMatchWildcardInModule(mPath)
+       }
+       return str.HasPathPrefix(q.pattern, mPath)
+}
+
+// pathOnce invokes f to generate the pathSet for the given path,
+// if one is still needed.
+//
+// Note that, unlike sync.Once, pathOnce does not guarantee that a concurrent
+// call to f for the given path has completed on return.
+//
+// pathOnce is safe for concurrent use by multiple goroutines, but note that
+// multiple concurrent calls will result in the sets being added in
+// nondeterministic order.
+func (q *query) pathOnce(path string, f func() pathSet) {
+       if _, dup := q.pathSeen.LoadOrStore(path, nil); dup {
+               return
+       }
+
+       cs := f()
+
+       if len(cs.pkgMods) > 0 || cs.mod != (module.Version{}) || cs.err != nil {
+               cs.path = path
+               q.candidatesMu.Lock()
+               q.candidates = append(q.candidates, cs)
+               q.candidatesMu.Unlock()
+       }
+}
+
+// reportError logs err concisely using base.Errorf.
+func reportError(q *query, err error) {
+       errStr := err.Error()
+
+       // If err already mentions all of the relevant parts of q, just log err to
+       // reduce stutter. Otherwise, log both q and err.
+       //
+       // TODO(bcmills): Use errors.As to unpack these errors instead of parsing
+       // strings with regular expressions.
+
+       patternRE := regexp.MustCompile("(?m)(?:[ \t(\"`]|^)" + regexp.QuoteMeta(q.pattern) + "(?:[ @:)\"`]|$)")
+       if patternRE.MatchString(errStr) {
+               if q.rawVersion == "" {
+                       base.Errorf("go get: %s", errStr)
+                       return
+               }
+
+               versionRE := regexp.MustCompile("(?m)(?:[ @(\"`]|^)" + regexp.QuoteMeta(q.version) + "(?:[ :)\"`]|$)")
+               if versionRE.MatchString(errStr) {
+                       base.Errorf("go get: %s", errStr)
+                       return
+               }
+       }
+
+       base.Errorf("go get %s: %s", q, errStr)
+}
+
+func reportConflict(pq *query, m module.Version, conflict versionReason) {
+       if pq.conflict != nil {
+               // We've already reported a conflict for the proposed query.
+               // Don't report it again, even if it has other conflicts.
+               return
+       }
+       pq.conflict = conflict.reason
+
+       proposed := versionReason{
+               version: m.Version,
+               reason:  pq,
+       }
+       if pq.isWildcard() && !conflict.reason.isWildcard() {
+               // Prefer to report the specific path first and the wildcard second.
+               proposed, conflict = conflict, proposed
+       }
+       reportError(pq, &conflictError{
+               mPath:    m.Path,
+               proposed: proposed,
+               conflict: conflict,
+       })
+}
+
+type conflictError struct {
+       mPath    string
+       proposed versionReason
+       conflict versionReason
+}
+
+func (e *conflictError) Error() string {
+       argStr := func(q *query, v string) string {
+               if v != q.version {
+                       return fmt.Sprintf("%s@%s (%s)", q.pattern, q.version, v)
+               }
+               return q.String()
+       }
+
+       pq := e.proposed.reason
+       rq := e.conflict.reason
+       modDetail := ""
+       if e.mPath != pq.pattern {
+               modDetail = fmt.Sprintf("for module %s, ", e.mPath)
+       }
+
+       return fmt.Sprintf("%s%s conflicts with %s",
+               modDetail,
+               argStr(pq, e.proposed.version),
+               argStr(rq, e.conflict.version))
+}
+
+func versionOkForMainModule(version string) bool {
+       return version == "upgrade" || version == "patch"
+}
index 4a183d6881be67c617f445f7bc6cfc6446283266..4aaaa8d206527b07c922fc3c9176945d6711e462 100644 (file)
@@ -12,6 +12,7 @@ import (
        "context"
        "fmt"
        "os"
+       "strings"
 
        "golang.org/x/mod/module"
 )
@@ -27,6 +28,11 @@ import (
 //
 var buildList []module.Version
 
+// capVersionSlice returns s with its cap reduced to its length.
+func capVersionSlice(s []module.Version) []module.Version {
+       return s[:len(s):len(s)]
+}
+
 // LoadAllModules loads and returns the list of modules matching the "all"
 // module pattern, starting with the Target module and in a deterministic
 // (stable) order, without loading any packages.
@@ -35,21 +41,21 @@ var buildList []module.Version
 // LoadAllModules need only be called if LoadPackages is not,
 // typically in commands that care about modules but no particular package.
 //
-// The caller must not modify the returned list.
+// The caller must not modify the returned list, but may append to it.
 func LoadAllModules(ctx context.Context) []module.Version {
        LoadModFile(ctx)
        ReloadBuildList()
        WriteGoMod()
-       return buildList
+       return capVersionSlice(buildList)
 }
 
 // LoadedModules returns the list of module requirements loaded or set by a
 // previous call (typically LoadAllModules or LoadPackages), starting with the
 // Target module and in a deterministic (stable) order.
 //
-// The caller must not modify the returned list.
+// The caller must not modify the returned list, but may append to it.
 func LoadedModules() []module.Version {
-       return buildList
+       return capVersionSlice(buildList)
 }
 
 // Selected returns the selected version of the module with the given path, or
@@ -74,6 +80,147 @@ func SetBuildList(list []module.Version) {
        buildList = append([]module.Version{}, list...)
 }
 
+// EditBuildList edits the global build list by first adding every module in add
+// to the existing build list, then adjusting versions (and adding or removing
+// requirements as needed) until every module in mustSelect is selected at the
+// given version.
+//
+// (Note that the newly-added modules might not be selected in the resulting
+// build list: they could be lower than existing requirements or conflict with
+// versions in mustSelect.)
+//
+// After performing the requested edits, EditBuildList returns the updated build
+// list.
+//
+// If the versions listed in mustSelect are mutually incompatible (due to one of
+// the listed modules requiring a higher version of another), EditBuildList
+// returns a *ConstraintError and leaves the build list in its previous state.
+func EditBuildList(ctx context.Context, add, mustSelect []module.Version) error {
+       var upgraded = capVersionSlice(buildList)
+       if len(add) > 0 {
+               // First, upgrade the build list with any additions.
+               // In theory we could just append the additions to the build list and let
+               // mvs.Downgrade take care of resolving the upgrades too, but the
+               // diagnostics from Upgrade are currently much better in case of errors.
+               var err error
+               upgraded, err = mvs.Upgrade(Target, &mvsReqs{buildList: upgraded}, add...)
+               if err != nil {
+                       return err
+               }
+       }
+
+       downgraded, err := mvs.Downgrade(Target, &mvsReqs{buildList: append(upgraded, mustSelect...)}, mustSelect...)
+       if err != nil {
+               return err
+       }
+
+       final, err := mvs.Upgrade(Target, &mvsReqs{buildList: downgraded}, mustSelect...)
+       if err != nil {
+               return err
+       }
+
+       selected := make(map[string]module.Version, len(final))
+       for _, m := range final {
+               selected[m.Path] = m
+       }
+       inconsistent := false
+       for _, m := range mustSelect {
+               s, ok := selected[m.Path]
+               if !ok {
+                       if m.Version != "none" {
+                               panic(fmt.Sprintf("internal error: mvs.BuildList lost %v", m))
+                       }
+                       continue
+               }
+               if s.Version != m.Version {
+                       inconsistent = true
+                       break
+               }
+       }
+
+       if !inconsistent {
+               buildList = final
+               return nil
+       }
+
+       // We overshot one or more of the modules in mustSelected, which means that
+       // Downgrade removed something in mustSelect because it conflicted with
+       // something else in mustSelect.
+       //
+       // Walk the requirement graph to find the conflict.
+       //
+       // TODO(bcmills): Ideally, mvs.Downgrade (or a replacement for it) would do
+       // this directly.
+
+       reqs := &mvsReqs{buildList: final}
+       reason := map[module.Version]module.Version{}
+       for _, m := range mustSelect {
+               reason[m] = m
+       }
+       queue := mustSelect[:len(mustSelect):len(mustSelect)]
+       for len(queue) > 0 {
+               var m module.Version
+               m, queue = queue[0], queue[1:]
+               required, err := reqs.Required(m)
+               if err != nil {
+                       return err
+               }
+               for _, r := range required {
+                       if _, ok := reason[r]; !ok {
+                               reason[r] = reason[m]
+                               queue = append(queue, r)
+                       }
+               }
+       }
+
+       var conflicts []Conflict
+       for _, m := range mustSelect {
+               s, ok := selected[m.Path]
+               if !ok {
+                       if m.Version != "none" {
+                               panic(fmt.Sprintf("internal error: mvs.BuildList lost %v", m))
+                       }
+                       continue
+               }
+               if s.Version != m.Version {
+                       conflicts = append(conflicts, Conflict{
+                               Source:     reason[s],
+                               Dep:        s,
+                               Constraint: m,
+                       })
+               }
+       }
+
+       return &ConstraintError{
+               Conflicts: conflicts,
+       }
+}
+
+// A ConstraintError describes inconsistent constraints in EditBuildList
+type ConstraintError struct {
+       // Conflict lists the source of the conflict for each version in mustSelect
+       // that could not be selected due to the requirements of some other version in
+       // mustSelect.
+       Conflicts []Conflict
+}
+
+func (e *ConstraintError) Error() string {
+       b := new(strings.Builder)
+       b.WriteString("version constraints conflict:")
+       for _, c := range e.Conflicts {
+               fmt.Fprintf(b, "\n\t%v requires %v, but %v is requested", c.Source, c.Dep, c.Constraint)
+       }
+       return b.String()
+}
+
+// A Conflict documents that Source requires Dep, which conflicts with Constraint.
+// (That is, Dep has the same module path as Constraint but a higher version.)
+type Conflict struct {
+       Source     module.Version
+       Dep        module.Version
+       Constraint module.Version
+}
+
 // ReloadBuildList resets the state of loaded packages, then loads and returns
 // the build list set in SetBuildList.
 func ReloadBuildList() []module.Version {
@@ -84,7 +231,7 @@ func ReloadBuildList() []module.Version {
                listRoots:          func() []string { return nil },
                allClosesOverTests: index.allPatternClosesOverTests(), // but doesn't matter because the root list is empty.
        })
-       return buildList
+       return capVersionSlice(buildList)
 }
 
 // TidyBuildList trims the build list to the minimal requirements needed to
index 193edfdd2088b1761173a7c565d142e4a6fe17ce..eb0a366f92445af9f11c8d63da4bc75d80fa2f5e 100644 (file)
@@ -188,9 +188,9 @@ func (e *invalidImportError) Unwrap() error {
 // importFromBuildList can return an empty directory string, for fake packages
 // like "C" and "unsafe".
 //
-// If the package cannot be found in the current build list,
+// If the package cannot be found in buildList,
 // importFromBuildList returns an *ImportMissingError.
-func importFromBuildList(ctx context.Context, path string) (m module.Version, dir string, err error) {
+func importFromBuildList(ctx context.Context, path string, buildList []module.Version) (m module.Version, dir string, err error) {
        if strings.Contains(path, "@") {
                return module.Version{}, "", fmt.Errorf("import path should not have @version")
        }
index 0a84a1765a584ef51c62027b6eca9f2a869c1b7d..302330278eba273784aabdcedc5fdc983bc22642 100644 (file)
@@ -141,6 +141,15 @@ type PackageOpts struct {
        // if the flag is set to "readonly" (the default) or "vendor".
        ResolveMissingImports bool
 
+       // AllowPackage, if non-nil, is called after identifying the module providing
+       // each package. If AllowPackage returns a non-nil error, that error is set
+       // for the package, and the imports and test of that package will not be
+       // loaded.
+       //
+       // AllowPackage may be invoked concurrently by multiple goroutines,
+       // and may be invoked multiple times for a given package path.
+       AllowPackage func(ctx context.Context, path string, mod module.Version) error
+
        // LoadTests loads the test dependencies of each package matching a requested
        // pattern. If ResolveMissingImports is also true, test dependencies will be
        // resolved if missing.
@@ -550,6 +559,7 @@ func DirImportPath(dir string) string {
 func TargetPackages(ctx context.Context, pattern string) *search.Match {
        // TargetPackages is relative to the main module, so ensure that the main
        // module is a thing that can contain packages.
+       LoadModFile(ctx)
        ModRoot()
 
        m := search.NewMatch(pattern)
@@ -1042,7 +1052,7 @@ func (ld *loader) load(pkg *loadPkg) {
                return
        }
 
-       pkg.mod, pkg.dir, pkg.err = importFromBuildList(context.TODO(), pkg.path)
+       pkg.mod, pkg.dir, pkg.err = importFromBuildList(context.TODO(), pkg.path, buildList)
        if pkg.dir == "" {
                return
        }
@@ -1058,6 +1068,11 @@ func (ld *loader) load(pkg *loadPkg) {
                // to scanning source code for imports).
                ld.applyPkgFlags(pkg, pkgInAll)
        }
+       if ld.AllowPackage != nil {
+               if err := ld.AllowPackage(context.TODO(), pkg.path, pkg.mod); err != nil {
+                       pkg.err = err
+               }
+       }
 
        imports, testImports, err := scanDir(pkg.dir, ld.Tags)
        if err != nil {
index 045cbead3b2b17ded7d28685a4d8cd2267c87a6e..02b13cdd053499f816baabadbf376efa9d93d989 100644 (file)
@@ -99,8 +99,16 @@ func versions(ctx context.Context, path string, allowed AllowedFunc) ([]string,
 
 // Previous returns the tagged version of m.Path immediately prior to
 // m.Version, or version "none" if no prior version is tagged.
+//
+// Since the version of Target is not found in the version list,
+// it has no previous version.
 func (*mvsReqs) Previous(m module.Version) (module.Version, error) {
        // TODO(golang.org/issue/38714): thread tracing context through MVS.
+
+       if m == Target {
+               return module.Version{Path: m.Path, Version: "none"}, nil
+       }
+
        list, err := versions(context.TODO(), m.Path, CheckAllowed)
        if err != nil {
                if errors.Is(err, os.ErrNotExist) {
index 99cbac1aa79988906f0ba8411a7c3f5f26594ab7..d4a1e850415bd3a9db278e105e94c6a1963505e4 100644 (file)
@@ -47,8 +47,9 @@ import (
 //   with non-prereleases preferred over prereleases.
 // - a repository commit identifier or tag, denoting that commit.
 //
-// current denotes the current version of the module; it may be "" if the
-// current version is unknown or should not be considered. If query is
+// current denotes the currently-selected version of the module; it may be
+// "none" if no version is currently selected, or "" if the currently-selected
+// version is unknown or should not be considered. If query is
 // "upgrade" or "patch", current will be returned if it is a newer
 // semantic version or a chronologically later pseudo-version than the
 // version that would otherwise be chosen. This prevents accidental downgrades
@@ -98,7 +99,7 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
        ctx, span := trace.StartSpan(ctx, "modload.queryProxy "+path+" "+query)
        defer span.Done()
 
-       if current != "" && !semver.IsValid(current) {
+       if current != "" && current != "none" && !semver.IsValid(current) {
                return nil, fmt.Errorf("invalid previous version %q", current)
        }
        if cfg.BuildMod == "vendor" {
@@ -109,7 +110,7 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
        }
 
        if path == Target.Path {
-               if query != "latest" && query != "upgrade" && query != "patch" {
+               if query != "upgrade" && query != "patch" {
                        return nil, &QueryMatchesMainModuleError{Pattern: path, Query: query}
                }
                if err := allowed(ctx, Target); err != nil {
@@ -236,7 +237,7 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
                }
        }
 
-       if (query == "upgrade" || query == "patch") && current != "" {
+       if (query == "upgrade" || query == "patch") && current != "" && current != "none" {
                // "upgrade" and "patch" may stay on the current version if allowed.
                if err := allowed(ctx, module.Version{Path: path, Version: current}); errors.Is(err, ErrDisallowed) {
                        return nil, err
@@ -323,7 +324,7 @@ func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (*
                qm.mayUseLatest = true
 
        case query == "upgrade":
-               if current == "" {
+               if current == "" || current == "none" {
                        qm.mayUseLatest = true
                } else {
                        qm.mayUseLatest = modfetch.IsPseudoVersion(current)
@@ -331,6 +332,9 @@ func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (*
                }
 
        case query == "patch":
+               if current == "none" {
+                       return nil, &NoPatchBaseError{path}
+               }
                if current == "" {
                        qm.mayUseLatest = true
                } else {
@@ -554,6 +558,9 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
 
        if i := strings.Index(pattern, "..."); i >= 0 {
                base = pathpkg.Dir(pattern[:i+3])
+               if base == "." {
+                       return nil, nil, &WildcardInFirstElementError{Pattern: pattern, Query: query}
+               }
                match = func(mod module.Version, root string, isLocal bool) *search.Match {
                        m := search.NewMatch(pattern)
                        matchPackages(ctx, m, imports.AnyTags(), omitStd, []module.Version{mod})
@@ -578,7 +585,7 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
        if HasModRoot() {
                m := match(Target, modRoot, true)
                if len(m.Pkgs) > 0 {
-                       if query != "latest" && query != "upgrade" && query != "patch" {
+                       if query != "upgrade" && query != "patch" {
                                return nil, nil, &QueryMatchesPackagesInMainModuleError{
                                        Pattern:  pattern,
                                        Query:    query,
@@ -598,7 +605,7 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
                        return nil, nil, err
                }
 
-               if query != "latest" && query != "upgrade" && query != "patch" && matchPattern(Target.Path) {
+               if query != "upgrade" && query != "patch" && matchPattern(Target.Path) {
                        if err := allowed(ctx, Target); err == nil {
                                modOnly = &QueryResult{
                                        Mod: Target,
@@ -724,6 +731,7 @@ func queryPrefixModules(ctx context.Context, candidateModules []string, queryMod
        var (
                noPackage   *PackageNotInModuleError
                noVersion   *NoMatchingVersionError
+               noPatchBase *NoPatchBaseError
                notExistErr error
        )
        for _, r := range results {
@@ -740,6 +748,10 @@ func queryPrefixModules(ctx context.Context, candidateModules []string, queryMod
                        if noVersion == nil {
                                noVersion = rErr
                        }
+               case *NoPatchBaseError:
+                       if noPatchBase == nil {
+                               noPatchBase = rErr
+                       }
                default:
                        if errors.Is(rErr, fs.ErrNotExist) {
                                if notExistErr == nil {
@@ -771,6 +783,8 @@ func queryPrefixModules(ctx context.Context, candidateModules []string, queryMod
                        err = noPackage
                case noVersion != nil:
                        err = noVersion
+               case noPatchBase != nil:
+                       err = noPatchBase
                case notExistErr != nil:
                        err = notExistErr
                default:
@@ -795,12 +809,34 @@ type NoMatchingVersionError struct {
 
 func (e *NoMatchingVersionError) Error() string {
        currentSuffix := ""
-       if (e.query == "upgrade" || e.query == "patch") && e.current != "" {
+       if (e.query == "upgrade" || e.query == "patch") && e.current != "" && e.current != "none" {
                currentSuffix = fmt.Sprintf(" (current version is %s)", e.current)
        }
        return fmt.Sprintf("no matching versions for query %q", e.query) + currentSuffix
 }
 
+// A NoPatchBaseError indicates that Query was called with the query "patch"
+// but with a current version of "" or "none".
+type NoPatchBaseError struct {
+       path string
+}
+
+func (e *NoPatchBaseError) Error() string {
+       return fmt.Sprintf(`can't query version "patch" of module %s: no existing version is required`, e.path)
+}
+
+// A WildcardInFirstElementError indicates that a pattern passed to QueryPattern
+// had a wildcard in its first path element, and therefore had no pattern-prefix
+// modules to search in.
+type WildcardInFirstElementError struct {
+       Pattern string
+       Query   string
+}
+
+func (e *WildcardInFirstElementError) Error() string {
+       return fmt.Sprintf("no modules to query for %s@%s because first path element contains a wildcard", e.Pattern, e.Query)
+}
+
 // A PackageNotInModuleError indicates that QueryPattern found a candidate
 // module at the requested version, but that module did not contain any packages
 // matching the requested pattern.
index f6d6f5f764e59503e5101c73199fa7c2a72d306e..1fe742dc97d51df88db5a33e953223626108c641 100644 (file)
@@ -156,7 +156,7 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
                        isLocal = true
                } else {
                        var err error
-                       needSum := true
+                       const needSum = true
                        root, isLocal, err = fetch(ctx, mod, needSum)
                        if err != nil {
                                m.AddError(err)
@@ -174,3 +174,46 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
 
        return
 }
+
+// MatchInModule identifies the packages matching the given pattern within the
+// given module version, which does not need to be in the build list or module
+// requirement graph.
+//
+// If m is the zero module.Version, MatchInModule matches the pattern
+// against the standard library (std and cmd) in GOROOT/src.
+func MatchInModule(ctx context.Context, pattern string, m module.Version, tags map[string]bool) *search.Match {
+       match := search.NewMatch(pattern)
+       if m == (module.Version{}) {
+               matchPackages(ctx, match, tags, includeStd, nil)
+       }
+
+       LoadModFile(ctx)
+
+       if !match.IsLiteral() {
+               matchPackages(ctx, match, tags, omitStd, []module.Version{m})
+               return match
+       }
+
+       const needSum = true
+       root, isLocal, err := fetch(ctx, m, needSum)
+       if err != nil {
+               match.Errs = []error{err}
+               return match
+       }
+
+       dir, haveGoFiles, err := dirInModule(pattern, m.Path, root, isLocal)
+       if err != nil {
+               match.Errs = []error{err}
+               return match
+       }
+       if haveGoFiles {
+               if _, _, err := scanDir(dir, tags); err != imports.ErrNoGo {
+                       // ErrNoGo indicates that the directory is not actually a Go package,
+                       // perhaps due to the tags in use. Any other non-nil error indicates a
+                       // problem with one or more of the Go source files, but such an error does
+                       // not stop the package from existing, so it has no impact on matching.
+                       match.Pkgs = []string{pattern}
+               }
+       }
+       return match
+}
index d0b9860ade0f9ad576576ec64706cc1b7879e21c..2b7584920956eedf242646e45269e3f28693c84d 100644 (file)
@@ -3,5 +3,7 @@
 
 [!net] skip
 
+env GO111MODULE=off
+
 go get -u -n .../
 ! stderr 'duplicate loads of' # make sure old packages are removed from cache
index 868a8d43d67e41ac011c22710d359b30d2445455..20199c1c2ca8e8c61ef73af5df7d59aea17bd1da 100644 (file)
@@ -2,7 +2,7 @@ env GO111MODULE=on
 
 # explicit get should report errors about bad names
 ! go get appengine
-stderr '^go get appengine: package appengine is not in GOROOT \(.*\)$'
+stderr '^go get: malformed module path "appengine": missing dot in first path element$'
 ! go get x/y.z
 stderr 'malformed module path "x/y.z": missing dot in first path element'
 
index 1f7643e1deab05f9aa6d61a0852a26cd76ec8b96..72be61279999b61a2fdc472cdaae893ecadeabda 100644 (file)
@@ -5,7 +5,7 @@ env GO111MODULE=on
 # to resolve an external module.
 cd dir
 ! go get .
-stderr 'go get \.: path .* is not a package in module rooted at .*[/\\]dir$'
+stderr 'go get: \. \(.*[/\\]dir\) is not a package in module rooted at .*[/\\]dir$'
 ! go list
 ! stderr 'cannot find module providing package'
 stderr '^no Go files in '$WORK'[/\\]gopath[/\\]src[/\\]dir$'
index c53bbe456743f39ccd57e54e21ab6718d7297f10..2775fca44ee5daef927ccd20d79afdbdcd7916a6 100644 (file)
@@ -101,11 +101,11 @@ stderr '^rsc.io/quote@v1.999.999: reading .*/v1.999.999.info: 404 Not Found$'
 ! go mod download -json bad/path
 stdout '^\t"Error": "module bad/path: not a known dependency"'
 
-# download main module returns an error
+# download main module produces a warning or error
 go mod download m
 stderr '^go mod download: skipping argument m that resolves to the main module\n'
-go mod download m@latest
-stderr '^go mod download: skipping argument m@latest that resolves to the main module\n'
+go mod download m@latest
+stderr 'm@latest: can''t request version "latest" of the main module \(m\)'
 
 # download updates go.mod and populates go.sum
 cd update
index f64da3a3fd0437ecd74f919d3542920e315a2c8e..daed03b02c6cf41b6367d6ffa1846c2e37d1d39d 100644 (file)
@@ -1,23 +1,22 @@
 go mod tidy
 cp go.mod go.mod.orig
 
-# If there is no sensible *package* meaning for 'm/p', perhaps it should refer
-# to *module* m/p?
-# Today, it still seems to refer to the package.
+# If there is no sensible *package* meaning for 'm/p', it should refer
+# to *module* m/p.
 
-! go get -d m/p@v0.1.0
-stderr 'go get m/p@v0.1.0: module m/p@latest found \(v0.1.0, replaced by ./mp01\), but does not contain package m/p'
-cmp go.mod.orig go.mod
-
-
-# TODO(#37438): If we add v0.2.0 before this point, we end up (somehow!)
-# resolving m/p@v0.1.0 as *both* a module and a package.
+go get -d m/p  # @latest
+go list -m all
+stdout '^m/p v0.3.0 '
+! stdout '^m '
 
 cp go.mod.orig go.mod
-go mod edit -replace=m@v0.2.0=./m02
-go mod edit -replace=m/p@v0.2.0=./mp02
 
-# The argument 'm/p' in 'go get m/p' refers to *package* m/p,
+go get -d m/p@v0.1.0
+go list -m all
+stdout '^m/p v0.1.0 '
+! stdout '^m '
+
+# When feasible, the argument 'm/p' in 'go get m/p' refers to *package* m/p,
 # which is in module m.
 #
 # (It only refers to *module* m/p if there is no such package at the
@@ -26,7 +25,7 @@ go mod edit -replace=m/p@v0.2.0=./mp02
 go get -d m/p@v0.2.0
 go list -m all
 stdout '^m v0.2.0 '
-stdout '^m/p v0.1.0 '
+stdout '^m/p v0.1.0 '  # unchanged from the previous case
 
 # Repeating the above with module m/p already in the module graph does not
 # change its meaning.
@@ -36,26 +35,6 @@ go list -m all
 stdout '^m v0.2.0 '
 stdout '^m/p v0.1.0 '
 
-
-# TODO(#37438): If we add v0.3.0 before this point, we get a totally bogus error
-# today, because 'go get' ends up attempting to resolve package 'm/p' without a
-# specific version and can't find it if module m no longer contains v0.3.0.
-
-cp go.mod.orig go.mod
-go mod edit -replace=m@v0.3.0=./m03
-go mod edit -replace=m/p@v0.3.0=./mp03
-
-! go get -d m/p@v0.2.0
-stderr 'go get m/p@v0.2.0: module m/p@latest found \(v0.3.0, replaced by ./mp03\), but does not contain package m/p$'
-
-# If there is no sensible package meaning for 'm/p', perhaps it should refer
-# to *module* m/p?
-# Today, it still seems to refer to the package.
-
-! go get -d m/p@v0.3.0
-stderr '^go get m/p@v0.3.0: module m/p@latest found \(v0\.3\.0, replaced by \./mp03\), but does not contain package m/p$'
-
-
 -- go.mod --
 module example.com
 
@@ -63,7 +42,11 @@ go 1.16
 
 replace (
        m v0.1.0 => ./m01
+       m v0.2.0 => ./m02
+       m v0.3.0 => ./m03
        m/p v0.1.0 => ./mp01
+       m/p v0.2.0 => ./mp02
+       m/p v0.3.0 => ./mp03
 )
 -- m01/go.mod --
 module m
index 8f5bf20636d17265bfc7313f9586430275cd2f80..33605f51a5b505570a3b7c394bfe426b72862a39 100644 (file)
@@ -9,7 +9,7 @@ cp go.mod go.mod.orig
 # TODO(#27899): Should we automatically upgrade example.net/m to v0.2.0
 # to resolve the conflict?
 ! go get -d example.net/m/p@v1.0.0
-stderr '^go get example.net/m/p@v1.0.0: ambiguous import: found package example.net/m/p in multiple modules:\n\texample.net/m v0.1.0 \(.*[/\\]m1[/\\]p\)\n\texample.net/m/p v1.0.0 \(.*[/\\]p0\)\n\z'
+stderr '^example.net/m/p: ambiguous import: found package example.net/m/p in multiple modules:\n\texample.net/m v0.1.0 \(.*[/\\]m1[/\\]p\)\n\texample.net/m/p v1.0.0 \(.*[/\\]p0\)\n\z'
 cmp go.mod go.mod.orig
 
 # Upgrading both modules simultaneously resolves the ambiguous upgrade.
index f00f99ee8c83cbff4d77f77c98d6cd62827cd511..0e7f93bccb5b8773ee9b461b27efa3f5511074cf 100644 (file)
@@ -15,22 +15,20 @@ stdout '^example.net/ambiguous/nested v0.1.0$'
 
 
 # From an initial state that already depends on the shorter path,
-# the same 'go get' command attempts to add the longer path and fails.
-#
-# TODO(bcmills): What should really happen here?
-# Should we match the versioned package path against the existing package
-# (reducing unexpected errors), or give it the same meaning regardless of the
-# initial state?
+# the same 'go get' command should (somewhat arbitrarily) keep the
+# existing path, since it is a valid interpretation of the command.
 
 cp go.mod.orig go.mod
 go mod edit -require=example.net/ambiguous@v0.1.0
 
-! go get -d example.net/ambiguous/nested/pkg@v0.1.0
-stderr '^go get example.net/ambiguous/nested/pkg@v0.1.0: ambiguous import: found package example.net/ambiguous/nested/pkg in multiple modules:\n\texample.net/ambiguous v0.1.0 \(.*\)\n\texample.net/ambiguous/nested v0.1.0 \(.*\)\n\z'
+go get -d example.net/ambiguous/nested/pkg@v0.1.0
+go list -m all
+stdout '^example.net/ambiguous v0.1.0$'
+! stdout '^example.net/ambiguous/nested '
 
 
-# The user should be able to fix the aforementioned failure by explicitly
-# upgrading the conflicting module.
+# The user should be able to make the command unambiguous by explicitly
+# upgrading the conflicting module...
 
 go get -d example.net/ambiguous@v0.2.0 example.net/ambiguous/nested/pkg@v0.1.0
 go list -m all
@@ -39,38 +37,26 @@ stdout '^example.net/ambiguous v0.2.0$'
 
 
 # ...or by explicitly NOT adding the conflicting module.
-#
-# BUG(#37438): Today, this does not work: explicit module version constraints do
-# not affect the package-to-module mapping during package upgrades, so the
-# arguments are interpreted as specifying conflicting versions of the longer
-# module path.
 
 cp go.mod.orig go.mod
 go mod edit -require=example.net/ambiguous@v0.1.0
 
-! go get -d example.net/ambiguous/nested/pkg@v0.1.0 example.net/ambiguous/nested@none
-stderr '^go get: conflicting versions for module example.net/ambiguous/nested: v0.1.0 and none$'
-
- # go list -m all
- # ! stdout '^example.net/ambiguous/nested '
- # stdout '^example.net/ambiguous v0.1.0$'
+go get -d example.net/ambiguous/nested/pkg@v0.1.0 example.net/ambiguous/nested@none
+go list -m all
+! stdout '^example.net/ambiguous/nested '
+stdout '^example.net/ambiguous v0.1.0$'
 
 
 # The user should also be able to fix it by *downgrading* the conflicting module
 # away.
-#
-# BUG(#37438): Today, this does not work: the "ambiguous import" error causes
-# 'go get' to fail before applying the requested downgrade.
 
 cp go.mod.orig go.mod
 go mod edit -require=example.net/ambiguous@v0.1.0
 
-! go get -d example.net/ambiguous@none example.net/ambiguous/nested/pkg@v0.1.0
-stderr '^go get example.net/ambiguous/nested/pkg@v0.1.0: ambiguous import: found package example.net/ambiguous/nested/pkg in multiple modules:\n\texample.net/ambiguous v0.1.0 \(.*\)\n\texample.net/ambiguous/nested v0.1.0 \(.*\)\n\z'
-
- # go list -m all
- # stdout '^example.net/ambiguous/nested v0.1.0$'
- # !stdout '^example.net/ambiguous '
+go get -d example.net/ambiguous@none example.net/ambiguous/nested/pkg@v0.1.0
+go list -m all
+stdout '^example.net/ambiguous/nested v0.1.0$'
+! stdout '^example.net/ambiguous '
 
 
 # In contrast, if we do the same thing tacking a wildcard pattern ('/...') on
index ee9ac964751fe6b648fa77c879ce81089ec63a84..77dad2b3853fcfdf73f121ae6d5418c66e8f7df8 100644 (file)
@@ -19,7 +19,8 @@ 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\)'
+stderr '^go get: rsc.io/quote@v1.5.2 requires rsc.io/sampler@v1.3.0, not rsc.io/sampler@v1.0.0$'
+stderr '^go get: rsc.io/quote@v1.5.2 requires golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c, not golang.org/x/text@none$'
 go list -m all
 stdout 'rsc.io/quote v1.5.1'
 stdout 'rsc.io/sampler v1.3.0'
index 543f9f8111d30763cf8895f77999fc7bd7c2432a..f1167fb578a1320903baf2e0cef8b82d4e877a2a 100644 (file)
@@ -5,12 +5,12 @@ cp go.mod go.mod.orig
 # rather than a "matched no packages" warning.
 
 ! go get example.net/pkgadded@v1.1.0 example.net/pkgadded/subpkg/...
-stderr '^go get: conflicting versions for module example\.net/pkgadded: v1\.1\.0 and v1\.2\.0$'
+stderr '^go get: example.net/pkgadded@v1.1.0 conflicts with example.net/pkgadded/subpkg/...@upgrade \(v1.2.0\)$'
 ! stderr 'matched no packages'
 cmp go.mod.orig go.mod
 
 
-# A wildcard pattern should match a package in a module with that path.
+# A wildcard pattern should match the pattern with that path.
 
 go get example.net/pkgadded/...@v1.0.0
 go list -m all
@@ -23,8 +23,8 @@ cp go.mod.orig go.mod
 # package, then 'go get' should fail with a useful error message.
 
 ! go get example.net/pkgadded@v1.0.0 .
-stderr -count=1 '^go: found example.net/pkgadded/subpkg in example.net/pkgadded v1\.2\.0$'  # TODO: We shouldn't even try v1.2.0.
 stderr '^example.com/m imports\n\texample.net/pkgadded/subpkg: cannot find module providing package example.net/pkgadded/subpkg$'
+! stderr 'example.net/pkgadded v1\.2\.0'
 cmp go.mod.orig go.mod
 
 go get example.net/pkgadded@v1.0.0
diff --git a/src/cmd/go/testdata/script/mod_get_extra.txt b/src/cmd/go/testdata/script/mod_get_extra.txt
new file mode 100644 (file)
index 0000000..7efa24e
--- /dev/null
@@ -0,0 +1,69 @@
+cp go.mod go.mod.orig
+
+# The -u flag should not (even temporarily) upgrade modules whose versions are
+# determined by explicit queries to any version other than the explicit one.
+# Otherwise, 'go get -u' could introduce spurious dependencies.
+
+go get -d -u example.net/a@v0.1.0 example.net/b@v0.1.0
+go list -m all
+stdout '^example.net/a v0.1.0 '
+stdout '^example.net/b v0.1.0 '
+! stdout '^example.net/c '
+
+
+# TODO(bcmills): This property does not yet hold for modules added for
+# missing packages when the newly-added module matches a wildcard.
+
+cp go.mod.orig go.mod
+
+go get -d -u example.net/a@v0.1.0 example.net/b/...@v0.1.0
+go list -m all
+stdout '^example.net/a v0.1.0 '
+stdout '^example.net/b v0.1.0 '
+stdout '^example.net/c '  # BUG, but a minor and rare one
+
+
+-- go.mod --
+module example
+
+go 1.15
+
+replace (
+       example.net/a v0.1.0 => ./a1
+       example.net/b v0.1.0 => ./b1
+       example.net/b v0.2.0 => ./b2
+       example.net/c v0.1.0 => ./c1
+       example.net/c v0.2.0 => ./c1
+)
+
+-- a1/go.mod --
+module example.net/a
+
+go 1.15
+
+// example.net/a needs a dependency on example.net/b, but lacks a requirement
+// on it (perhaps due to a missed file in a VCS commit).
+-- a1/a.go --
+package a
+import _ "example.net/b"
+
+-- b1/go.mod --
+module example.net/b
+
+go 1.15
+-- b1/b.go --
+package b
+
+-- b2/go.mod --
+module example.net/b
+
+go 1.15
+
+require example.net/c v0.1.0
+-- b2/b.go --
+package b
+
+-- c1/go.mod --
+module example.net/c
+
+go 1.15
diff --git a/src/cmd/go/testdata/script/mod_get_issue37438.txt b/src/cmd/go/testdata/script/mod_get_issue37438.txt
new file mode 100644 (file)
index 0000000..38b2031
--- /dev/null
@@ -0,0 +1,37 @@
+# Regression test for https://golang.org/issue/37438.
+#
+# If a path exists at the requested version, but does not exist at the
+# version of the module that is already required and does not exist at
+# the version that would be selected by 'go mod tidy', then
+# 'go get foo@requested' should resolve the requested version,
+# not error out on the (unrelated) latest one.
+
+go get -d example.net/a/p@v0.2.0
+
+-- go.mod --
+module example
+
+go 1.15
+
+require example.net/a v0.1.0
+
+replace (
+       example.net/a v0.1.0 => ./a1
+       example.net/a v0.2.0 => ./a2
+       example.net/a v0.3.0 => ./a1
+)
+
+-- a1/go.mod --
+module example.net/a
+
+go 1.15
+-- a1/README --
+package example.net/a/p does not exist at this version.
+
+-- a2/go.mod --
+module example.net/a
+
+go 1.15
+-- a2/p/p.go --
+// Package p exists only at v0.2.0.
+package p
index 408a5b51c843f04f29ec537f28d0ac7187dbd3f1..eeaa92d8ca52d4dfffa54d531d89522f57602a89 100644 (file)
@@ -3,13 +3,13 @@ cp go.mod.orig go.mod
 
 # relative and absolute paths must be within the main module.
 ! go get -d ..
-stderr '^go get \.\.: path '$WORK'[/\\]gopath is not a package in module rooted at '$WORK'[/\\]gopath[/\\]src$'
+stderr '^go get: \.\. \('$WORK'[/\\]gopath\) is not within module rooted at '$WORK'[/\\]gopath[/\\]src$'
 ! go get -d $WORK
-stderr '^go get '$WORK': path '$WORK' is not a package in module rooted at '$WORK'[/\\]gopath[/\\]src$'
+stderr '^go get: '$WORK' is not within module rooted at '$WORK'[/\\]gopath[/\\]src$'
 ! go get -d ../...
-stderr '^go get: pattern \.\./\.\.\.: directory prefix \.\. outside available modules$'
+stderr '^go get: \.\./\.\.\. \('$WORK'[/\\]gopath([/\\]...)?\) is not within module rooted at '$WORK'[/\\]gopath[/\\]src$'
 ! go get -d $WORK/...
-stderr '^go get: pattern '$WORK'[/\\]\.\.\.: directory prefix \.\.[/\\]\.\. outside available modules$'
+stderr '^go get: '$WORK'[/\\]\.\.\. is not within module rooted at '$WORK'[/\\]gopath[/\\]src$'
 
 # @patch and @latest within the main module refer to the current version.
 # The main module won't be upgraded, but missing dependencies will be added.
@@ -22,21 +22,21 @@ go get -d rsc.io/x@patch
 grep 'rsc.io/quote v1.5.2' go.mod
 cp go.mod.orig go.mod
 
-# The main module cannot be updated to @latest, which is a specific version.
-! go get -d rsc.io/x@latest
-stderr '^go get rsc.io/x@latest: can.t request explicit version of path in main module$'
-
-# The main module cannot be updated to a specific version.
-! go get -d rsc.io/x@v0.1.0
-stderr '^go get rsc.io/x@v0.1.0: can.t request explicit version of path in main module$'
-! go get -d rsc.io/x@v0.1.0
-stderr '^go get rsc.io/x@v0.1.0: can.t request explicit version of path in main module$'
 
 # Upgrading a package pattern not contained in the main module should not
 # attempt to upgrade the main module.
 go get -d rsc.io/quote/...@v1.5.1
 grep 'rsc.io/quote v1.5.1' go.mod
 
+
+# The main module cannot be updated to a specific version.
+! go get -d rsc.io/x@v0.1.0
+stderr '^go get: package rsc.io/x is in the main module, so can''t request version v0.1.0$'
+
+# The main module cannot be updated to @latest, which is a specific version.
+! go get -d rsc.io/x/...@latest
+stderr '^go get: pattern rsc.io/x/... matches package rsc.io/x in the main module, so can''t request version latest$'
+
 -- go.mod.orig --
 module rsc.io
 
index b46ec8e8b6f2e636ae6013560cb5bbb6570157f3..e16c16089091bb39193e54abf076a44feb7240e0 100644 (file)
@@ -28,7 +28,9 @@ go list -m all
 stdout 'example.com/join/subpkg v1.0.0'
 
 # A 'go get' that simultaneously upgrades away conflicting package definitions is not ambiguous.
-go get example.com/join/subpkg@v1.1.0
+# (A wildcard pattern applies to both packages and modules,
+# because we define wildcard matching to apply after version resolution.)
+go get example.com/join/subpkg/...@v1.1.0
 
 # A 'go get' without an upgrade should find the package.
 rm go.mod
index 5c197bb0b82f771208559b14764948b830612941..f71620c1bcb41cac9e49ffc102c683f533a7ae4f 100644 (file)
@@ -11,5 +11,4 @@ go mod init m
 cmp stderr stderr-expected
 
 -- stderr-expected --
-go get: inconsistent versions:
-       example.com/newcycle/a@v1.0.0 requires example.com/newcycle/a@v1.0.1 (not example.com/newcycle/a@v1.0.0)
+go get: example.com/newcycle/a@v1.0.0 requires example.com/newcycle/a@v1.0.1, not example.com/newcycle/a@v1.0.0
diff --git a/src/cmd/go/testdata/script/mod_get_patch.txt b/src/cmd/go/testdata/script/mod_get_patch.txt
new file mode 100644 (file)
index 0000000..053ef62
--- /dev/null
@@ -0,0 +1,130 @@
+# This test examines the behavior of 'go get …@patch'
+# See also mod_upgrade_patch.txt (focused on "-u=patch" specifically)
+# and mod_get_patchmod.txt (focused on module/package ambiguities).
+
+cp go.mod go.mod.orig
+
+# example.net/b@patch refers to the patch for the version of b that was selected
+# at the start of 'go get', not the version after applying other changes.
+
+! go get -d example.net/a@v0.2.0 example.net/b@patch
+stderr '^go get: example.net/a@v0.2.0 requires example.net/b@v0.2.0, not example.net/b@patch \(v0.1.1\)$'
+cmp go.mod go.mod.orig
+
+
+# -u=patch changes the default version for other arguments to '@patch',
+# but they continue to be resolved against the originally-selected version,
+# not the updated one.
+#
+# TODO(#42360): Reconsider the change in defaults.
+
+! go get -d -u=patch example.net/a@v0.2.0 example.net/b
+stderr '^go get: example.net/a@v0.2.0 requires example.net/b@v0.2.0, not example.net/b@patch \(v0.1.1\)$'
+cmp go.mod go.mod.orig
+
+
+# -u=patch refers to the patches for the selected versions of dependencies *after*
+# applying other version changes, not the versions that were selected at the start.
+# However, it should not patch versions determined by explicit arguments.
+
+go get -d -u=patch example.net/a@v0.2.0
+go list -m all
+stdout '^example.net/a v0.2.0 '
+stdout '^example.net/b v0.2.1 '
+
+
+# "-u=patch all" should be equivalent to "all@patch", and should fail if the
+# patched versions result in a higher-than-patch upgrade.
+
+cp go.mod.orig go.mod
+! go get -u=patch all
+stderr '^go get: example.net/a@v0.1.1 \(matching all@patch\) requires example.net/b@v0.2.0, not example.net/b@v0.1.1 \(matching all@patch\)$'
+cmp go.mod go.mod.orig
+
+
+# On the other hand, "-u=patch ./..." should patch-upgrade dependencies until
+# they reach a fixed point, even if that results in higher-than-patch upgrades.
+
+go get -u=patch ./...
+go list -m all
+stdout '^example.net/a v0.1.1 '
+stdout '^example.net/b v0.2.1 '
+
+
+-- go.mod --
+module example
+
+go 1.16
+
+require (
+       example.net/a v0.1.0
+       example.net/b v0.1.0  // indirect
+)
+
+replace (
+       example.net/a v0.1.0 => ./a10
+       example.net/a v0.1.1 => ./a11
+       example.net/a v0.2.0 => ./a20
+       example.net/a v0.2.1 => ./a21
+       example.net/b v0.1.0 => ./b
+       example.net/b v0.1.1 => ./b
+       example.net/b v0.2.0 => ./b
+       example.net/b v0.2.1 => ./b
+       example.net/b v0.3.0 => ./b
+       example.net/b v0.3.1 => ./b
+)
+-- example.go --
+package example
+
+import _ "example.net/a"
+
+-- a10/go.mod --
+module example.net/a
+
+go 1.16
+
+require example.net/b v0.1.0
+-- a10/a.go --
+package a
+
+import _ "example.net/b"
+
+-- a11/go.mod --
+module example.net/a
+
+go 1.16
+
+require example.net/b v0.2.0  // upgraded
+-- a11/a.go --
+package a
+
+import _ "example.net/b"
+
+-- a20/go.mod --
+module example.net/a
+
+go 1.16
+
+require example.net/b v0.2.0
+-- a20/a.go --
+package a
+
+import _ "example.net/b"
+
+-- a21/go.mod --
+module example.net/a
+
+go 1.16
+
+require example.net/b v0.2.0  // not upgraded
+-- a21/a.go --
+package a
+
+import _ "example.net/b"
+
+-- b/go.mod --
+module example.net/b
+
+go 1.16
+-- b/b.go --
+package b
diff --git a/src/cmd/go/testdata/script/mod_get_patchbound.txt b/src/cmd/go/testdata/script/mod_get_patchbound.txt
new file mode 100644 (file)
index 0000000..4fd1ec5
--- /dev/null
@@ -0,0 +1,84 @@
+# -u=patch will patch dependencies as far as possible, but not so far that they
+# conflict with other command-line arguments.
+
+go list -m all
+stdout '^example.net/a v0.1.0 '
+stdout '^example.net/b v0.1.0 '
+
+go get -d -u=patch example.net/a@v0.2.0
+go list -m all
+stdout '^example.net/a v0.2.0 '
+stdout '^example.net/b v0.1.1 '  # not v0.1.2, which requires …/a v0.3.0.
+
+-- go.mod --
+module example
+
+go 1.16
+
+require (
+       example.net/a v0.1.0
+       example.net/b v0.1.0 // indirect
+)
+
+replace (
+       example.net/a v0.1.0 => ./a
+       example.net/a v0.2.0 => ./a
+       example.net/a v0.3.0 => ./a
+       example.net/b v0.1.0 => ./b10
+       example.net/b v0.1.1 => ./b11
+       example.net/b v0.1.2 => ./b12
+)
+-- example.go --
+package example
+
+import _ "example.net/a"
+
+-- a/go.mod --
+module example.net/a
+
+go 1.16
+
+require example.net/b v0.1.0
+-- a/a.go --
+package a
+
+import _ "example.net/b"
+
+-- b10/go.mod --
+module example.net/b
+
+go 1.16
+
+require example.net/a v0.1.0
+-- b10/b.go --
+package b
+-- b10/b_test.go --
+package b_test
+
+import _ "example.net/a"
+
+-- b11/go.mod --
+module example.net/b
+
+go 1.16
+
+require example.net/a v0.2.0
+-- b11/b.go --
+package b
+-- b11/b_test.go --
+package b_test
+
+import _ "example.net/a"
+
+-- b12/go.mod --
+module example.net/b
+
+go 1.16
+
+require example.net/a v0.3.0
+-- b12/b.go --
+package b
+-- b12/b_test.go --
+package b_test
+
+import _ "example.net/a"
diff --git a/src/cmd/go/testdata/script/mod_get_patchcycle.txt b/src/cmd/go/testdata/script/mod_get_patchcycle.txt
new file mode 100644 (file)
index 0000000..d1db56f
--- /dev/null
@@ -0,0 +1,64 @@
+# If a patch of a module requires a higher version of itself,
+# it should be reported as its own conflict.
+#
+# This case is weird and unlikely to occur often at all, but it should not
+# spuriously succeed.
+# (It used to print v0.1.1 but then silently upgrade to v0.2.0.)
+
+! go get example.net/a@patch
+stderr '^go get: example.net/a@patch \(v0.1.1\) requires example.net/a@v0.2.0, not example.net/a@patch \(v0.1.1\)$'  # TODO: A mention of b v0.1.0 would be nice.
+
+-- go.mod --
+module example
+
+go 1.16
+
+require example.net/a v0.1.0
+
+replace (
+       example.net/a v0.1.0 => ./a10
+       example.net/a v0.1.1 => ./a11
+       example.net/a v0.2.0 => ./a20
+       example.net/b v0.1.0 => ./b10
+)
+-- example.go --
+package example
+
+import _ "example.net/a"
+
+-- a10/go.mod --
+module example.net/a
+
+go 1.16
+-- a10/a.go --
+package a
+
+-- a11/go.mod --
+module example.net/a
+
+go 1.16
+
+require example.net/b v0.1.0
+-- a11/a.go --
+package a
+
+import _ "example.net/b"
+
+-- a20/go.mod --
+module example.net/a
+
+go 1.16
+-- a20/a.go --
+package a
+
+
+-- b10/go.mod --
+module example.net/b
+
+go 1.16
+
+require example.net/a v0.2.0
+-- b10/b.go --
+package b
+
+import _ "example.net/a"
index 0f4e2e1647f3ff64a99881e59a9832793c834522..e39d13a0f4184e515e14f7b097f5ddd0d7935491 100644 (file)
@@ -4,20 +4,41 @@ go get -d example.net/pkgremoved@v0.1.0
 go list example.net/pkgremoved
 stdout '^example.net/pkgremoved'
 
+cp go.mod go.mod.orig
+
+
 # When we resolve a new dependency on example.net/other,
 # it will change the meaning of the path "example.net/pkgremoved"
 # from a package (at v0.1.0) to only a module (at v0.2.0).
 #
 # If we simultaneously 'get' that module at the query "patch", the module should
-# be upgraded to its patch release (v0.2.1) even though it no longer matches a
-# package.
-#
-# BUG(#37438): Today, the pattern is only interpreted as its initial kind
-# (a package), so the 'go get' invocation fails.
+# be constrained to the latest patch of its originally-selected version (v0.1.0),
+# not upgraded to the latest patch of the new transitive dependency.
 
 ! go get -d example.net/pkgremoved@patch example.net/other@v0.1.0
+stderr '^go get: example.net/other@v0.1.0 requires example.net/pkgremoved@v0.2.0, not example.net/pkgremoved@patch \(v0.1.1\)$'
+cmp go.mod.orig go.mod
+
+
+# However, we should be able to patch from a package to a module and vice-versa.
+
+# Package to module ...
+
+go get -d example.net/pkgremoved@v0.3.0
+go list example.net/pkgremoved
+stdout 'example.net/pkgremoved'
 
-stderr '^go get example.net/pkgremoved@patch: module example.net/pkgremoved@latest found \(v0.2.1, replaced by ./pr2\), but does not contain package example.net/pkgremoved$'
+go get -d example.net/pkgremoved@patch
+! go list example.net/pkgremoved
+
+# ... and module to package.
+
+go get -d example.net/pkgremoved@v0.4.0
+! go list example.net/pkgremoved
+
+go get -d example.net/pkgremoved@patch
+go list example.net/pkgremoved
+stdout 'example.net/pkgremoved'
 
 
 -- go.mod --
@@ -27,10 +48,18 @@ go 1.16
 
 replace (
        example.net/other v0.1.0 => ./other
-  example.net/pkgremoved v0.1.0 => ./pr1
-  example.net/pkgremoved v0.1.1 => ./pr1
-  example.net/pkgremoved v0.2.0 => ./pr2
-  example.net/pkgremoved v0.2.1 => ./pr2
+
+       example.net/pkgremoved v0.1.0 => ./prpkg
+       example.net/pkgremoved v0.1.1 => ./prpkg
+
+       example.net/pkgremoved v0.2.0 => ./prmod
+       example.net/pkgremoved v0.2.1 => ./prmod
+
+       example.net/pkgremoved v0.3.0 => ./prpkg
+       example.net/pkgremoved v0.3.1 => ./prmod
+
+       example.net/pkgremoved v0.4.0 => ./prmod
+       example.net/pkgremoved v0.4.1 => ./prpkg
 )
 -- other/go.mod --
 module example.net/other
@@ -40,13 +69,14 @@ go 1.16
 require example.net/pkgremoved v0.2.0
 -- other/other.go --
 package other
--- pr1/go.mod --
+-- prpkg/go.mod --
 module example.net/pkgremoved
 
 go 1.16
--- pr1/pkgremoved.go --
+-- prpkg/pkgremoved.go --
 package pkgremoved
--- pr2/go.mod --
+-- prmod/go.mod --
 module example.net/pkgremoved
--- pr2/README.txt --
-Package pkgremoved was removed in v0.2.0.
+-- prmod/README.txt --
+Package pkgremoved was removed in v0.2.0 and v0.3.1,
+and added in v0.1.0 and v0.4.1.
index 8adc4b0c06579d96896e68f70faedf6b5dc7714b..aee4374dc8a88a5dff020078c9e392178d9e7ba1 100644 (file)
@@ -10,21 +10,27 @@ grep 'require rsc.io/quote' go.mod
 
 cp go.mod.orig go.mod
 ! go get -d rsc.io/quote/x...
-stderr 'go get rsc.io/quote/x...: module rsc.io/quote@upgrade found \(v1.5.2\), but does not contain packages matching rsc.io/quote/x...'
+stderr 'go get: module rsc.io/quote@upgrade found \(v1.5.2\), but does not contain packages matching rsc.io/quote/x...'
 ! grep 'require rsc.io/quote' go.mod
 
 ! go get -d rsc.io/quote/x/...
-stderr 'go get rsc.io/quote/x/...: module rsc.io/quote@upgrade found \(v1.5.2\), but does not contain packages matching rsc.io/quote/x/...'
+stderr 'go get: module rsc.io/quote@upgrade found \(v1.5.2\), but does not contain packages matching rsc.io/quote/x/...'
 ! grep 'require rsc.io/quote' go.mod
 
 # If a pattern matches no packages within a module, the module should not
-# be upgraded, even if the module path matches the pattern.
+# be upgraded, even if the module path is a prefix of the pattern.
 cp go.mod.orig go.mod
 go mod edit -require example.com/nest@v1.0.0
 go get -d example.com/nest/sub/y...
 grep 'example.com/nest/sub v1.0.0' go.mod
 grep 'example.com/nest v1.0.0' go.mod
 
+# However, if the pattern matches the module path itself, the module
+# should be upgraded even if it contains no matching packages.
+go get -d example.com/n...t
+grep 'example.com/nest v1.1.0' go.mod
+grep 'example.com/nest/sub v1.0.0' go.mod
+
 -- go.mod.orig --
 module m
 
index f8386059008da80d9501086e283d27732209823f..76d0793ffef7c48587ede6d584636b8b85a12089 100644 (file)
@@ -82,7 +82,7 @@ cp go.mod.orig go.mod
 ! go list example
 stderr '^package example is not in GOROOT \(.*\)$'
 ! go get -d example
-stderr '^go get example: package example is not in GOROOT \(.*\)$'
+stderr '^go get: malformed module path "example": missing dot in first path element$'
 
 go mod edit -replace example@v0.1.0=./example
 
diff --git a/src/cmd/go/testdata/script/mod_get_split.txt b/src/cmd/go/testdata/script/mod_get_split.txt
new file mode 100644 (file)
index 0000000..f4e7661
--- /dev/null
@@ -0,0 +1,157 @@
+cp go.mod go.mod.orig
+
+
+# 'go get' on a package already provided by the build list should update
+# the module already in the build list, not fail with an ambiguous import error.
+
+go get -d example.net/split/nested@patch
+go list -m all
+stdout '^example.net/split v0.2.1 '
+! stdout '^example.net/split/nested'
+
+# We should get the same behavior if we use a pattern that matches only that package.
+
+cp go.mod.orig go.mod
+
+go get -d example.net/split/nested/...@patch
+go list -m all
+stdout '^example.net/split v0.2.1 '
+! stdout '^example.net/split/nested'
+
+
+# If we request a version for which the package only exists in one particular module,
+# we should add that one particular module but not resolve import ambiguities.
+#
+# In particular, if the module that previously provided the package has a
+# matching version, but does not itself match the pattern and contains no
+# matching packages, we should not change its version. (We should *not* downgrade
+# module example.net/split to v0.1.0, despite the fact that
+# example.net/split v0.2.0 currently provides the package with the requested path.)
+#
+# TODO(#27899): Maybe we should resolve the ambiguities by upgrading.
+
+cp go.mod.orig go.mod
+
+! go get -d example.net/split/nested@v0.1.0
+stderr '^example.net/split/nested: ambiguous import: found package example.net/split/nested in multiple modules:\n\texample.net/split v0.2.0 \(.*split.2[/\\]nested\)\n\texample.net/split/nested v0.1.0 \(.*nested.1\)$'
+
+# A wildcard that matches packages in some module at its selected version
+# but not at the requested version should fail.
+#
+# We can't set the module to the selected version, because that version doesn't
+# even match the query: if we ran the same query twice, we wouldn't consider the
+# module to match the wildcard during the second call, so why should we consider
+# it to match during the first one?  ('go get' should be idempotent, and if we
+# did that then it would not be.)
+#
+# But we also can't leave it where it is: the user requested that we set everything
+# matching the pattern to the given version, and right now we have packages
+# that match the pattern but *not* the version.
+#
+# That only leaves two options: we can set the module to an arbitrary version
+# (perhaps 'latest' or 'none'), or we can report an error and the let the user
+# disambiguate. We would rather not choose arbitrarily, so we do the latter.
+#
+# TODO(#27899): Should we instead upgrade or downgrade to an arbirary version?
+
+! go get -d example.net/split/nested/...@v0.1.0
+stderr '^go get: example.net/split/nested/\.\.\.@v0.1.0 matches packages in example.net/split@v0.2.0 but not example.net/split@v0.1.0: specify a different version for module example.net/split$'
+
+cmp go.mod go.mod.orig
+
+
+# If another argument resolves the ambiguity, we should be ok again.
+
+go get -d example.net/split@none example.net/split/nested@v0.1.0
+go list -m all
+! stdout '^example.net/split '
+stdout '^example.net/split/nested v0.1.0 '
+
+cp go.mod.orig go.mod
+
+go get -d example.net/split@v0.3.0 example.net/split/nested@v0.1.0
+go list -m all
+stdout '^example.net/split v0.3.0 '
+stdout '^example.net/split/nested v0.1.0 '
+
+
+# If a pattern applies to modules and to packages, we should set all matching
+# modules to the version indicated by the pattern, and also resolve packages
+# to match the pattern if possible.
+
+cp go.mod.orig go.mod
+go get -d example.net/split/nested@v0.0.0
+
+go get -d example.net/...@v0.1.0
+go list -m all
+stdout '^example.net/split v0.1.0 '
+stdout '^example.net/split/nested v0.1.0 '
+
+go get -d example.net/...
+go list -m all
+stdout '^example.net/split v0.3.0 '
+stdout '^example.net/split/nested v0.2.0 '
+
+
+# @none applies to all matching module paths,
+# regardless of whether they contain any packages.
+
+go get -d example.net/...@none
+go list -m all
+! stdout '^example.net'
+
+# Starting from no dependencies, a wildcard can resolve to an empty module with
+# the same prefix even if it contains no packages.
+
+go get -d example.net/...@none
+go get -d example.net/split/...@v0.1.0
+go list -m all
+stdout '^example.net/split v0.1.0 '
+
+
+-- go.mod --
+module m
+
+go 1.16
+
+require example.net/split v0.2.0
+
+replace (
+       example.net/split v0.1.0 => ./split.1
+       example.net/split v0.2.0 => ./split.2
+       example.net/split v0.2.1 => ./split.2
+       example.net/split v0.3.0 => ./split.3
+       example.net/split/nested v0.0.0 => ./nested.0
+       example.net/split/nested v0.1.0 => ./nested.1
+       example.net/split/nested v0.2.0 => ./nested.2
+)
+-- split.1/go.mod --
+module example.net/split
+
+go 1.16
+-- split.2/go.mod --
+module example.net/split
+
+go 1.16
+-- split.2/nested/nested.go --
+package nested
+-- split.3/go.mod --
+module example.net/split
+
+go 1.16
+-- nested.0/go.mod --
+module example.net/split/nested
+
+go 1.16
+-- nested.1/go.mod --
+module example.net/split/nested
+
+go 1.16
+-- nested.1/nested.go --
+package nested
+-- nested.2/go.mod --
+module example.net/split/nested
+
+go 1.16
+-- nested.2/nested.go --
+package nested
diff --git a/src/cmd/go/testdata/script/mod_get_wild.txt b/src/cmd/go/testdata/script/mod_get_wild.txt
new file mode 100644 (file)
index 0000000..78c645c
--- /dev/null
@@ -0,0 +1,95 @@
+# This test covers a crazy edge-case involving wildcards and multiple passes of
+# patch-upgrades, but if we get it right we probably get many other edge-cases
+# right too.
+
+go list -m all
+stdout '^example.net/a v0.1.0 '
+! stdout '^example.net/b '
+
+
+# Requesting pattern example.../b by itself fails: there is no such module
+# already in the build list, and the wildcard in the first element prevents us
+# from attempting to resolve a new module whose path is a prefix of the pattern.
+
+! go get -d -u=patch example.../b@upgrade
+stderr '^go get: no modules to query for example\.\.\./b@upgrade because first path element contains a wildcard$'
+
+
+# Patching . causes a patch to example.net/a, which introduces a new match
+# for example.net/b/..., which is itself patched and causes another upgrade to
+# example.net/a, which is then patched again.
+
+go get -d -u=patch . example.../b@upgrade
+go list -m all
+stdout '^example.net/a v0.2.1 '  # upgraded by dependency of b and -u=patch
+stdout '^example.net/b v0.2.0 '  # introduced by patch of a and upgraded by wildcard
+
+
+-- go.mod --
+module example
+
+go 1.16
+
+require example.net/a v0.1.0
+
+replace (
+       example.net/a v0.1.0 => ./a10
+       example.net/a v0.1.1 => ./a11
+       example.net/a v0.2.0 => ./a20
+       example.net/a v0.2.1 => ./a20
+       example.net/b v0.1.0 => ./b1
+       example.net/b v0.1.1 => ./b1
+       example.net/b v0.2.0 => ./b2
+)
+-- example.go --
+package example
+
+import _ "example.net/a"
+
+-- a10/go.mod --
+module example.net/a
+
+go 1.16
+-- a10/a.go --
+package a
+
+-- a11/go.mod --
+module example.net/a
+
+go 1.16
+
+require example.net/b v0.1.0
+-- a11/a.go --
+package a
+-- a11/unimported/unimported.go --
+package unimported
+
+import _ "example.net/b"
+
+
+-- a20/go.mod --
+module example.net/a
+
+go 1.16
+-- a20/a.go --
+package a
+
+-- b1/go.mod --
+module example.net/b
+
+go 1.16
+-- b1/b.go --
+package b
+
+-- b2/go.mod --
+module example.net/b
+
+go 1.16
+
+require example.net/a v0.2.0
+-- b2/b.go --
+package b
+-- b2/b_test.go --
+package b_test
+
+import _ "example.net/a"
index f9dfdd6346219719bfd54a8818c04e2bb0e6f5dc..43b9564356100a02cea4f9c00ae394930748db37 100644 (file)
@@ -222,7 +222,7 @@ stdout 'github.com/pierrec/lz4 v2.0.5\+incompatible'
 # not resolve to a pseudo-version with a different major version.
 cp go.mod.orig go.mod
 ! go get -d github.com/pierrec/lz4@v2.0.8
-stderr 'go get github.com/pierrec/lz4@v2.0.8: github.com/pierrec/lz4@v2.0.8: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2'
+stderr 'go get: github.com/pierrec/lz4@v2.0.8: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2'
 
 # An invalid +incompatible suffix for a canonical version should error out,
 # not resolve to a pseudo-version.
index a71c4a849e502fb38fcdaa4b792c39dc754473a1..c0c382bfa638b0fcb71473c560704179e7726795 100644 (file)
@@ -69,14 +69,13 @@ import (
 
 func Test(t *testing.T) {}
 -- update-main-expected --
-go: example.com/badchain/c upgrade => v1.1.0
 go get: example.com/badchain/c@v1.0.0 updating to
        example.com/badchain/c@v1.1.0: parsing go.mod:
        module declares its path as: badchain.example.com/c
                but was required as: example.com/badchain/c
 -- update-a-expected --
-go: example.com/badchain/a upgrade => v1.1.0
-go get: example.com/badchain/a@v1.1.0 requires
+go get: example.com/badchain/a@v1.0.0 updating to
+       example.com/badchain/a@v1.1.0 requires
        example.com/badchain/b@v1.1.0 requires
        example.com/badchain/c@v1.1.0: parsing go.mod:
        module declares its path as: badchain.example.com/c
index d969fce145e9da94db16af4bf516ffcccb9423b8..28379ab40d886f090977526c98c2a019c397fcab 100644 (file)
@@ -130,12 +130,12 @@ stderr 'cannot find main module'
 # 'go get -u all' upgrades the transitive import graph of the main module,
 # which is empty.
 ! go get -u all
-stderr 'go get all: cannot match "all": working directory is not part of a module'
+stderr 'go get: cannot match "all": working directory is not part of a module'
 
 # 'go get' should check the proposed module graph for consistency,
 # even though we won't write it anywhere.
 ! go get -d example.com/printversion@v1.0.0 example.com/version@none
-stderr 'inconsistent versions'
+stderr '^go get: example.com/printversion@v1.0.0 requires example.com/version@v1.0.0, not example.com/version@none$'
 
 # 'go get -d' should download and extract the source code needed to build the requested version.
 rm -r $GOPATH/pkg/mod/example.com
index a07a07c4bcc476a6dbcac9ba1b8d5ee03f311f43..1f13d7ad693e901ebf1121487794197b4c37a774 100644 (file)
@@ -8,7 +8,7 @@ go mod download example.com/join@v1.1.0
 env GOPROXY=file:///$WORK/badproxy
 cp go.mod.orig go.mod
 ! go get -d example.com/join/subpkg
-stderr 'go get example.com/join/subpkg: example.com/join/subpkg@v0.0.0-20190624000000-123456abcdef: .*'
+stderr 'go get: example.com/join/subpkg@v0.0.0-20190624000000-123456abcdef: .*'
 
 # If @v/list is empty, the 'go' command should still try to resolve
 # other module paths.
@@ -40,7 +40,7 @@ env GOPROXY=file:///$WORK/gatekeeper
 chmod 0000 $WORK/gatekeeper/example.com/join/subpkg/@latest
 cp go.mod.orig go.mod
 ! go get -d example.com/join/subpkg
-stderr 'go get example.com/join/subpkg: module example.com/join/subpkg: (invalid response from proxy ".+": json: invalid character .+|reading file://.*/gatekeeper/example.com/join/subpkg/@latest: .+)'
+stderr 'go get: module example.com/join/subpkg: (invalid response from proxy ".+": json: invalid character .+|reading file://.*/gatekeeper/example.com/join/subpkg/@latest: .+)'
 
 -- go.mod.orig --
 module example.com/othermodule
index 742c6f17e34de3026544a73dd6d6b599d8d40908..b0019694119bb17b2e95bfd180055aafc32daafb 100644 (file)
@@ -19,7 +19,7 @@ stdout '^rsc.io/quote v1.5.1$'
 # get excluded version
 cp go.exclude.mod go.exclude.mod.orig
 ! go get -modfile=go.exclude.mod -d rsc.io/quote@v1.5.0
-stderr '^go get rsc.io/quote@v1.5.0: rsc.io/quote@v1.5.0: excluded by go.mod$'
+stderr '^go get: rsc.io/quote@v1.5.0: excluded by go.mod$'
 
 # get non-excluded version
 cp go.exclude.mod.orig go.exclude.mod
index 93609f36c954a5755566103bb992aa45763150d6..eb00e8405c01073740f44c1d769647324182b092 100644 (file)
@@ -29,7 +29,7 @@ go list -m vcs-test.golang.org/git/retract-pseudo.git
 stdout '^vcs-test.golang.org/git/retract-pseudo.git v1.0.1-0.20201009173747-713affd19d7b$'
 
 ! go get -d vcs-test.golang.org/git/retract-pseudo.git@v1.0.1-0.20201009173747-64c061ed4371
-stderr '^go get vcs-test.golang.org/git/retract-pseudo.git@v1.0.1-0.20201009173747-64c061ed4371: vcs-test.golang.org/git/retract-pseudo.git@v1.0.1-0.20201009173747-64c061ed4371: invalid pseudo-version: tag \(v1.0.0\) found on revision 64c061ed4371 is already canonical, so should not be replaced with a pseudo-version derived from that tag$'
+stderr '^go get: vcs-test.golang.org/git/retract-pseudo.git@v1.0.1-0.20201009173747-64c061ed4371: invalid pseudo-version: tag \(v1.0.0\) found on revision 64c061ed4371 is already canonical, so should not be replaced with a pseudo-version derived from that tag$'
 
 -- retract-pseudo.sh --
 #!/bin/bash
index fb320a557abf8e02aa3f535caed3c2e0da8dfc4e..9a688e1461ca94efdce410cafbc9d4f06146fd1d 100644 (file)
@@ -9,7 +9,7 @@ env dbname=localhost.localdev/sumdb
 cp go.mod.orig go.mod
 env GOSUMDB=$sumdb' '$proxy/sumdb-wrong
 ! go get -d rsc.io/quote
-stderr 'go get rsc.io/quote: rsc.io/quote@v1.5.2: verifying module: checksum mismatch'
+stderr 'go get: rsc.io/quote@v1.5.2: verifying module: checksum mismatch'
 stderr 'downloaded: h1:3fEy'
 stderr 'localhost.localdev/sumdb: h1:wrong'
 stderr 'SECURITY ERROR\nThis download does NOT match the one reported by the checksum server.'
index 6108c0a5d36f1979433a4235fb9ab1b6adfa30bc..22fcbf3de84a71f92d3ebbd83aa56377d9485f0f 100644 (file)
@@ -13,7 +13,7 @@ env GOPATH=$WORK/gopath1
 [windows] env GOPROXY=file:///$WORK/sumproxy,https://proxy.golang.org
 [!windows] env GOPROXY=file://$WORK/sumproxy,https://proxy.golang.org
 ! go get -d golang.org/x/text@v0.3.2
-stderr '^go get golang.org/x/text@v0.3.2: golang.org/x/text@v0.3.2: verifying module: golang.org/x/text@v0.3.2: reading file://.*/sumdb/sum.golang.org/lookup/golang.org/x/text@v0.3.2: (no such file or directory|.*cannot find the path specified.*)'
+stderr '^go get: golang.org/x/text@v0.3.2: verifying module: golang.org/x/text@v0.3.2: reading file://.*/sumdb/sum.golang.org/lookup/golang.org/x/text@v0.3.2: (no such file or directory|.*cannot find the path specified.*)'
 
 # If the proxy does not claim to support the database,
 # checksum verification should fall through to the next proxy,
index 1ef25b9aef56345bb3396dacc5910e389a9e0ab7..8b34f8bf27dfc66070075e4efe2efe78fa2e20ad 100644 (file)
@@ -9,6 +9,11 @@ stdout '^patch.example.com/direct v1.0.0'
 stdout '^patch.example.com/indirect v1.0.0'
 ! stdout '^patch.example.com/depofdirectpatch'
 
+# @patch should be rejected for modules not already in the build list.
+! go get -d patch.example.com/depofdirectpatch@patch
+stderr '^go get: can''t query version "patch" of module patch.example.com/depofdirectpatch: no existing version is required$'
+cmp go.mod.orig go.mod
+
 # get -u=patch, with no arguments, should patch-update all dependencies
 # of the package in the current directory, pulling in transitive dependencies
 # and also patching those.
@@ -19,7 +24,7 @@ stdout '^patch.example.com/direct v1.0.1'
 stdout '^patch.example.com/indirect v1.0.1'
 stdout '^patch.example.com/depofdirectpatch v1.0.0'
 
-# 'get all@patch' should be equivalent to 'get -u=patch all'
+# 'get all@patch' should patch the modules that provide packages in 'all'.
 cp go.mod.orig go.mod
 go get -d all@patch
 go list -m all
@@ -27,6 +32,15 @@ stdout '^patch.example.com/direct v1.0.1'
 stdout '^patch.example.com/indirect v1.0.1'
 stdout '^patch.example.com/depofdirectpatch v1.0.0'
 
+# ...but 'all@patch' should fail if any of the affected modules do not already
+# have a selected version.
+cp go.mod.orig go.mod
+go mod edit -droprequire=patch.example.com/direct
+cp go.mod go.mod.dropped
+! go get -d all@patch
+stderr '^go get all@patch: can''t query version "patch" of module patch.example.com/direct: no existing version is required$'
+cmp go.mod.dropped go.mod
+
 # 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
@@ -69,10 +83,10 @@ 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.
+# 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'
+stderr 'go get: can''t request explicit version "patch" of standard library package cmd/vet$'
 
 # However, standard-library packages without explicit versions are fine.
 go get -d -u=patch -d cmd/go
@@ -85,6 +99,7 @@ go get -d example.com/noroot@patch
 go list -m all
 stdout '^example.com/noroot v1.0.1$'
 
+
 -- go.mod --
 module x