Commit a3a0cc2e authored by Jay Conrod's avatar Jay Conrod

cmd/go: restore @latest behavior and support @upgrade in 'go get'

'go get path@latest' may now downgrade a module required at a
pre-release or pseudo-version newer than the latest released
version. This restores the 1.12 behavior and the ability to easily
roll back from a temporary development version.

'go get path@upgrade' is like @latest but will not downgrade.
If no version suffix is specified ('go get path'), @upgrade is
implied.

Fixes #32846

Change-Id: Ibec0628292ab1c484716a5add0950d7a7ee45f47
Reviewed-on: https://go-review.googlesource.com/c/go/+/184440
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarBryan C. Mills <bcmills@google.com>
parent b412fde5
...@@ -586,12 +586,14 @@ ...@@ -586,12 +586,14 @@
// 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. The suffix @patch requests the latest patch release: if the path // module named by the given path. The suffix @upgrade is like @latest but
// is already in the build list, the selected version will have the same minor // will not downgrade a module if it is already required at a revision or
// version. If the path is not already in the build list, @patch is equivalent // pre-release version newer than the latest released version. The suffix
// to @latest. Neither @latest nor @patch will cause 'go get' to downgrade a module // @patch requests the latest patch release: the latest released version
// in the build list if it is required at a newer pre-release version that is // with the same major and minor version numbers as the currently required
// newer than the latest released version. // 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.
// //
// 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
......
...@@ -59,12 +59,14 @@ dependency should be removed entirely, downgrading or removing modules ...@@ -59,12 +59,14 @@ 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. The suffix @patch requests the latest patch release: if the path module named by the given path. The suffix @upgrade is like @latest but
is already in the build list, the selected version will have the same minor will not downgrade a module if it is already required at a revision or
version. If the path is not already in the build list, @patch is equivalent pre-release version newer than the latest released version. The suffix
to @latest. Neither @latest nor @patch will cause 'go get' to downgrade a module @patch requests the latest patch release: the latest released version
in the build list if it is required at a newer pre-release version that is with the same major and minor version numbers as the currently required
newer than the latest released version. 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.
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
...@@ -178,7 +180,7 @@ func (v *upgradeFlag) Set(s string) error { ...@@ -178,7 +180,7 @@ func (v *upgradeFlag) Set(s string) error {
s = "" s = ""
} }
if s == "true" { if s == "true" {
s = "latest" s = "upgrade"
} }
*v = upgradeFlag(s) *v = upgradeFlag(s)
return nil return nil
...@@ -202,8 +204,9 @@ type getArg struct { ...@@ -202,8 +204,9 @@ type getArg struct {
// if there is no "@"). path specifies the modules or packages to get. // if there is no "@"). path specifies the modules or packages to get.
path string path string
// vers is the part of the argument after "@" (or "" if there is no "@"). // vers is the part of the argument after "@" or an implied
// vers specifies the module version to get. // "upgrade" or "patch" if there is no "@". vers specifies the
// module version to get.
vers string vers string
} }
...@@ -249,7 +252,7 @@ func runGet(cmd *base.Command, args []string) { ...@@ -249,7 +252,7 @@ func runGet(cmd *base.Command, args []string) {
} }
switch getU { switch getU {
case "", "latest", "patch": case "", "upgrade", "patch":
// ok // ok
default: default:
base.Fatalf("go get: unknown upgrade flag -u=%s", getU) base.Fatalf("go get: unknown upgrade flag -u=%s", getU)
...@@ -283,11 +286,11 @@ func runGet(cmd *base.Command, args []string) { ...@@ -283,11 +286,11 @@ func runGet(cmd *base.Command, args []string) {
// Parse command-line arguments and report errors. The command-line // Parse command-line arguments and report errors. The command-line
// arguments are of the form path@version or simply path, with implicit // arguments are of the form path@version or simply path, with implicit
// @latest. path@none is "downgrade away". // @upgrade. path@none is "downgrade away".
var gets []getArg var gets []getArg
var queries []*query var queries []*query
for _, arg := range search.CleanPatterns(args) { for _, arg := range search.CleanPatterns(args) {
// Argument is module query path@vers, or else path with implicit @latest. // Argument is path or path@vers.
path := arg path := arg
vers := "" vers := ""
if i := strings.Index(arg, "@"); i >= 0 { if i := strings.Index(arg, "@"); i >= 0 {
...@@ -298,10 +301,14 @@ func runGet(cmd *base.Command, args []string) { ...@@ -298,10 +301,14 @@ func runGet(cmd *base.Command, args []string) {
continue continue
} }
// If the user runs 'go get -u=patch some/module', update some/module to a // If no version suffix is specified, assume @upgrade.
// patch release, not a minor version. // If -u=patch was specified, assume @patch instead.
if vers == "" && getU != "" { if vers == "" {
vers = string(getU) if getU != "" {
vers = string(getU)
} else {
vers = "upgrade"
}
} }
gets = append(gets, getArg{raw: arg, path: path, vers: vers}) gets = append(gets, getArg{raw: arg, path: path, vers: vers})
...@@ -358,7 +365,7 @@ func runGet(cmd *base.Command, args []string) { ...@@ -358,7 +365,7 @@ func runGet(cmd *base.Command, args []string) {
// The argument is a package path. // The argument is a package path.
if pkgs := modload.TargetPackages(path); len(pkgs) != 0 { if pkgs := modload.TargetPackages(path); len(pkgs) != 0 {
// The path is in the main module. Nothing to query. // The path is in the main module. Nothing to query.
if vers != "" && vers != "latest" && vers != "patch" { if vers != "upgrade" && vers != "patch" {
base.Errorf("go get %s: can't request explicit version of path in main module", arg) base.Errorf("go get %s: can't request explicit version of path in main module", arg)
} }
continue continue
...@@ -376,8 +383,8 @@ func runGet(cmd *base.Command, args []string) { ...@@ -376,8 +383,8 @@ func runGet(cmd *base.Command, args []string) {
continue continue
} }
// If we're querying "latest" or "patch", we need to know the current // If we're querying "upgrade" or "patch", we need to know the current
// version of the module. For "latest", we want to avoid accidentally // version of the module. For "upgrade", we want to avoid accidentally
// downgrading from a newer prerelease. For "patch", we need to query // downgrading from a newer prerelease. For "patch", we need to query
// the correct minor version. // the correct minor version.
// Here, we check if "path" is the name of a module in the build list // Here, we check if "path" is the name of a module in the build list
...@@ -736,10 +743,6 @@ func getQuery(path, vers string, prevM module.Version, forceModulePath bool) (mo ...@@ -736,10 +743,6 @@ func getQuery(path, vers string, prevM module.Version, forceModulePath bool) (mo
base.Fatalf("go get: internal error: prevM may be set if and only if forceModulePath is set") 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"
}
if forceModulePath || !strings.Contains(path, "...") { if forceModulePath || !strings.Contains(path, "...") {
if path == modload.Target.Path { if path == modload.Target.Path {
if vers != "latest" { if vers != "latest" {
...@@ -893,9 +896,10 @@ func (u *upgrader) Upgrade(m module.Version) (module.Version, error) { ...@@ -893,9 +896,10 @@ func (u *upgrader) Upgrade(m module.Version) (module.Version, error) {
// which may return a pseudoversion for the latest commit. // which may return a pseudoversion for the latest commit.
// Query "latest" returns the newest tagged version or the newest // Query "latest" returns the newest tagged version or the newest
// prerelease version if there are no non-prereleases, or repo.Latest // prerelease version if there are no non-prereleases, or repo.Latest
// if there aren't any tagged versions. Since we're providing the previous // if there aren't any tagged versions.
// version, Query will confirm the latest version is actually newer // If we're querying "upgrade" or "patch", Query will compare the current
// and will return the current version if not. // version against the chosen version and will return the current version
// if it is newer.
info, err := modload.Query(m.Path, string(getU), m.Version, modload.Allowed) info, err := modload.Query(m.Path, string(getU), m.Version, 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.
......
...@@ -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", m.Version, Allowed); err == nil && semver.Compare(info.Version, m.Version) > 0 { if info, err := Query(m.Path, "upgrade", 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,
......
...@@ -28,9 +28,10 @@ import ( ...@@ -28,9 +28,10 @@ import (
// tagged version, 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 // If there are no tagged versions in the repo, latest returns the most
// recent commit. // recent commit.
// - the literal string "upgrade", equivalent to "latest" except that if
// current is a newer version, current will be returned (see below).
// - the literal string "patch", denoting the latest available tagged version // - the literal string "patch", denoting the latest available tagged version
// with the same major and minor number as current. If current is "", // with the same major and minor number as current (see below).
// "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.
...@@ -39,11 +40,12 @@ import ( ...@@ -39,11 +40,12 @@ 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.
// //
// current is optional, denoting the current version of the module. // current denotes the current version of the module; it may be "" if the
// If query is "latest" or "patch", current will be returned if it is a newer // current version is unknown or should not be considered. If query is
// semantic version or if it is a chronologically later pseudoversion. This // "upgrade" or "patch", current will be returned if it is a newer
// prevents accidental downgrades from newer prerelease or development // semantic version or a chronologically later pseudo-version than the
// versions. // version that would otherwise be chosen. This prevents accidental downgrades
// from newer pre-release or development versions.
// //
// If the allowed function is non-nil, Query excludes any versions for which // If the allowed function is non-nil, Query excludes any versions for which
// allowed returns false. // allowed returns false.
...@@ -81,6 +83,10 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version) ...@@ -81,6 +83,10 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version)
ok = allowed ok = allowed
mayUseLatest = true mayUseLatest = true
case query == "upgrade":
ok = allowed
mayUseLatest = true
case query == "patch": case query == "patch":
if current == "" { if current == "" {
ok = allowed ok = allowed
...@@ -202,9 +208,9 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version) ...@@ -202,9 +208,9 @@ func queryProxy(proxy, path, query, current string, allowed func(module.Version)
return nil, err return nil, err
} }
// For "latest" and "patch", make sure we don't accidentally downgrade // For "upgrade" and "patch", make sure we don't accidentally downgrade
// from a newer prerelease or from a chronologically newer pseudoversion. // from a newer prerelease or from a chronologically newer pseudoversion.
if current != "" && (query == "latest" || query == "patch") { if current != "" && (query == "upgrade" || query == "patch") {
currentTime, err := modfetch.PseudoVersionTime(current) currentTime, err := modfetch.PseudoVersionTime(current)
if semver.Compare(rev.Version, current) < 0 || (err == nil && rev.Time.Before(currentTime)) { if semver.Compare(rev.Version, current) < 0 || (err == nil && rev.Time.Before(currentTime)) {
return repo.Stat(current) return repo.Stat(current)
......
...@@ -4,13 +4,19 @@ env GO111MODULE=on ...@@ -4,13 +4,19 @@ env GO111MODULE=on
# @patch and @latest within the main module refer to the current version. # @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. # The main module won't be upgraded, but missing dependencies will be added.
cp go.mod.orig go.mod cp go.mod.orig go.mod
go get -d rsc.io/x@latest go get -d rsc.io/x
grep 'rsc.io/quote v1.5.2' go.mod
go get -d rsc.io/x@upgrade
grep 'rsc.io/quote v1.5.2' go.mod grep 'rsc.io/quote v1.5.2' go.mod
cp go.mod.orig go.mod cp go.mod.orig go.mod
go get -d rsc.io/x@patch go get -d rsc.io/x@patch
grep 'rsc.io/quote v1.5.2' go.mod grep 'rsc.io/quote v1.5.2' go.mod
cp go.mod.orig 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. # The main module cannot be updated to a specific version.
! go get rsc.io/x@v0.1.0 ! go get 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$' stderr '^go get rsc.io/x@v0.1.0: can.t request explicit version of path in main module$'
......
...@@ -10,11 +10,11 @@ grep 'require rsc.io/quote' go.mod ...@@ -10,11 +10,11 @@ grep 'require rsc.io/quote' go.mod
cp go.mod.orig go.mod cp go.mod.orig go.mod
! go get -d rsc.io/quote/x... ! go get -d rsc.io/quote/x...
stderr 'go get rsc.io/quote/x...: module rsc.io/quote@latest \(v1.5.2\) found, but does not contain packages matching rsc.io/quote/x...' stderr 'go get rsc.io/quote/x...: module rsc.io/quote@upgrade \(v1.5.2\) found, but does not contain packages matching rsc.io/quote/x...'
! grep 'require rsc.io/quote' go.mod ! grep 'require rsc.io/quote' go.mod
! go get -d rsc.io/quote/x/... ! go get -d rsc.io/quote/x/...
stderr 'go get rsc.io/quote/x/...: module rsc.io/quote@latest \(v1.5.2\) found, but does not contain packages matching rsc.io/quote/x/...' stderr 'go get rsc.io/quote/x/...: module rsc.io/quote@upgrade \(v1.5.2\) found, but does not contain packages matching rsc.io/quote/x/...'
! grep 'require rsc.io/quote' go.mod ! grep 'require rsc.io/quote' go.mod
# If a pattern matches no packages within a module, the module should not # If a pattern matches no packages within a module, the module should not
......
...@@ -17,7 +17,7 @@ stderr 'ReadZip not implemented for svn' ...@@ -17,7 +17,7 @@ stderr 'ReadZip not implemented for svn'
# reasonable message instead of a panic. # reasonable message instead of a panic.
! go get -d vcs-test.golang.org/svn/nonexistent.svn ! go get -d vcs-test.golang.org/svn/nonexistent.svn
! stderr panic ! stderr panic
stderr 'go get vcs-test.golang.org/svn/nonexistent.svn: no matching versions for query "latest"' stderr 'go get vcs-test.golang.org/svn/nonexistent.svn: no matching versions for query "upgrade"'
-- go.mod -- -- go.mod --
module golang/go/issues/28943/main module golang/go/issues/28943/main
......
...@@ -9,18 +9,33 @@ env GO111MODULE=on ...@@ -9,18 +9,33 @@ env GO111MODULE=on
# The v0.1.1 pseudo-version is semantically higher than the latest tag. # The v0.1.1 pseudo-version is semantically higher than the latest tag.
# The v0.0.0 pseudo-version is chronologically newer. # The v0.0.0 pseudo-version is chronologically newer.
# 'get -u' should not downgrade to the (lower) tagged version. # Start at v0.1.1-0.20190429073117-b5426c86b553
go get -d example.com/pseudoupgrade@b5426c8 go get -d example.com/pseudoupgrade@b5426c8
go list -m -u all
stdout '^example.com/pseudoupgrade v0.1.1-0.20190429073117-b5426c86b553$'
# 'get -u' should not downgrade to the (lower) tagged version.
go get -d -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 # 'get example.com/pseudoupgrade@upgrade' should not downgrade.
# the (lower) tagged version. go get -d example.com/pseudoupgrade@upgrade
go get -d example.com/pseudoupgrade@latest
go list -m all go list -m 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' should not downgrade.
# This is equivalent to 'get example.com/pseudoupgrade@upgrade'.
go get -d example.com/pseudoupgrade
go list -m all
stdout '^example.com/pseudoupgrade v0.1.1-0.20190429073117-b5426c86b553$'
# 'get example.com/pseudoupgrade@latest' should downgrade.
# @latest should not consider the current version.
go get -d example.com/pseudoupgrade@latest
go list -m all
stdout '^example.com/pseudoupgrade v0.1.0$'
# We should observe the same behavior with the newer pseudo-version. # We should observe the same behavior with the newer pseudo-version.
go get -d example.com/pseudoupgrade@v0.0.0-20190430073000-30950c05d534 go get -d example.com/pseudoupgrade@v0.0.0-20190430073000-30950c05d534
...@@ -29,12 +44,21 @@ go get -d -u ...@@ -29,12 +44,21 @@ go get -d -u
go list -m -u all go list -m -u all
stdout '^example.com/pseudoupgrade v0.0.0-20190430073000-30950c05d534$' stdout '^example.com/pseudoupgrade v0.0.0-20190430073000-30950c05d534$'
# 'get example.com/pseudoupgrade@latest' should not downgrade to the # 'get example.com/pseudoupgrade@upgrade should not downgrade.
# chronologically older tagged version. go get -d example.com/pseudoupgrade@upgrade
go get -d example.com/pseudoupgrade@latest
go list -m -u all go list -m -u all
stdout '^example.com/pseudoupgrade v0.0.0-20190430073000-30950c05d534$' stdout '^example.com/pseudoupgrade v0.0.0-20190430073000-30950c05d534$'
# 'get example.com/pseudoupgrade' should not downgrade.
go get -d example.com/pseudoupgrade
go list -m -u all
stdout '^example.com/pseudoupgrade v0.0.0-20190430073000-30950c05d534$'
# 'get example.com/pseudoupgrade@latest' should downgrade.
go get -d example.com/pseudoupgrade@latest
go list -m -u all
stdout '^example.com/pseudoupgrade v0.1.0$'
-- 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