1 // Copyright 2018 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
7 // Code authors are expected to tag the revisions they want users to use,
8 // including prereleases. However, not all authors tag versions at all,
9 // and not all commits a user might want to try will have tags.
10 // A pseudo-version is a version with a special form that allows us to
11 // address an untagged commit and order that version with respect to
12 // other versions we might encounter.
14 // A pseudo-version takes one of the general forms:
16 // (1) vX.0.0-yyyymmddhhmmss-abcdef123456
17 // (2) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456
18 // (3) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible
19 // (4) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456
20 // (5) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible
22 // If there is no recently tagged version with the right major version vX,
23 // then form (1) is used, creating a space of pseudo-versions at the bottom
24 // of the vX version range, less than any tagged version, including the unlikely v0.0.0.
26 // If the most recent tagged version before the target commit is vX.Y.Z or vX.Y.Z+incompatible,
27 // then the pseudo-version uses form (2) or (3), making it a prerelease for the next
28 // possible semantic version after vX.Y.Z. The leading 0 segment in the prerelease string
29 // ensures that the pseudo-version compares less than possible future explicit prereleases
30 // like vX.Y.(Z+1)-rc1 or vX.Y.(Z+1)-1.
32 // If the most recent tagged version before the target commit is vX.Y.Z-pre or vX.Y.Z-pre+incompatible,
33 // then the pseudo-version uses form (4) or (5), making it a slightly later prerelease.
38 "cmd/go/internal/semver"
45 // PseudoVersion returns a pseudo-version for the given major version ("v1")
46 // preexisting older tagged version ("" or "v1.2.3" or "v1.2.3-pre"), revision time,
47 // and revision identifier (usually a 12-byte commit hash prefix).
48 func PseudoVersion(major
, older
string, t time
.Time
, rev
string) string {
52 major
= strings
.TrimSuffix(major
, "-unstable") // make gopkg.in/macaroon-bakery.v2-unstable use "v2"
53 segment
:= fmt
.Sprintf("%s-%s", t
.UTC().Format("20060102150405"), rev
)
54 build
:= semver
.Build(older
)
55 older
= semver
.Canonical(older
)
57 return major
+ ".0.0-" + segment
// form (1)
59 if semver
.Prerelease(older
) != "" {
60 return older
+ ".0." + segment
+ build
// form (4), (5)
64 // Extract patch from vMAJOR.MINOR.PATCH
65 v
:= older
[:len(older
)]
66 i
:= strings
.LastIndex(v
, ".") + 1
67 v
, patch
:= v
[:i
], v
[i
:]
69 // Increment PATCH by adding 1 to decimal:
70 // scan right to left turning 9s to 0s until you find a digit to increment.
71 // (Number might exceed int64, but math/big is overkill.)
72 digits
:= []byte(patch
)
73 for i
= len(digits
) - 1; i
>= 0 && digits
[i
] == '9'; i
-- {
79 // digits is all zeros
81 digits
= append(digits
, '0')
83 patch
= string(digits
)
86 return v
+ patch
+ "-0." + segment
+ build
89 var pseudoVersionRE
= regexp
.MustCompile(`^v[0-9]+\.(0\.0-|\d+\.\d+-([^+]*\.)?0\.)\d{14}-[A-Za-z0-9]+(\+incompatible)?$`)
91 // IsPseudoVersion reports whether v is a pseudo-version.
92 func IsPseudoVersion(v
string) bool {
93 return strings
.Count(v
, "-") >= 2 && semver
.IsValid(v
) && pseudoVersionRE
.MatchString(v
)
96 // PseudoVersionTime returns the time stamp of the pseudo-version v.
97 // It returns an error if v is not a pseudo-version or if the time stamp
98 // embedded in the pseudo-version is not a valid time.
99 func PseudoVersionTime(v
string) (time
.Time
, error
) {
100 timestamp
, _
, err
:= parsePseudoVersion(v
)
101 t
, err
:= time
.Parse("20060102150405", timestamp
)
103 return time
.Time
{}, fmt
.Errorf("pseudo-version with malformed time %s: %q", timestamp
, v
)
108 // PseudoVersionRev returns the revision identifier of the pseudo-version v.
109 // It returns an error if v is not a pseudo-version.
110 func PseudoVersionRev(v
string) (rev
string, err error
) {
111 _
, rev
, err
= parsePseudoVersion(v
)
115 func parsePseudoVersion(v
string) (timestamp
, rev
string, err error
) {
116 if !IsPseudoVersion(v
) {
117 return "", "", fmt
.Errorf("malformed pseudo-version %q", v
)
119 v
= strings
.TrimSuffix(v
, "+incompatible")
120 j
:= strings
.LastIndex(v
, "-")
121 v
, rev
= v
[:j
], v
[j
+1:]
122 i
:= strings
.LastIndex(v
, "-")
123 if j
:= strings
.LastIndex(v
, "."); j
> i
{
128 return timestamp
, rev
, nil