Source File
pseudo.go
Belonging Package
golang.org/x/mod/module
// Copyright 2018 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.// Pseudo-versions//// Code authors are expected to tag the revisions they want users to use,// including prereleases. However, not all authors tag versions at all,// and not all commits a user might want to try will have tags.// A pseudo-version is a version with a special form that allows us to// address an untagged commit and order that version with respect to// other versions we might encounter.//// A pseudo-version takes one of the general forms://// (1) vX.0.0-yyyymmddhhmmss-abcdef123456// (2) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456// (3) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible// (4) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456// (5) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible//// If there is no recently tagged version with the right major version vX,// then form (1) is used, creating a space of pseudo-versions at the bottom// of the vX version range, less than any tagged version, including the unlikely v0.0.0.//// If the most recent tagged version before the target commit is vX.Y.Z or vX.Y.Z+incompatible,// then the pseudo-version uses form (2) or (3), making it a prerelease for the next// possible semantic version after vX.Y.Z. The leading 0 segment in the prerelease string// ensures that the pseudo-version compares less than possible future explicit prereleases// like vX.Y.(Z+1)-rc1 or vX.Y.(Z+1)-1.//// If the most recent tagged version before the target commit is vX.Y.Z-pre or vX.Y.Z-pre+incompatible,// then the pseudo-version uses form (4) or (5), making it a slightly later prerelease.package moduleimport ()var pseudoVersionRE = lazyregexp.New(`^v[0-9]+\.(0\.0-|\d+\.\d+-([^+]*\.)?0\.)\d{14}-[A-Za-z0-9]+(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$`)const PseudoVersionTimestampFormat = "20060102150405"// PseudoVersion returns a pseudo-version for the given major version ("v1")// preexisting older tagged version ("" or "v1.2.3" or "v1.2.3-pre"), revision time,// and revision identifier (usually a 12-byte commit hash prefix).func (, string, time.Time, string) string {if == "" {= "v0"}:= fmt.Sprintf("%s-%s", .UTC().Format(PseudoVersionTimestampFormat), ):= semver.Build()= semver.Canonical()if == "" {return + ".0.0-" + // form (1)}if semver.Prerelease() != "" {return + ".0." + + // form (4), (5)}// Form (2), (3).// Extract patch from vMAJOR.MINOR.PATCH:= strings.LastIndex(, ".") + 1, := [:], [:]// Reassemble.return + incDecimal() + "-0." + +}// ZeroPseudoVersion returns a pseudo-version with a zero timestamp and// revision, which may be used as a placeholder.func ( string) string {return PseudoVersion(, "", time.Time{}, "000000000000")}// incDecimal returns the decimal string incremented by 1.func ( string) string {// Scan right to left turning 9s to 0s until you find a digit to increment.:= []byte():= len() - 1for ; >= 0 && [] == '9'; -- {[] = '0'}if >= 0 {[]++} else {// digits is all zeros[0] = '1'= append(, '0')}return string()}// decDecimal returns the decimal string decremented by 1, or the empty string// if the decimal is all zeroes.func ( string) string {// Scan right to left turning 0s to 9s until you find a digit to decrement.:= []byte():= len() - 1for ; >= 0 && [] == '0'; -- {[] = '9'}if < 0 {// decimal is all zerosreturn ""}if == 0 && [] == '1' && len() > 1 {= [1:]} else {[]--}return string()}// IsPseudoVersion reports whether v is a pseudo-version.func ( string) bool {return strings.Count(, "-") >= 2 && semver.IsValid() && pseudoVersionRE.MatchString()}// IsZeroPseudoVersion returns whether v is a pseudo-version with a zero base,// timestamp, and revision, as returned by [ZeroPseudoVersion].func ( string) bool {return == ZeroPseudoVersion(semver.Major())}// PseudoVersionTime returns the time stamp of the pseudo-version v.// It returns an error if v is not a pseudo-version or if the time stamp// embedded in the pseudo-version is not a valid time.func ( string) (time.Time, error) {, , , , := parsePseudoVersion()if != nil {return time.Time{},}, := time.Parse("20060102150405", )if != nil {return time.Time{}, &InvalidVersionError{Version: ,Pseudo: true,Err: fmt.Errorf("malformed time %q", ),}}return , nil}// PseudoVersionRev returns the revision identifier of the pseudo-version v.// It returns an error if v is not a pseudo-version.func ( string) ( string, error) {_, _, , _, = parsePseudoVersion()return}// PseudoVersionBase returns the canonical parent version, if any, upon which// the pseudo-version v is based.//// If v has no parent version (that is, if it is "vX.0.0-[…]"),// PseudoVersionBase returns the empty string and a nil error.func ( string) (string, error) {, , , , := parsePseudoVersion()if != nil {return "",}switch := semver.Prerelease(); {case "":// vX.0.0-yyyymmddhhmmss-abcdef123456 → ""if != "" {// Pseudo-versions of the form vX.0.0-yyyymmddhhmmss-abcdef123456+incompatible// are nonsensical: the "vX.0.0-" prefix implies that there is no parent tag,// but the "+incompatible" suffix implies that the major version of// the parent tag is not compatible with the module's import path.//// There are a few such entries in the index generated by proxy.golang.org,// but we believe those entries were generated by the proxy itself.return "", &InvalidVersionError{Version: ,Pseudo: true,Err: fmt.Errorf("lacks base version, but has build metadata %q", ),}}return "", nilcase "-0":// vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z// vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z+incompatible= strings.TrimSuffix(, ):= strings.LastIndexByte(, '.')if < 0 {panic("base from parsePseudoVersion missing patch number: " + )}:= decDecimal([+1:])if == "" {// vX.0.0-0 is invalid, but has been observed in the wild in the index// generated by requests to proxy.golang.org.//// NOTE(bcmills): I cannot find a historical bug that accounts for// pseudo-versions of this form, nor have I seen such versions in any// actual go.mod files. If we find actual examples of this form and a// reasonable theory of how they came into existence, it seems fine to// treat them as equivalent to vX.0.0 (especially since the invalid// pseudo-versions have lower precedence than the real ones). For now, we// reject them.return "", &InvalidVersionError{Version: ,Pseudo: true,Err: fmt.Errorf("version before %s would have negative patch number", ),}}return [:+1] + + , nildefault:// vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z-pre// vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z-pre+incompatibleif !strings.HasSuffix(, ".0") {panic(`base from parsePseudoVersion missing ".0" before date: ` + )}return strings.TrimSuffix(, ".0") + , nil}}var errPseudoSyntax = errors.New("syntax error")func ( string) (, , , string, error) {if !IsPseudoVersion() {return "", "", "", "", &InvalidVersionError{Version: ,Pseudo: true,Err: errPseudoSyntax,}}= semver.Build()= strings.TrimSuffix(, ):= strings.LastIndex(, "-"), = [:], [+1:]:= strings.LastIndex(, "-")if := strings.LastIndex(, "."); > {= [:] // "vX.Y.Z-pre.0" or "vX.Y.(Z+1)-0"= [+1:]} else {= [:] // "vX.0.0"= [+1:]}return , , , , nil}
The pages are generated with Golds v0.8.4. (GOOS=linux GOARCH=amd64)