Commit 4c2ffd26 authored by Jay Conrod's avatar Jay Conrod

cmd/go: avoid accidental downgrades in 'go get' with latest and patch

Currently, 'go get -u' and 'go get -u=patch' avoid accidentally
downgrading modules by preventing upgrades in two cases:

1) If the current version is a prerelease that is semantically later
   than the "latest" or "patch" version.
2) If the current version is a pseudoversion that is chronologically
   newer than the "latest" or "patch" version.

With this change, 'go get m@latest' and 'go get m@patch' prevent
downgrades using the same checks.

Also: 'go get m@patch' now works if m is a module path but not a
package path (i.e., there is no package in the module root directory).

Fixes #30634
Fixes #32537

Change-Id: I916630c385b5f3ba7c13e0d65ba08f73a1a67829
Reviewed-on: https://go-review.googlesource.com/c/go/+/180337
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBryan C. Mills <bcmills@google.com>
parent 80f89133
...@@ -586,11 +586,12 @@ ...@@ -586,11 +586,12 @@
// depending on it as needed. // 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 of the
// given path. // given path. The suffix @patch requests the latest patch release: if the path
// // is already in the build list, the selected version will have the same minor
// The suffix @patch requests the latest patch release: if the path is already in // version. If the path is not already in the build list, @patch is equivalent
// the build list, the selected version will have the same minor version. // to @latest. Neither @latest nor @patch will cause 'go get' to downgrade a module
// If the path is not already in the build list, @patch is equivalent to @latest. // in the build list if it is required at a newer pre-release version that is
// newer than the latest released version.
// //
// Although get defaults to using the latest version of the module containing // 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 // a named package, it does not use the latest version of that module's
......
...@@ -11,7 +11,6 @@ import ( ...@@ -11,7 +11,6 @@ import (
"cmd/go/internal/get" "cmd/go/internal/get"
"cmd/go/internal/imports" "cmd/go/internal/imports"
"cmd/go/internal/load" "cmd/go/internal/load"
"cmd/go/internal/modfetch"
"cmd/go/internal/modload" "cmd/go/internal/modload"
"cmd/go/internal/module" "cmd/go/internal/module"
"cmd/go/internal/mvs" "cmd/go/internal/mvs"
...@@ -60,11 +59,12 @@ dependency should be removed entirely, downgrading or removing modules ...@@ -60,11 +59,12 @@ dependency should be removed entirely, downgrading or removing modules
depending on it as needed. 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 of the
given path. given path. The suffix @patch requests the latest patch release: if the path
is already in the build list, the selected version will have the same minor
The suffix @patch requests the latest patch release: if the path is already in version. If the path is not already in the build list, @patch is equivalent
the build list, the selected version will have the same minor version. to @latest. Neither @latest nor @patch will cause 'go get' to downgrade a module
If the path is not already in the build list, @patch is equivalent to @latest. in the build list if it is required at a newer pre-release version that is
newer than the latest released version.
Although get defaults to using the latest version of the module containing 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 a named package, it does not use the latest version of that module's
...@@ -219,10 +219,13 @@ type querySpec struct { ...@@ -219,10 +219,13 @@ type querySpec struct {
vers string vers string
// forceModulePath is true if path should be interpreted as a module path. // forceModulePath is true if path should be interpreted as a module path.
// If forceModulePath is true, prevM must be set.
forceModulePath bool forceModulePath bool
// prevM is the previous version of the module. prevM is needed // prevM is the previous version of the module. prevM is needed
// if vers is "patch", and the module was previously in the build list. // 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 prevM module.Version
} }
...@@ -266,7 +269,12 @@ func runGet(cmd *base.Command, args []string) { ...@@ -266,7 +269,12 @@ func runGet(cmd *base.Command, args []string) {
base.Fatalf("go get: disabled by -mod=%s", cfg.BuildMod) base.Fatalf("go get: disabled by -mod=%s", cfg.BuildMod)
} }
modload.LoadBuildList() buildList := modload.LoadBuildList()
buildList = buildList[:len(buildList):len(buildList)] // copy on append
versionByPath := make(map[string]string)
for _, m := range buildList {
versionByPath[m.Path] = m.Version
}
// Do not allow any updating of go.mod until we've applied // Do not allow any updating of go.mod until we've applied
// all the requested changes and checked that the result matches // all the requested changes and checked that the result matches
...@@ -356,33 +364,66 @@ func runGet(cmd *base.Command, args []string) { ...@@ -356,33 +364,66 @@ func runGet(cmd *base.Command, args []string) {
continue continue
} }
if vers == "patch" { first := path
// We need to know the previous version of the module to find if i := strings.IndexByte(first, '/'); i >= 0 {
// the new version, but we don't know what module provides this first = path
// package yet. Wait until we load packages later. }
// TODO(golang.org/issue/30634): @latest should also depend on if !strings.Contains(first, ".") {
// the current version to prevent downgrading from newer pseudoversions. // The path doesn't have a dot in the first component and cannot be
} else { // queried as a module. It may be a package in the standard library,
// The requested version of path doesn't depend on the existing version, // which is fine, so don't report an error unless we encounter
// so query the module before loading the package. This may let us // a problem loading packages below.
// load the package only once at the correct version. continue
queries = append(queries, &query{querySpec: querySpec{path: path, vers: vers}, arg: arg}) }
// If we're querying "latest" or "patch", we need to know the current
// version of the module. For "latest", 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: path, vers: vers}, arg: arg}
if v, ok := versionByPath[path]; ok && path != modload.Target.Path {
q.prevM = module.Version{Path: path, Version: v}
q.forceModulePath = true
} }
queries = append(queries, q)
} }
} }
base.ExitIfErrors() base.ExitIfErrors()
// Query modules referenced by command line arguments at requested versions, // Query modules referenced by command line arguments at requested versions.
// and add them to the build list. We need to do this before loading packages // We need to do this before loading packages since patterns that refer to
// since patterns that refer to packages in unknown modules can't be // packages in unknown modules can't be expanded. This also avoids looking
// expanded. This also avoids looking up new modules while loading packages, // up new modules while loading packages, only to downgrade later.
// only to downgrade later.
queryCache := make(map[querySpec]*query) queryCache := make(map[querySpec]*query)
byPath := runQueries(queryCache, queries, nil) byPath := runQueries(queryCache, queries, nil)
// Add queried modules to the build list. This prevents some additional // Add missing modules to the build list.
// lookups for modules at "latest" when we load packages later. // We call SetBuildList here and elsewhere, since newUpgrader,
buildList, err := mvs.UpgradeAll(modload.Target, newUpgrader(byPath, nil)) // ImportPathsQuiet, and other functions read the global build list.
for _, q := range queries {
if _, ok := versionByPath[q.m.Path]; !ok && q.m.Version != "none" {
buildList = append(buildList, q.m)
}
}
versionByPath = 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 { if err != nil {
base.Fatalf("go get: %v", err) base.Fatalf("go get: %v", err)
} }
...@@ -478,6 +519,10 @@ func runGet(cmd *base.Command, args []string) { ...@@ -478,6 +519,10 @@ func runGet(cmd *base.Command, args []string) {
continue continue
} }
allStd = false 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}) addQuery(&query{querySpec: querySpec{path: m.Path, vers: arg.vers, forceModulePath: true, prevM: m}, arg: arg.raw})
} }
if allStd && arg.path != arg.raw { if allStd && arg.path != arg.raw {
...@@ -538,7 +583,6 @@ func runGet(cmd *base.Command, args []string) { ...@@ -538,7 +583,6 @@ func runGet(cmd *base.Command, args []string) {
// Scan for any upgrades lost by the downgrades. // Scan for any upgrades lost by the downgrades.
var lostUpgrades []*query var lostUpgrades []*query
var versionByPath map[string]string
if len(down) > 0 { if len(down) > 0 {
versionByPath = make(map[string]string) versionByPath = make(map[string]string)
for _, m := range modload.BuildList() { for _, m := range modload.BuildList() {
...@@ -680,15 +724,21 @@ func runQueries(cache map[querySpec]*query, queries []*query, modOnly map[string ...@@ -680,15 +724,21 @@ func runQueries(cache map[querySpec]*query, queries []*query, modOnly map[string
// If forceModulePath is set, getQuery must interpret path // If forceModulePath is set, getQuery must interpret path
// as a module path. // as a module path.
func getQuery(path, vers string, prevM module.Version, forceModulePath bool) (module.Version, error) { func getQuery(path, vers string, prevM module.Version, forceModulePath bool) (module.Version, error) {
switch vers { if (prevM.Version != "") != forceModulePath {
case "": // 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")
}
if vers == "" || vers == "patch" && prevM.Version == "" {
vers = "latest" vers = "latest"
case "patch":
if prevM.Version == "" {
vers = "latest"
} else {
vers = semver.MajorMinor(prevM.Version)
}
} }
if forceModulePath || !strings.Contains(path, "...") { if forceModulePath || !strings.Contains(path, "...") {
...@@ -699,7 +749,7 @@ func getQuery(path, vers string, prevM module.Version, forceModulePath bool) (mo ...@@ -699,7 +749,7 @@ func getQuery(path, vers string, prevM module.Version, forceModulePath bool) (mo
} }
// If the path doesn't contain a wildcard, try interpreting it as a module path. // If the path doesn't contain a wildcard, try interpreting it as a module path.
info, err := modload.Query(path, vers, modload.Allowed) info, err := modload.Query(path, vers, prevM.Version, modload.Allowed)
if err == nil { if err == nil {
return module.Version{Path: path, Version: info.Version}, nil return module.Version{Path: path, Version: info.Version}, nil
} }
...@@ -840,18 +890,14 @@ func (u *upgrader) Upgrade(m module.Version) (module.Version, error) { ...@@ -840,18 +890,14 @@ func (u *upgrader) Upgrade(m module.Version) (module.Version, error) {
} }
// Run query required by upgrade semantics. // Run query required by upgrade semantics.
// Note that query "latest" is not the same as // Note that Query "latest" is not the same as using repo.Latest,
// using repo.Latest. // which may return a pseudoversion for the latest commit.
// The query only falls back to untagged versions // Query "latest" returns the newest tagged version or the newest
// if nothing is tagged. The Latest method // prerelease version if there are no non-prereleases, or repo.Latest
// only ever returns untagged versions, // if there aren't any tagged versions. Since we're providing the previous
// which is not what we want. // version, Query will confirm the latest version is actually newer
query := "latest" // and will return the current version if not.
if getU == "patch" { info, err := modload.Query(m.Path, string(getU), m.Version, modload.Allowed)
// For patch upgrade, query "v1.2".
query = semver.MajorMinor(m.Version)
}
info, err := modload.Query(m.Path, query, modload.Allowed)
if err != nil { if err != nil {
// Report error but return m, to let version selection continue. // Report error but return m, to let version selection continue.
// (Reporting the error will fail the command at the next base.ExitIfErrors.) // (Reporting the error will fail the command at the next base.ExitIfErrors.)
...@@ -866,18 +912,6 @@ func (u *upgrader) Upgrade(m module.Version) (module.Version, error) { ...@@ -866,18 +912,6 @@ func (u *upgrader) Upgrade(m module.Version) (module.Version, error) {
return m, nil return m, nil
} }
// If we're on a later prerelease, keep using it,
// even though normally an Upgrade will ignore prereleases.
if semver.Compare(info.Version, m.Version) < 0 {
return m, nil
}
// If we're on a pseudo-version chronologically after the latest tagged version, keep using it.
// This avoids some accidental downgrades.
if mTime, err := modfetch.PseudoVersionTime(m.Version); err == nil && info.Time.Before(mTime) {
return m, nil
}
return module.Version{Path: m.Path, Version: info.Version}, nil return module.Version{Path: m.Path, Version: info.Version}, nil
} }
......
...@@ -79,7 +79,7 @@ func addUpdate(m *modinfo.ModulePublic) { ...@@ -79,7 +79,7 @@ func addUpdate(m *modinfo.ModulePublic) {
return return
} }
if info, err := Query(m.Path, "latest", Allowed); err == nil && semver.Compare(info.Version, m.Version) > 0 { if info, err := Query(m.Path, "latest", m.Version, Allowed); err == nil && semver.Compare(info.Version, m.Version) > 0 {
m.Update = &modinfo.ModulePublic{ m.Update = &modinfo.ModulePublic{
Path: m.Path, Path: m.Path,
Version: info.Version, Version: info.Version,
...@@ -127,7 +127,7 @@ func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic { ...@@ -127,7 +127,7 @@ func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic {
// complete fills in the extra fields in m. // complete fills in the extra fields in m.
complete := func(m *modinfo.ModulePublic) { complete := func(m *modinfo.ModulePublic) {
if m.Version != "" { if m.Version != "" {
if q, err := Query(m.Path, m.Version, nil); err != nil { if q, err := Query(m.Path, m.Version, "", nil); err != nil {
m.Error = &modinfo.ModuleError{Err: err.Error()} m.Error = &modinfo.ModuleError{Err: err.Error()}
} else { } else {
m.Version = q.Version m.Version = q.Version
......
...@@ -739,7 +739,7 @@ func fixVersion(path, vers string) (string, error) { ...@@ -739,7 +739,7 @@ func fixVersion(path, vers string) (string, error) {
return vers, nil return vers, nil
} }
info, err := Query(path, vers, nil) info, err := Query(path, vers, "", nil)
if err != nil { if err != nil {
return "", err return "", err
} }
......
...@@ -55,18 +55,28 @@ func listModules(args []string, listVersions bool) []*modinfo.ModulePublic { ...@@ -55,18 +55,28 @@ func listModules(args []string, listVersions bool) []*modinfo.ModulePublic {
base.Fatalf("go: cannot use relative path %s to specify module", arg) base.Fatalf("go: cannot use relative path %s to specify module", arg)
} }
if i := strings.Index(arg, "@"); i >= 0 { if i := strings.Index(arg, "@"); i >= 0 {
info, err := Query(arg[:i], arg[i+1:], nil) path := arg[:i]
vers := arg[i+1:]
var current string
for _, m := range buildList {
if m.Path == path {
current = m.Version
break
}
}
info, err := Query(path, vers, current, nil)
if err != nil { if err != nil {
mods = append(mods, &modinfo.ModulePublic{ mods = append(mods, &modinfo.ModulePublic{
Path: arg[:i], Path: path,
Version: arg[i+1:], Version: vers,
Error: &modinfo.ModuleError{ Error: &modinfo.ModuleError{
Err: err.Error(), Err: err.Error(),
}, },
}) })
continue continue
} }
mods = append(mods, moduleInfo(module.Version{Path: arg[:i], Version: info.Version}, false)) mods = append(mods, moduleInfo(module.Version{Path: path, Version: info.Version}, false))
continue continue
} }
...@@ -101,7 +111,7 @@ func listModules(args []string, listVersions bool) []*modinfo.ModulePublic { ...@@ -101,7 +111,7 @@ func listModules(args []string, listVersions bool) []*modinfo.ModulePublic {
// Don't make the user provide an explicit '@latest' when they're // Don't make the user provide an explicit '@latest' when they're
// explicitly asking what the available versions are. // explicitly asking what the available versions are.
// Instead, resolve the module, even if it isn't an existing dependency. // Instead, resolve the module, even if it isn't an existing dependency.
info, err := Query(arg, "latest", nil) info, err := Query(arg, "latest", "", nil)
if err == nil { if err == nil {
mods = append(mods, moduleInfo(module.Version{Path: arg, Version: info.Version}, false)) mods = append(mods, moduleInfo(module.Version{Path: arg, Version: info.Version}, false))
} else { } else {
......
...@@ -24,9 +24,13 @@ import ( ...@@ -24,9 +24,13 @@ import (
// The module must be a complete module path. // The module must be a complete module path.
// The version must take one of the following forms: // The version must take one of the following forms:
// //
// - the literal string "latest", denoting the latest available, allowed tagged version, // - the literal string "latest", denoting the latest available, allowed
// with non-prereleases preferred over prereleases. // tagged version, with non-prereleases preferred over prereleases.
// If there are no tagged versions in the repo, latest returns the most recent commit. // If there are no tagged versions in the repo, latest returns the most
// recent commit.
// - the literal string "patch", denoting the latest available tagged version
// with the same major and minor number as current. If current is "",
// "patch" is equivalent to "latest".
// - v1, denoting the latest available tagged version v1.x.x. // - v1, denoting the latest available tagged version v1.x.x.
// - v1.2, denoting the latest available tagged version v1.2.x. // - v1.2, denoting the latest available tagged version v1.2.x.
// - v1.2.3, a semantic version string denoting that tagged version. // - v1.2.3, a semantic version string denoting that tagged version.
...@@ -35,20 +39,30 @@ import ( ...@@ -35,20 +39,30 @@ import (
// with non-prereleases preferred over prereleases. // with non-prereleases preferred over prereleases.
// - a repository commit identifier or tag, denoting that commit. // - a repository commit identifier or tag, denoting that commit.
// //
// If the allowed function is non-nil, Query excludes any versions for which allowed returns false. // current is optional, denoting the current version of the module.
// If query is "latest" or "patch", current will be returned if it is a newer
// semantic version or if it is a chronologically later pseudoversion. This
// prevents accidental downgrades from newer prerelease or development
// versions.
//
// If the allowed function is non-nil, Query excludes any versions for which
// allowed returns false.
// //
// If path is the path of the main module and the query is "latest", // If path is the path of the main module and the query is "latest",
// Query returns Target.Version as the version. // Query returns Target.Version as the version.
func Query(path, query string, allowed func(module.Version) bool) (*modfetch.RevInfo, error) { func Query(path, query, current string, allowed func(module.Version) bool) (*modfetch.RevInfo, error) {
var info *modfetch.RevInfo var info *modfetch.RevInfo
err := modfetch.TryProxies(func(proxy string) (err error) { err := modfetch.TryProxies(func(proxy string) (err error) {
info, err = queryProxy(proxy, path, query, allowed) info, err = queryProxy(proxy, path, query, current, allowed)
return err return err
}) })
return info, err return info, err
} }
func queryProxy(proxy, path, query string, allowed func(module.Version) bool) (*modfetch.RevInfo, error) { func queryProxy(proxy, path, query, current string, allowed func(module.Version) bool) (*modfetch.RevInfo, error) {
if current != "" && !semver.IsValid(current) {
return nil, fmt.Errorf("invalid previous version %q", current)
}
if allowed == nil { if allowed == nil {
allowed = func(module.Version) bool { return true } allowed = func(module.Version) bool { return true }
} }
...@@ -61,9 +75,22 @@ func queryProxy(proxy, path, query string, allowed func(module.Version) bool) (* ...@@ -61,9 +75,22 @@ func queryProxy(proxy, path, query string, allowed func(module.Version) bool) (*
var ok func(module.Version) bool var ok func(module.Version) bool
var prefix string var prefix string
var preferOlder bool var preferOlder bool
var mayUseLatest bool
switch { switch {
case query == "latest": case query == "latest":
ok = allowed ok = allowed
mayUseLatest = true
case query == "patch":
if current == "" {
ok = allowed
mayUseLatest = true
} else {
prefix = semver.MajorMinor(current)
ok = func(m module.Version) bool {
return matchSemverPrefix(prefix, m.Version) && allowed(m)
}
}
case strings.HasPrefix(query, "<="): case strings.HasPrefix(query, "<="):
v := query[len("<="):] v := query[len("<="):]
...@@ -166,41 +193,59 @@ func queryProxy(proxy, path, query string, allowed func(module.Version) bool) (* ...@@ -166,41 +193,59 @@ func queryProxy(proxy, path, query string, allowed func(module.Version) bool) (*
return nil, err return nil, err
} }
lookup := func(v string) (*modfetch.RevInfo, error) {
rev, err := repo.Stat(v)
if err != nil {
return nil, err
}
// For "latest" and "patch", make sure we don't accidentally downgrade
// from a newer prerelease or from a chronologically newer pseudoversion.
if current != "" && (query == "latest" || query == "patch") {
currentTime, err := modfetch.PseudoVersionTime(current)
if semver.Compare(rev.Version, current) < 0 || (err == nil && rev.Time.Before(currentTime)) {
return repo.Stat(current)
}
}
return rev, nil
}
if preferOlder { if preferOlder {
for _, v := range versions { for _, v := range versions {
if semver.Prerelease(v) == "" && ok(module.Version{Path: path, Version: v}) { if semver.Prerelease(v) == "" && ok(module.Version{Path: path, Version: v}) {
return repo.Stat(v) return lookup(v)
} }
} }
for _, v := range versions { for _, v := range versions {
if semver.Prerelease(v) != "" && ok(module.Version{Path: path, Version: v}) { if semver.Prerelease(v) != "" && ok(module.Version{Path: path, Version: v}) {
return repo.Stat(v) return lookup(v)
} }
} }
} else { } else {
for i := len(versions) - 1; i >= 0; i-- { for i := len(versions) - 1; i >= 0; i-- {
v := versions[i] v := versions[i]
if semver.Prerelease(v) == "" && ok(module.Version{Path: path, Version: v}) { if semver.Prerelease(v) == "" && ok(module.Version{Path: path, Version: v}) {
return repo.Stat(v) return lookup(v)
} }
} }
for i := len(versions) - 1; i >= 0; i-- { for i := len(versions) - 1; i >= 0; i-- {
v := versions[i] v := versions[i]
if semver.Prerelease(v) != "" && ok(module.Version{Path: path, Version: v}) { if semver.Prerelease(v) != "" && ok(module.Version{Path: path, Version: v}) {
return repo.Stat(v) return lookup(v)
} }
} }
} }
if query == "latest" { if mayUseLatest {
// Special case for "latest": if no tags match, use latest commit in repo, // Special case for "latest": if no tags match, use latest commit in repo,
// provided it is not excluded. // provided it is not excluded.
if info, err := repo.Latest(); err == nil && allowed(module.Version{Path: path, Version: info.Version}) { if latest, err := repo.Latest(); err == nil && allowed(module.Version{Path: path, Version: latest.Version}) {
return info, nil return lookup(latest.Name)
} }
} }
return nil, &NoMatchingVersionError{query: query} return nil, &NoMatchingVersionError{query: query, current: current}
} }
// isSemverPrefix reports whether v is a semantic version prefix: v1 or v1.2 (not v1.2.3). // isSemverPrefix reports whether v is a semantic version prefix: v1 or v1.2 (not v1.2.3).
...@@ -310,7 +355,7 @@ func QueryPattern(pattern, query string, allowed func(module.Version) bool) ([]Q ...@@ -310,7 +355,7 @@ func QueryPattern(pattern, query string, allowed func(module.Version) bool) ([]Q
err := modfetch.TryProxies(func(proxy string) error { err := modfetch.TryProxies(func(proxy string) error {
queryModule := func(path string) (r QueryResult, err error) { queryModule := func(path string) (r QueryResult, err error) {
r.Mod.Path = path r.Mod.Path = path
r.Rev, err = queryProxy(proxy, path, query, allowed) r.Rev, err = queryProxy(proxy, path, query, "", allowed)
if err != nil { if err != nil {
return r, err return r, err
} }
...@@ -445,11 +490,15 @@ func queryPrefixModules(candidateModules []string, queryModule func(path string) ...@@ -445,11 +490,15 @@ func queryPrefixModules(candidateModules []string, queryModule func(path string)
// code for the versions it knows about, and thus did not have the opportunity // code for the versions it knows about, and thus did not have the opportunity
// to return a non-400 status code to suppress fallback. // to return a non-400 status code to suppress fallback.
type NoMatchingVersionError struct { type NoMatchingVersionError struct {
query string query, current string
} }
func (e *NoMatchingVersionError) Error() string { func (e *NoMatchingVersionError) Error() string {
return fmt.Sprintf("no matching versions for query %q", e.query) currentSuffix := ""
if (e.query == "latest" || e.query == "patch") && e.current != "" {
currentSuffix = fmt.Sprintf(" (current version is %s)", e.current)
}
return fmt.Sprintf("no matching versions for query %q", e.query) + currentSuffix
} }
// A packageNotInModuleError indicates that QueryPattern found a candidate // A packageNotInModuleError indicates that QueryPattern found a candidate
......
...@@ -52,6 +52,7 @@ var ( ...@@ -52,6 +52,7 @@ var (
var queryTests = []struct { var queryTests = []struct {
path string path string
query string query string
current string
allow string allow string
vers string vers string
err string err string
...@@ -108,7 +109,18 @@ var queryTests = []struct { ...@@ -108,7 +109,18 @@ var queryTests = []struct {
{path: queryRepo, query: "v1.9.10-pre2+wrongmetadata", err: `unknown revision v1.9.10-pre2+wrongmetadata`}, {path: queryRepo, query: "v1.9.10-pre2+wrongmetadata", err: `unknown revision v1.9.10-pre2+wrongmetadata`},
{path: queryRepo, query: "v1.9.10-pre2", err: `unknown revision v1.9.10-pre2`}, {path: queryRepo, query: "v1.9.10-pre2", err: `unknown revision v1.9.10-pre2`},
{path: queryRepo, query: "latest", vers: "v1.9.9"}, {path: queryRepo, query: "latest", vers: "v1.9.9"},
{path: queryRepo, query: "latest", current: "v1.9.10-pre1", vers: "v1.9.10-pre1"},
{path: queryRepo, query: "latest", current: "v1.9.10-pre2+metadata", vers: "v1.9.10-pre2.0.20190513201126-42abcb6df8ee"},
{path: queryRepo, query: "latest", current: "v0.0.0-20990101120000-5ba9a4ea6213", vers: "v0.0.0-20990101120000-5ba9a4ea6213"},
{path: queryRepo, query: "latest", allow: "NOMATCH", err: `no matching versions for query "latest"`}, {path: queryRepo, query: "latest", allow: "NOMATCH", err: `no matching versions for query "latest"`},
{path: queryRepo, query: "latest", current: "v1.9.9", allow: "NOMATCH", err: `no matching versions for query "latest" (current version is v1.9.9)`},
{path: queryRepo, query: "latest", current: "v1.99.99", err: `unknown revision v1.99.99`},
{path: queryRepo, query: "patch", current: "", vers: "v1.9.9"},
{path: queryRepo, query: "patch", current: "v0.1.0", vers: "v0.1.2"},
{path: queryRepo, query: "patch", current: "v1.9.0", vers: "v1.9.9"},
{path: queryRepo, query: "patch", current: "v1.9.10-pre1", vers: "v1.9.10-pre1"},
{path: queryRepo, query: "patch", current: "v1.9.10-pre2+metadata", vers: "v1.9.10-pre2.0.20190513201126-42abcb6df8ee"},
{path: queryRepo, query: "patch", current: "v1.99.99", err: `no matching versions for query "patch" (current version is v1.99.99)`},
{path: queryRepo, query: ">v1.9.9", vers: "v1.9.10-pre1"}, {path: queryRepo, query: ">v1.9.9", vers: "v1.9.10-pre1"},
{path: queryRepo, query: ">v1.10.0", err: `no matching versions for query ">v1.10.0"`}, {path: queryRepo, query: ">v1.10.0", err: `no matching versions for query ">v1.10.0"`},
{path: queryRepo, query: ">=v1.10.0", err: `no matching versions for query ">=v1.10.0"`}, {path: queryRepo, query: ">=v1.10.0", err: `no matching versions for query ">=v1.10.0"`},
...@@ -147,8 +159,8 @@ func TestQuery(t *testing.T) { ...@@ -147,8 +159,8 @@ func TestQuery(t *testing.T) {
ok, _ := path.Match(allow, m.Version) ok, _ := path.Match(allow, m.Version)
return ok return ok
} }
t.Run(strings.ReplaceAll(tt.path, "/", "_")+"/"+tt.query+"/"+allow, func(t *testing.T) { t.Run(strings.ReplaceAll(tt.path, "/", "_")+"/"+tt.query+"/"+tt.current+"/"+allow, func(t *testing.T) {
info, err := Query(tt.path, tt.query, allowed) info, err := Query(tt.path, tt.query, tt.current, allowed)
if tt.err != "" { if tt.err != "" {
if err != nil && err.Error() == tt.err { if err != nil && err.Error() == tt.err {
return return
......
...@@ -35,7 +35,7 @@ type Reqs interface { ...@@ -35,7 +35,7 @@ type Reqs interface {
// Max returns the maximum of v1 and v2 (it returns either v1 or v2). // Max returns the maximum of v1 and v2 (it returns either v1 or v2).
// //
// For all versions v, Max(v, "none") must be v, // For all versions v, Max(v, "none") must be v,
// and for the tanget passed as the first argument to MVS functions, // and for the target passed as the first argument to MVS functions,
// Max(target, v) must be target. // Max(target, v) must be target.
// //
// Note that v1 < v2 can be written Max(v1, v2) != v1 // Note that v1 < v2 can be written Max(v1, v2) != v1
......
A module which has no root package.
-- .mod --
module example.com/noroot
-- .info --
{"Version":"v1.0.0"}
-- pkg/pkg.go --
package pkg
A module which has no root package.
-- .mod --
module example.com/noroot
-- .info --
{"Version":"v1.0.1"}
-- pkg/pkg.go --
package pkg
...@@ -5,7 +5,7 @@ written by hand ...@@ -5,7 +5,7 @@ written by hand
module example.com/pseudoupgrade module example.com/pseudoupgrade
-- .info -- -- .info --
{"Version":"v0.0.0-20190429073000-30950c05d534","Name":"v0.0.0-20190429073000-30950c05d534","Short":"30950c05d534","Time":"2019-04-29T07:30:00Z"} {"Version":"v0.0.0-20190430073000-30950c05d534","Name":"v0.0.0-20190430073000-30950c05d534","Short":"30950c05d534","Time":"2019-04-30T07:30:00Z"}
-- pseudoupgrade.go -- -- pseudoupgrade.go --
package pseudoupgrade package pseudoupgrade
......
...@@ -14,6 +14,7 @@ go list -m all ...@@ -14,6 +14,7 @@ go list -m all
stdout '^github.com/rsc/legacytest v2\.0\.1-0\.\d{14}-7303f7796364\+incompatible$' stdout '^github.com/rsc/legacytest v2\.0\.1-0\.\d{14}-7303f7796364\+incompatible$'
# get should include incompatible tags in "latest" calculation. # get should include incompatible tags in "latest" calculation.
go mod edit -droprequire github.com/rsc/legacytest
go get -d github.com/rsc/legacytest@latest go get -d github.com/rsc/legacytest@latest
go list go list
go list -m all go list -m all
......
...@@ -3,17 +3,38 @@ env GO111MODULE=on ...@@ -3,17 +3,38 @@ env GO111MODULE=on
# For this test module there are three versions: # For this test module there are three versions:
# * v0.1.1-0.20190429073117-b5426c86b553 # * v0.1.1-0.20190429073117-b5426c86b553
# * v0.1.0 # * v0.1.0
# * v0.0.0-20190429073000-30950c05d534 # * v0.0.0-20190430073000-30950c05d534
# Only v0.1.0 is tagged. # Only v0.1.0 is tagged.
# #
# The latest pseudo-version is semantically higher than the latest tag. # The v0.1.1 pseudo-version is semantically higher than the latest tag.
# 'get -u' should not downgrade to the (lower) tagged version. # The v0.0.0 pseudo-version is chronologically newer.
# 'get -u' should not downgrade to the (lower) tagged version.
go get -d example.com/pseudoupgrade@b5426c8 go get -d example.com/pseudoupgrade@b5426c8
go get -u go get -d -u
go list -m -u all go list -m -u all
stdout '^example.com/pseudoupgrade v0.1.1-0.20190429073117-b5426c86b553$' stdout '^example.com/pseudoupgrade v0.1.1-0.20190429073117-b5426c86b553$'
# 'get example.com/pseudoupgrade@latest' should not downgrade to
# the (lower) tagged version.
go get -d example.com/pseudoupgrade@latest
go list -m all
stdout '^example.com/pseudoupgrade v0.1.1-0.20190429073117-b5426c86b553$'
# We should observe the same behavior with the newer pseudo-version.
go get -d example.com/pseudoupgrade@v0.0.0-20190430073000-30950c05d534
# 'get -u' should not downgrade to the chronologically older tagged version.
go get -d -u
go list -m -u all
stdout '^example.com/pseudoupgrade v0.0.0-20190430073000-30950c05d534$'
# 'get example.com/pseudoupgrade@latest' should not downgrade to the
# chronologically older tagged version.
go get -d example.com/pseudoupgrade@latest
go list -m -u all
stdout '^example.com/pseudoupgrade v0.0.0-20190430073000-30950c05d534$'
-- go.mod -- -- go.mod --
module x module x
......
...@@ -3,9 +3,12 @@ env GO111MODULE=on ...@@ -3,9 +3,12 @@ env GO111MODULE=on
# For this test module there are three versions: # For this test module there are three versions:
# * v0.1.1-0.20190429073117-b5426c86b553 # * v0.1.1-0.20190429073117-b5426c86b553
# * v0.1.0 # * v0.1.0
# * v0.0.0-20190429073000-30950c05d534 # * v0.0.0-20190430073000-30950c05d534
# Only v0.1.0 is tagged. # Only v0.1.0 is tagged.
# #
# The v0.1.1 pseudo-version is semantically higher than the latest tag.
# The v0.0.0 pseudo-version is chronologically newer.
# The latest pseudo-version is semantically higher than the latest tag. # The latest pseudo-version is semantically higher than the latest tag.
# 'list -u' should not suggest a lower version as an upgrade. # 'list -u' should not suggest a lower version as an upgrade.
...@@ -13,6 +16,10 @@ go get -d example.com/pseudoupgrade@b5426c8 ...@@ -13,6 +16,10 @@ go get -d example.com/pseudoupgrade@b5426c8
go list -m -u all go list -m -u all
stdout '^example.com/pseudoupgrade v0.1.1-0.20190429073117-b5426c86b553$' stdout '^example.com/pseudoupgrade v0.1.1-0.20190429073117-b5426c86b553$'
go get -d example.com/pseudoupgrade@v0.0.0-20190430073000-30950c05d534
go list -m -u all
stdout '^example.com/pseudoupgrade v0.0.0-20190430073000-30950c05d534$'
-- go.mod -- -- go.mod --
module x module x
......
...@@ -10,7 +10,7 @@ go mod download example.com/badchain/b@v1.1.0 ...@@ -10,7 +10,7 @@ go mod download example.com/badchain/b@v1.1.0
go mod download example.com/badchain/c@v1.1.0 go mod download example.com/badchain/c@v1.1.0
# Try to update example.com/badchain/a (and its dependencies). # Try to update example.com/badchain/a (and its dependencies).
! go get -d -u example.com/badchain/a ! go get -d example.com/badchain/a
cmp stderr update-a-expected cmp stderr update-a-expected
cmp go.mod go.mod.orig cmp go.mod go.mod.orig
......
...@@ -76,6 +76,13 @@ stderr 'cannot use pattern .* with explicit version' ...@@ -76,6 +76,13 @@ stderr 'cannot use pattern .* with explicit version'
# However, standard-library packages without explicit versions are fine. # However, standard-library packages without explicit versions are fine.
go get -d -u=patch -d cmd/go go get -d -u=patch -d cmd/go
# We can upgrade to a new version of a module with no root package.
go get -d example.com/noroot@v1.0.0
go list -m all
stdout '^example.com/noroot v1.0.0$'
go get -d example.com/noroot@patch
go list -m all
stdout '^example.com/noroot v1.0.1$'
-- go.mod -- -- go.mod --
module x module x
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment