Skip to content

Commit 4c2ffd2

Browse files
author
Jay Conrod
committed
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]>
1 parent 80f8913 commit 4c2ffd2

16 files changed

+272
-114
lines changed

src/cmd/go/alldocs.go

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/go/internal/modget/get.go

Lines changed: 95 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"cmd/go/internal/get"
1212
"cmd/go/internal/imports"
1313
"cmd/go/internal/load"
14-
"cmd/go/internal/modfetch"
1514
"cmd/go/internal/modload"
1615
"cmd/go/internal/module"
1716
"cmd/go/internal/mvs"
@@ -60,11 +59,12 @@ dependency should be removed entirely, downgrading or removing modules
6059
depending on it as needed.
6160
6261
The version suffix @latest explicitly requests the latest minor release of the
63-
given path.
64-
65-
The suffix @patch requests the latest patch release: if the path is already in
66-
the build list, the selected version will have the same minor version.
67-
If the path is not already in the build list, @patch is equivalent to @latest.
62+
given path. The suffix @patch requests the latest patch release: if the path
63+
is already in the build list, the selected version will have the same minor
64+
version. If the path is not already in the build list, @patch is equivalent
65+
to @latest. Neither @latest nor @patch will cause 'go get' to downgrade a module
66+
in the build list if it is required at a newer pre-release version that is
67+
newer than the latest released version.
6868
6969
Although get defaults to using the latest version of the module containing
7070
a named package, it does not use the latest version of that module's
@@ -219,10 +219,13 @@ type querySpec struct {
219219
vers string
220220

221221
// forceModulePath is true if path should be interpreted as a module path.
222+
// If forceModulePath is true, prevM must be set.
222223
forceModulePath bool
223224

224225
// prevM is the previous version of the module. prevM is needed
225-
// if vers is "patch", and the module was previously in the build list.
226+
// to determine the minor version number if vers is "patch". It's also
227+
// used to avoid downgrades from prerelease versions newer than
228+
// "latest" and "patch". If prevM is set, forceModulePath must be true.
226229
prevM module.Version
227230
}
228231

@@ -266,7 +269,12 @@ func runGet(cmd *base.Command, args []string) {
266269
base.Fatalf("go get: disabled by -mod=%s", cfg.BuildMod)
267270
}
268271

269-
modload.LoadBuildList()
272+
buildList := modload.LoadBuildList()
273+
buildList = buildList[:len(buildList):len(buildList)] // copy on append
274+
versionByPath := make(map[string]string)
275+
for _, m := range buildList {
276+
versionByPath[m.Path] = m.Version
277+
}
270278

271279
// Do not allow any updating of go.mod until we've applied
272280
// all the requested changes and checked that the result matches
@@ -356,33 +364,66 @@ func runGet(cmd *base.Command, args []string) {
356364
continue
357365
}
358366

359-
if vers == "patch" {
360-
// We need to know the previous version of the module to find
361-
// the new version, but we don't know what module provides this
362-
// package yet. Wait until we load packages later.
363-
// TODO(golang.org/issue/30634): @latest should also depend on
364-
// the current version to prevent downgrading from newer pseudoversions.
365-
} else {
366-
// The requested version of path doesn't depend on the existing version,
367-
// so query the module before loading the package. This may let us
368-
// load the package only once at the correct version.
369-
queries = append(queries, &query{querySpec: querySpec{path: path, vers: vers}, arg: arg})
367+
first := path
368+
if i := strings.IndexByte(first, '/'); i >= 0 {
369+
first = path
370+
}
371+
if !strings.Contains(first, ".") {
372+
// The path doesn't have a dot in the first component and cannot be
373+
// queried as a module. It may be a package in the standard library,
374+
// which is fine, so don't report an error unless we encounter
375+
// a problem loading packages below.
376+
continue
377+
}
378+
379+
// If we're querying "latest" or "patch", we need to know the current
380+
// version of the module. For "latest", we want to avoid accidentally
381+
// downgrading from a newer prerelease. For "patch", we need to query
382+
// the correct minor version.
383+
// Here, we check if "path" is the name of a module in the build list
384+
// (other than the main module) and set prevM if so. If "path" isn't
385+
// a module in the build list, the current version doesn't matter
386+
// since it's either an unknown module or a package within a module
387+
// that we'll discover later.
388+
q := &query{querySpec: querySpec{path: path, vers: vers}, arg: arg}
389+
if v, ok := versionByPath[path]; ok && path != modload.Target.Path {
390+
q.prevM = module.Version{Path: path, Version: v}
391+
q.forceModulePath = true
370392
}
393+
queries = append(queries, q)
371394
}
372395
}
373396
base.ExitIfErrors()
374397

375-
// Query modules referenced by command line arguments at requested versions,
376-
// and add them to the build list. We need to do this before loading packages
377-
// since patterns that refer to packages in unknown modules can't be
378-
// expanded. This also avoids looking up new modules while loading packages,
379-
// only to downgrade later.
398+
// Query modules referenced by command line arguments at requested versions.
399+
// We need to do this before loading packages since patterns that refer to
400+
// packages in unknown modules can't be expanded. This also avoids looking
401+
// up new modules while loading packages, only to downgrade later.
380402
queryCache := make(map[querySpec]*query)
381403
byPath := runQueries(queryCache, queries, nil)
382404

383-
// Add queried modules to the build list. This prevents some additional
384-
// lookups for modules at "latest" when we load packages later.
385-
buildList, err := mvs.UpgradeAll(modload.Target, newUpgrader(byPath, nil))
405+
// Add missing modules to the build list.
406+
// We call SetBuildList here and elsewhere, since newUpgrader,
407+
// ImportPathsQuiet, and other functions read the global build list.
408+
for _, q := range queries {
409+
if _, ok := versionByPath[q.m.Path]; !ok && q.m.Version != "none" {
410+
buildList = append(buildList, q.m)
411+
}
412+
}
413+
versionByPath = nil // out of date now; rebuilt later when needed
414+
modload.SetBuildList(buildList)
415+
416+
// Upgrade modules specifically named on the command line. This is our only
417+
// chance to upgrade modules without root packages (modOnly below).
418+
// This also skips loading packages at an old version, only to upgrade
419+
// and reload at a new version.
420+
upgrade := make(map[string]*query)
421+
for path, q := range byPath {
422+
if q.path == q.m.Path && q.m.Version != "none" {
423+
upgrade[path] = q
424+
}
425+
}
426+
buildList, err := mvs.UpgradeAll(modload.Target, newUpgrader(upgrade, nil))
386427
if err != nil {
387428
base.Fatalf("go get: %v", err)
388429
}
@@ -478,6 +519,10 @@ func runGet(cmd *base.Command, args []string) {
478519
continue
479520
}
480521
allStd = false
522+
if m.Path == modload.Target.Path {
523+
// pkg is in the main module.
524+
continue
525+
}
481526
addQuery(&query{querySpec: querySpec{path: m.Path, vers: arg.vers, forceModulePath: true, prevM: m}, arg: arg.raw})
482527
}
483528
if allStd && arg.path != arg.raw {
@@ -538,7 +583,6 @@ func runGet(cmd *base.Command, args []string) {
538583

539584
// Scan for any upgrades lost by the downgrades.
540585
var lostUpgrades []*query
541-
var versionByPath map[string]string
542586
if len(down) > 0 {
543587
versionByPath = make(map[string]string)
544588
for _, m := range modload.BuildList() {
@@ -680,15 +724,21 @@ func runQueries(cache map[querySpec]*query, queries []*query, modOnly map[string
680724
// If forceModulePath is set, getQuery must interpret path
681725
// as a module path.
682726
func getQuery(path, vers string, prevM module.Version, forceModulePath bool) (module.Version, error) {
683-
switch vers {
684-
case "":
727+
if (prevM.Version != "") != forceModulePath {
728+
// We resolve package patterns by calling QueryPattern, which does not
729+
// accept a previous version and therefore cannot take it into account for
730+
// the "latest" or "patch" queries.
731+
// If we are resolving a package path or pattern, the caller has already
732+
// resolved any existing packages to their containing module(s), and
733+
// will set both prevM.Version and forceModulePath for those modules.
734+
// The only remaining package patterns are those that are not already
735+
// provided by the build list, which are indicated by
736+
// an empty prevM.Version.
737+
base.Fatalf("go get: internal error: prevM may be set if and only if forceModulePath is set")
738+
}
739+
740+
if vers == "" || vers == "patch" && prevM.Version == "" {
685741
vers = "latest"
686-
case "patch":
687-
if prevM.Version == "" {
688-
vers = "latest"
689-
} else {
690-
vers = semver.MajorMinor(prevM.Version)
691-
}
692742
}
693743

694744
if forceModulePath || !strings.Contains(path, "...") {
@@ -699,7 +749,7 @@ func getQuery(path, vers string, prevM module.Version, forceModulePath bool) (mo
699749
}
700750

701751
// If the path doesn't contain a wildcard, try interpreting it as a module path.
702-
info, err := modload.Query(path, vers, modload.Allowed)
752+
info, err := modload.Query(path, vers, prevM.Version, modload.Allowed)
703753
if err == nil {
704754
return module.Version{Path: path, Version: info.Version}, nil
705755
}
@@ -840,18 +890,14 @@ func (u *upgrader) Upgrade(m module.Version) (module.Version, error) {
840890
}
841891

842892
// Run query required by upgrade semantics.
843-
// Note that query "latest" is not the same as
844-
// using repo.Latest.
845-
// The query only falls back to untagged versions
846-
// if nothing is tagged. The Latest method
847-
// only ever returns untagged versions,
848-
// which is not what we want.
849-
query := "latest"
850-
if getU == "patch" {
851-
// For patch upgrade, query "v1.2".
852-
query = semver.MajorMinor(m.Version)
853-
}
854-
info, err := modload.Query(m.Path, query, modload.Allowed)
893+
// Note that Query "latest" is not the same as using repo.Latest,
894+
// which may return a pseudoversion for the latest commit.
895+
// Query "latest" returns the newest tagged version or the newest
896+
// prerelease version if there are no non-prereleases, or repo.Latest
897+
// if there aren't any tagged versions. Since we're providing the previous
898+
// version, Query will confirm the latest version is actually newer
899+
// and will return the current version if not.
900+
info, err := modload.Query(m.Path, string(getU), m.Version, modload.Allowed)
855901
if err != nil {
856902
// Report error but return m, to let version selection continue.
857903
// (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) {
866912
return m, nil
867913
}
868914

869-
// If we're on a later prerelease, keep using it,
870-
// even though normally an Upgrade will ignore prereleases.
871-
if semver.Compare(info.Version, m.Version) < 0 {
872-
return m, nil
873-
}
874-
875-
// If we're on a pseudo-version chronologically after the latest tagged version, keep using it.
876-
// This avoids some accidental downgrades.
877-
if mTime, err := modfetch.PseudoVersionTime(m.Version); err == nil && info.Time.Before(mTime) {
878-
return m, nil
879-
}
880-
881915
return module.Version{Path: m.Path, Version: info.Version}, nil
882916
}
883917

src/cmd/go/internal/modload/build.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func addUpdate(m *modinfo.ModulePublic) {
7979
return
8080
}
8181

82-
if info, err := Query(m.Path, "latest", Allowed); err == nil && semver.Compare(info.Version, m.Version) > 0 {
82+
if info, err := Query(m.Path, "latest", m.Version, Allowed); err == nil && semver.Compare(info.Version, m.Version) > 0 {
8383
m.Update = &modinfo.ModulePublic{
8484
Path: m.Path,
8585
Version: info.Version,
@@ -127,7 +127,7 @@ func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic {
127127
// complete fills in the extra fields in m.
128128
complete := func(m *modinfo.ModulePublic) {
129129
if m.Version != "" {
130-
if q, err := Query(m.Path, m.Version, nil); err != nil {
130+
if q, err := Query(m.Path, m.Version, "", nil); err != nil {
131131
m.Error = &modinfo.ModuleError{Err: err.Error()}
132132
} else {
133133
m.Version = q.Version

src/cmd/go/internal/modload/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -739,7 +739,7 @@ func fixVersion(path, vers string) (string, error) {
739739
return vers, nil
740740
}
741741

742-
info, err := Query(path, vers, nil)
742+
info, err := Query(path, vers, "", nil)
743743
if err != nil {
744744
return "", err
745745
}

src/cmd/go/internal/modload/list.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,28 @@ func listModules(args []string, listVersions bool) []*modinfo.ModulePublic {
5555
base.Fatalf("go: cannot use relative path %s to specify module", arg)
5656
}
5757
if i := strings.Index(arg, "@"); i >= 0 {
58-
info, err := Query(arg[:i], arg[i+1:], nil)
58+
path := arg[:i]
59+
vers := arg[i+1:]
60+
var current string
61+
for _, m := range buildList {
62+
if m.Path == path {
63+
current = m.Version
64+
break
65+
}
66+
}
67+
68+
info, err := Query(path, vers, current, nil)
5969
if err != nil {
6070
mods = append(mods, &modinfo.ModulePublic{
61-
Path: arg[:i],
62-
Version: arg[i+1:],
71+
Path: path,
72+
Version: vers,
6373
Error: &modinfo.ModuleError{
6474
Err: err.Error(),
6575
},
6676
})
6777
continue
6878
}
69-
mods = append(mods, moduleInfo(module.Version{Path: arg[:i], Version: info.Version}, false))
79+
mods = append(mods, moduleInfo(module.Version{Path: path, Version: info.Version}, false))
7080
continue
7181
}
7282

@@ -101,7 +111,7 @@ func listModules(args []string, listVersions bool) []*modinfo.ModulePublic {
101111
// Don't make the user provide an explicit '@latest' when they're
102112
// explicitly asking what the available versions are.
103113
// Instead, resolve the module, even if it isn't an existing dependency.
104-
info, err := Query(arg, "latest", nil)
114+
info, err := Query(arg, "latest", "", nil)
105115
if err == nil {
106116
mods = append(mods, moduleInfo(module.Version{Path: arg, Version: info.Version}, false))
107117
} else {

0 commit comments

Comments
 (0)