diff --git a/src/cmd/go/internal/gover/mod.go b/src/cmd/go/internal/gover/mod.go index c47841164a..8b9032f7b8 100644 --- a/src/cmd/go/internal/gover/mod.go +++ b/src/cmd/go/internal/gover/mod.go @@ -88,3 +88,25 @@ func untoolchain(x string) string { } return x } + +// ModIsPrefix reports whether v is a valid version syntax prefix for the module with the given path. +// The caller is assumed to have checked that ModIsValid(path, vers) is true. +func ModIsPrefix(path, vers string) bool { + if IsToolchain(path) { + return IsLang(vers) + } + // Semver + dots := 0 + for i := 0; i < len(vers); i++ { + switch vers[i] { + case '-', '+': + return false + case '.': + dots++ + if dots >= 2 { + return false + } + } + } + return true +} diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index 4ae2444927..2056b95558 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -3231,9 +3231,10 @@ func PackagesAndErrorsOutsideModule(ctx context.Context, opts PackageOpts, args // Check that the arguments satisfy syntactic constraints. var version string + var firstPath string for _, arg := range args { if i := strings.Index(arg, "@"); i >= 0 { - version = arg[i+1:] + firstPath, version = arg[:i], arg[i+1:] if version == "" { return nil, fmt.Errorf("%s: version must not be empty", arg) } @@ -3271,7 +3272,7 @@ func PackagesAndErrorsOutsideModule(ctx context.Context, opts PackageOpts, args // later arguments, and other modules would. Let's not try to be too // magical though. allowed := modload.CheckAllowed - if modload.IsRevisionQuery(version) { + if modload.IsRevisionQuery(firstPath, version) { // Don't check for retractions if a specific revision is requested. allowed = nil } diff --git a/src/cmd/go/internal/modfetch/cache.go b/src/cmd/go/internal/modfetch/cache.go index fab30f2944..5a727c6dfa 100644 --- a/src/cmd/go/internal/modfetch/cache.go +++ b/src/cmd/go/internal/modfetch/cache.go @@ -50,7 +50,7 @@ func CachePath(ctx context.Context, m module.Version, suffix string) (string, er if err != nil { return "", err } - if !semver.IsValid(m.Version) { + if !gover.ModIsValid(m.Path, m.Version) { return "", fmt.Errorf("non-semver module version %q", m.Version) } if module.CanonicalVersion(m.Version) != m.Version { @@ -79,7 +79,7 @@ func DownloadDir(ctx context.Context, m module.Version) (string, error) { if err != nil { return "", err } - if !semver.IsValid(m.Version) { + if !gover.ModIsValid(m.Path, m.Version) { return "", fmt.Errorf("non-semver module version %q", m.Version) } if module.CanonicalVersion(m.Version) != m.Version { @@ -334,7 +334,7 @@ func (r *cachingRepo) Zip(ctx context.Context, dst io.Writer, version string) er // InfoFile is like Lookup(ctx, path).Stat(version) but also returns the name of the file // containing the cached information. func InfoFile(ctx context.Context, path, version string) (*RevInfo, string, error) { - if !semver.IsValid(version) { + if !gover.ModIsValid(path, version) { return nil, "", fmt.Errorf("invalid version %q", version) } @@ -374,7 +374,7 @@ func InfoFile(ctx context.Context, path, version string) (*RevInfo, string, erro func GoMod(ctx context.Context, path, rev string) ([]byte, error) { // Convert commit hash to pseudo-version // to increase cache hit rate. - if !semver.IsValid(rev) { + if !gover.ModIsValid(path, rev) { if _, info, err := readDiskStat(ctx, path, rev); err == nil { rev = info.Version } else { @@ -409,7 +409,7 @@ func GoMod(ctx context.Context, path, rev string) ([]byte, error) { // GoModFile is like GoMod but returns the name of the file containing // the cached information. func GoModFile(ctx context.Context, path, version string) (string, error) { - if !semver.IsValid(version) { + if !gover.ModIsValid(path, version) { return "", fmt.Errorf("invalid version %q", version) } if _, err := GoMod(ctx, path, version); err != nil { @@ -426,7 +426,7 @@ func GoModFile(ctx context.Context, path, version string) (string, error) { // GoModSum returns the go.sum entry for the module version's go.mod file. // (That is, it returns the entry listed in go.sum as "path version/go.mod".) func GoModSum(ctx context.Context, path, version string) (string, error) { - if !semver.IsValid(version) { + if !gover.ModIsValid(path, version) { return "", fmt.Errorf("invalid version %q", version) } data, err := GoMod(ctx, path, version) diff --git a/src/cmd/go/internal/modload/list.go b/src/cmd/go/internal/modload/list.go index f77901fa21..3df8d017ab 100644 --- a/src/cmd/go/internal/modload/list.go +++ b/src/cmd/go/internal/modload/list.go @@ -183,7 +183,7 @@ func listModules(ctx context.Context, rs *Requirements, args []string, mode List } allowed := CheckAllowed - if IsRevisionQuery(vers) || mode&ListRetracted != 0 { + if IsRevisionQuery(path, vers) || mode&ListRetracted != 0 { // Allow excluded and retracted versions if the user asked for a // specific revision or used 'go list -retracted'. allowed = nil diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go index 226807126a..eef9228454 100644 --- a/src/cmd/go/internal/modload/modfile.go +++ b/src/cmd/go/internal/modload/modfile.go @@ -25,7 +25,6 @@ import ( "golang.org/x/mod/modfile" "golang.org/x/mod/module" - "golang.org/x/mod/semver" ) const ( @@ -762,7 +761,7 @@ func rawGoModData(m module.Version) (name string, data []byte, err error) { return "", nil, module.VersionError(m, fmt.Errorf("reading %s: %v", base.ShortPath(name), err)) } } else { - if !semver.IsValid(m.Version) { + if !gover.ModIsValid(m.Path, m.Version) { // Disallow the broader queries supported by fetch.Lookup. base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version) } diff --git a/src/cmd/go/internal/modload/mvs.go b/src/cmd/go/internal/modload/mvs.go index 4b30fa3100..8ae2dbff1e 100644 --- a/src/cmd/go/internal/modload/mvs.go +++ b/src/cmd/go/internal/modload/mvs.go @@ -60,7 +60,7 @@ func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) { return summary.require, nil } -// Max returns the maximum of v1 and v2 according to semver.Compare. +// Max returns the maximum of v1 and v2 according to gover.ModCompare. // // As a special case, the version "" is considered higher than all other // versions. The main module (also known as the target) has no version and must diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go index c539e144ba..19ba5b0650 100644 --- a/src/cmd/go/internal/modload/query.go +++ b/src/cmd/go/internal/modload/query.go @@ -324,36 +324,18 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed // a particular version or revision in a repository like "v1.0.0", "master", // or "0123abcd". IsRevisionQuery returns false if vers is a query that // chooses from among available versions like "latest" or ">v1.0.0". -func IsRevisionQuery(vers string) bool { +func IsRevisionQuery(path, vers string) bool { if vers == "latest" || vers == "upgrade" || vers == "patch" || strings.HasPrefix(vers, "<") || strings.HasPrefix(vers, ">") || - (semver.IsValid(vers) && isSemverPrefix(vers)) { + (gover.ModIsValid(path, vers) && gover.ModIsPrefix(path, vers)) { return false } return true } -// isSemverPrefix reports whether v is a semantic version prefix: v1 or v1.2 (not v1.2.3). -// The caller is assumed to have checked that semver.IsValid(v) is true. -func isSemverPrefix(v string) bool { - dots := 0 - for i := 0; i < len(v); i++ { - switch v[i] { - case '-', '+': - return false - case '.': - dots++ - if dots >= 2 { - return false - } - } - } - return true -} - type queryMatcher struct { path string prefix string @@ -417,10 +399,10 @@ func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (* case strings.HasPrefix(query, "<="): v := query[len("<="):] - if !semver.IsValid(v) { + if !gover.ModIsValid(path, v) { return badVersion(v) } - if isSemverPrefix(v) { + if gover.ModIsPrefix(path, v) { // Refuse to say whether <=v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3). return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query) } @@ -431,7 +413,7 @@ func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (* case strings.HasPrefix(query, "<"): v := query[len("<"):] - if !semver.IsValid(v) { + if !gover.ModIsValid(path, v) { return badVersion(v) } qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, v) < 0 } @@ -441,7 +423,7 @@ func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (* case strings.HasPrefix(query, ">="): v := query[len(">="):] - if !semver.IsValid(v) { + if !gover.ModIsValid(path, v) { return badVersion(v) } qm.filter = func(mv string) bool { return gover.ModCompare(qm.path, mv, v) >= 0 } @@ -452,10 +434,10 @@ func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (* case strings.HasPrefix(query, ">"): v := query[len(">"):] - if !semver.IsValid(v) { + if !gover.ModIsValid(path, v) { return badVersion(v) } - if isSemverPrefix(v) { + if gover.ModIsPrefix(path, v) { // Refuse to say whether >v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3). return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query) } @@ -465,8 +447,8 @@ func newQueryMatcher(path string, query, current string, allowed AllowedFunc) (* qm.preferIncompatible = true } - case semver.IsValid(query): - if isSemverPrefix(query) { + case gover.ModIsValid(path, query): + if gover.ModIsPrefix(path, query) { qm.prefix = query + "." // Do not allow the query "v1.2" to match versions lower than "v1.2.0", // such as prereleases for that version. (https://golang.org/issue/31972)