Skip to content

Commit 712cba3

Browse files
author
Jay Conrod
committed
cmd/go: ignore retracted versions when converting revisions to versions
When a module author retracts a version, the go command should act as if it doesn't exist unless it's specifically requested. When converting a revision to a version, we should ignore tags for retracted versions. For example, if the tag v1.0.0 is retracted, and branch B points to the same revision, we should convert B to a pseudo-version, not v1.0.0. Similarly, if B points to a commit after v1.0.0, we should not use v1.0.0 as the base; we can use an earlier non-retracted tag or no base. Fixes #41700 Change-Id: Ia596b05b0780e5acfe6616a04e94d24bd342fbae Reviewed-on: https://go-review.googlesource.com/c/go/+/261079 Run-TryBot: Jay Conrod <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]> TryBot-Result: Go Bot <[email protected]> Trust: Jay Conrod <[email protected]>
1 parent d4a5797 commit 712cba3

File tree

5 files changed

+143
-11
lines changed

5 files changed

+143
-11
lines changed

src/cmd/go/internal/modfetch/codehost/codehost.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,8 @@ type Repo interface {
7979
ReadZip(rev, subdir string, maxSize int64) (zip io.ReadCloser, err error)
8080

8181
// RecentTag returns the most recent tag on rev or one of its predecessors
82-
// with the given prefix and major version.
83-
// An empty major string matches any major version.
84-
RecentTag(rev, prefix, major string) (tag string, err error)
82+
// with the given prefix. allowed may be used to filter out unwanted versions.
83+
RecentTag(rev, prefix string, allowed func(string) bool) (tag string, err error)
8584

8685
// DescendsFrom reports whether rev or any of its ancestors has the given tag.
8786
//

src/cmd/go/internal/modfetch/codehost/git.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ func (r *gitRepo) readFileRevs(tags []string, file string, fileMap map[string]*F
644644
return missing, nil
645645
}
646646

647-
func (r *gitRepo) RecentTag(rev, prefix, major string) (tag string, err error) {
647+
func (r *gitRepo) RecentTag(rev, prefix string, allowed func(string) bool) (tag string, err error) {
648648
info, err := r.Stat(rev)
649649
if err != nil {
650650
return "", err
@@ -680,7 +680,10 @@ func (r *gitRepo) RecentTag(rev, prefix, major string) (tag string, err error) {
680680
// NOTE: Do not replace the call to semver.Compare with semver.Max.
681681
// We want to return the actual tag, not a canonicalized version of it,
682682
// and semver.Max currently canonicalizes (see golang.org/issue/32700).
683-
if c := semver.Canonical(semtag); c != "" && strings.HasPrefix(semtag, c) && (major == "" || semver.Major(c) == major) && semver.Compare(semtag, highest) > 0 {
683+
if c := semver.Canonical(semtag); c == "" || !strings.HasPrefix(semtag, c) || !allowed(semtag) {
684+
continue
685+
}
686+
if semver.Compare(semtag, highest) > 0 {
684687
highest = semtag
685688
}
686689
}

src/cmd/go/internal/modfetch/codehost/vcs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ func (r *vcsRepo) ReadFileRevs(revs []string, file string, maxSize int64) (map[s
395395
return nil, vcsErrorf("ReadFileRevs not implemented")
396396
}
397397

398-
func (r *vcsRepo) RecentTag(rev, prefix, major string) (tag string, err error) {
398+
func (r *vcsRepo) RecentTag(rev, prefix string, allowed func(string) bool) (tag string, err error) {
399399
// We don't technically need to lock here since we're returning an error
400400
// uncondititonally, but doing so anyway will help to avoid baking in
401401
// lock-inversion bugs.

src/cmd/go/internal/modfetch/coderepo.go

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -419,9 +419,14 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
419419
tagPrefix = r.codeDir + "/"
420420
}
421421

422+
isRetracted, err := r.retractedVersions()
423+
if err != nil {
424+
isRetracted = func(string) bool { return false }
425+
}
426+
422427
// tagToVersion returns the version obtained by trimming tagPrefix from tag.
423-
// If the tag is invalid or a pseudo-version, tagToVersion returns an empty
424-
// version.
428+
// If the tag is invalid, retracted, or a pseudo-version, tagToVersion returns
429+
// an empty version.
425430
tagToVersion := func(tag string) (v string, tagIsCanonical bool) {
426431
if !strings.HasPrefix(tag, tagPrefix) {
427432
return "", false
@@ -436,6 +441,9 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
436441
if v == "" || !strings.HasPrefix(trimmed, v) {
437442
return "", false // Invalid or incomplete version (just vX or vX.Y).
438443
}
444+
if isRetracted(v) {
445+
return "", false
446+
}
439447
if v == trimmed {
440448
tagIsCanonical = true
441449
}
@@ -500,15 +508,24 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
500508
return checkGoMod()
501509
}
502510

511+
// Find the highest tagged version in the revision's history, subject to
512+
// major version and +incompatible constraints. Use that version as the
513+
// pseudo-version base so that the pseudo-version sorts higher. Ignore
514+
// retracted versions.
515+
allowedMajor := func(major string) func(v string) bool {
516+
return func(v string) bool {
517+
return (major == "" || semver.Major(v) == major) && !isRetracted(v)
518+
}
519+
}
503520
if pseudoBase == "" {
504521
var tag string
505522
if r.pseudoMajor != "" || canUseIncompatible() {
506-
tag, _ = r.code.RecentTag(info.Name, tagPrefix, r.pseudoMajor)
523+
tag, _ = r.code.RecentTag(info.Name, tagPrefix, allowedMajor(r.pseudoMajor))
507524
} else {
508525
// Allow either v1 or v0, but not incompatible higher versions.
509-
tag, _ = r.code.RecentTag(info.Name, tagPrefix, "v1")
526+
tag, _ = r.code.RecentTag(info.Name, tagPrefix, allowedMajor("v1"))
510527
if tag == "" {
511-
tag, _ = r.code.RecentTag(info.Name, tagPrefix, "v0")
528+
tag, _ = r.code.RecentTag(info.Name, tagPrefix, allowedMajor("v0"))
512529
}
513530
}
514531
pseudoBase, _ = tagToVersion(tag) // empty if the tag is invalid
@@ -869,6 +886,57 @@ func (r *codeRepo) modPrefix(rev string) string {
869886
return r.modPath + "@" + rev
870887
}
871888

889+
func (r *codeRepo) retractedVersions() (func(string) bool, error) {
890+
versions, err := r.Versions("")
891+
if err != nil {
892+
return nil, err
893+
}
894+
895+
for i, v := range versions {
896+
if strings.HasSuffix(v, "+incompatible") {
897+
versions = versions[:i]
898+
break
899+
}
900+
}
901+
if len(versions) == 0 {
902+
return func(string) bool { return false }, nil
903+
}
904+
905+
var highest string
906+
for i := len(versions) - 1; i >= 0; i-- {
907+
v := versions[i]
908+
if semver.Prerelease(v) == "" {
909+
highest = v
910+
break
911+
}
912+
}
913+
if highest == "" {
914+
highest = versions[len(versions)-1]
915+
}
916+
917+
data, err := r.GoMod(highest)
918+
if err != nil {
919+
return nil, err
920+
}
921+
f, err := modfile.ParseLax("go.mod", data, nil)
922+
if err != nil {
923+
return nil, err
924+
}
925+
retractions := make([]modfile.VersionInterval, len(f.Retract))
926+
for _, r := range f.Retract {
927+
retractions = append(retractions, r.VersionInterval)
928+
}
929+
930+
return func(v string) bool {
931+
for _, r := range retractions {
932+
if semver.Compare(r.Low, v) <= 0 && semver.Compare(v, r.High) <= 0 {
933+
return true
934+
}
935+
}
936+
return false
937+
}, nil
938+
}
939+
872940
func (r *codeRepo) Zip(dst io.Writer, version string) error {
873941
if version != module.CanonicalVersion(version) {
874942
return fmt.Errorf("version %s is not canonical", version)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# When converting a commit to a pseudo-version, don't use a retracted version
2+
# as the base.
3+
# Verifies golang.org/issue/41700.
4+
5+
[!net] skip
6+
[!exec:git] skip
7+
env GOPROXY=direct
8+
env GOSUMDB=off
9+
go mod init m
10+
11+
# Control: check that v1.0.0 is the only version and is retracted.
12+
go list -m -versions vcs-test.golang.org/git/retract-pseudo.git
13+
stdout '^vcs-test.golang.org/git/retract-pseudo.git$'
14+
go list -m -versions -retracted vcs-test.golang.org/git/retract-pseudo.git
15+
stdout '^vcs-test.golang.org/git/retract-pseudo.git v1.0.0$'
16+
17+
# 713affd19d7b is a commit after v1.0.0. Don't use v1.0.0 as the base.
18+
go list -m vcs-test.golang.org/git/retract-pseudo.git@713affd19d7b
19+
stdout '^vcs-test.golang.org/git/retract-pseudo.git v0.0.0-20201009173747-713affd19d7b$'
20+
21+
# 64c061ed4371 is the commit v1.0.0 refers to. Don't convert to v1.0.0.
22+
go list -m vcs-test.golang.org/git/retract-pseudo.git@64c061ed4371
23+
stdout '^vcs-test.golang.org/git/retract-pseudo.git v0.0.0-20201009173747-64c061ed4371'
24+
25+
# A retracted version is a valid base. Retraction should not validate existing
26+
# pseudo-versions, nor should it turn invalid pseudo-versions valid.
27+
go get -d vcs-test.golang.org/git/[email protected]
28+
go list -m vcs-test.golang.org/git/retract-pseudo.git
29+
stdout '^vcs-test.golang.org/git/retract-pseudo.git v1.0.1-0.20201009173747-713affd19d7b$'
30+
31+
! go get -d vcs-test.golang.org/git/[email protected]
32+
stderr '^go get vcs-test.golang.org/git/[email protected]: vcs-test.golang.org/git/[email protected]: 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$'
33+
34+
-- retract-pseudo.sh --
35+
#!/bin/bash
36+
37+
# This is not part of the test.
38+
# Run this to generate and update the repository on vcs-test.golang.org.
39+
40+
set -euo pipefail
41+
42+
rm -rf retract-pseudo
43+
mkdir retract-pseudo
44+
cd retract-pseudo
45+
git init
46+
47+
# Create the module.
48+
# Retract v1.0.0 and tag v1.0.0 at the same commit.
49+
# The module has no unretracted release versions.
50+
go mod init vcs-test.golang.org/git/retract-pseudo.git
51+
go mod edit -retract v1.0.0
52+
echo 'package p' >p.go
53+
git add -A
54+
git commit -m 'create module retract-pseudo'
55+
git tag v1.0.0
56+
57+
# Commit a trivial change so the default branch does not point to v1.0.0.
58+
git mv p.go q.go
59+
git commit -m 'trivial change'
60+
61+
zip -r ../retract-pseudo.zip .
62+
gsutil cp ../retract-pseudo.zip gs://vcs-test/git/retract-pseudo.zip

0 commit comments

Comments
 (0)