Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 37 additions & 27 deletions version.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const (
rc = releasePhase(3)
cloudonly = releasePhase(4)
stable = releasePhase(5)
custom = releasePhase(6)
adhoc = releasePhase(6)
)

// Version represents a CockroachDB (binary) version. Versions consist of three parts:
Expand All @@ -53,11 +53,11 @@ type Version struct {
// the fields are compared in the order listed here, and the earliest field with
// a difference determines the relative ordering of two unequal versions.
//
// The reference order: year, ordinal, patch, phase, phaseOrdinal, phaseSubOrdinal, nightlyOrdinal
year, ordinal, patch int
phase releasePhase
phaseOrdinal, phaseSubOrdinal, nightlyOrdinal int
customLabel string
// The reference order: year, ordinal, patch, phase, phaseOrdinal, phaseSubOrdinal, customOrdinal
year, ordinal, patch int
phase releasePhase
phaseOrdinal, phaseSubOrdinal, customOrdinal int
adhocLabel string
// raw is the original, unprocessed string this Version was created with
raw string
}
Expand All @@ -82,7 +82,7 @@ func (v Version) Patch() int {
// - %p: phase sort order (see the top of version.go)
// - %o: phase ordinal (eg, the 1 in "v24.1.0-rc.1")
// - %s: phase sub-ordinal (eg the 2 in "v24.1.0-rc.1-cloudonly.2")
// - %n: nightly ordinal (eg the 12 in "v24.1.0-12-gabcdef")
// - %n: adhoc build ordinal (eg the 12 in "v24.1.0-12-gabcdef")
// - %%: literal "%"
func (v Version) Format(formatStr string) string {
placeholderRe := regexp.MustCompile("%[^%XYZpPosn]")
Expand All @@ -96,7 +96,7 @@ func (v Version) Format(formatStr string) string {
beta: "beta",
rc: "rc",
cloudonly: "cloudonly",
custom: "",
adhoc: "",
stable: "",
}

Expand All @@ -107,7 +107,7 @@ func (v Version) Format(formatStr string) string {
formatStr = strings.ReplaceAll(formatStr, "%P", phaseName[v.phase])
formatStr = strings.ReplaceAll(formatStr, "%o", strconv.Itoa(v.phaseOrdinal))
formatStr = strings.ReplaceAll(formatStr, "%s", strconv.Itoa(v.phaseSubOrdinal))
formatStr = strings.ReplaceAll(formatStr, "%n", strconv.Itoa(v.nightlyOrdinal))
formatStr = strings.ReplaceAll(formatStr, "%n", strconv.Itoa(v.customOrdinal))
formatStr = strings.ReplaceAll(formatStr, "%%", "%")
return formatStr
}
Expand Down Expand Up @@ -171,9 +171,19 @@ func (v Version) IsPrerelease() bool {
return v.phase < cloudonly && !v.Empty()
}

// IsCustomOrNightlyBuild determines if the version is a custom build or nightly build.
func (v Version) IsCustomOrNightlyBuild() bool {
return v.nightlyOrdinal > 0
// IsCustomOrAdhocBuild determines if the version is a adhoc build or adhoc build.
func (v Version) IsCustomOrAdhocBuild() bool {
return v.IsCustomBuild() || v.IsAdhocBuild()
}

// IsCustomBuild determines if the version is a adhoc build.
func (v Version) IsCustomBuild() bool {
return v.customOrdinal > 0
}

// IsAdhocBuild determines if the version is a adhoc build.
func (v Version) IsAdhocBuild() bool {
return v.adhocLabel != ""
}

// IsCloudOnlyBuild determines if the version is a CockroachDB Cloud specific build.
Expand All @@ -197,17 +207,17 @@ func Parse(str string) (Version, error) {
patterns := []*regexp.Regexp{
regexp.MustCompile(`^v(?P<year>[1-9][0-9]*)\.(?P<ordinal>[1-9][0-9]*)\.(?P<patch>(?:[1-9][0-9]*|0))(?:-fips)?$`),
regexp.MustCompile(`^v(?P<year>[1-9][0-9]*)\.(?P<ordinal>[1-9][0-9]*)\.(?P<patch>(?:[1-9][0-9]*|0))-(?P<phase>alpha|beta|rc|cloudonly)\.(?P<phaseOrdinal>[0-9]+)(?:-fips)?$`),
regexp.MustCompile(`^v(?P<year>[1-9][0-9]*)\.(?P<ordinal>[1-9][0-9]*)\.(?P<patch>(?:[1-9][0-9]*|0))-(?P<nightlyOrdinal>(?:[1-9][0-9]*|0))-g[a-f0-9]+(?:-fips)?$`),
regexp.MustCompile(`^v(?P<year>[1-9][0-9]*)\.(?P<ordinal>[1-9][0-9]*)\.(?P<patch>(?:[1-9][0-9]*|0))-(?P<phase>alpha|beta|rc|cloudonly).(?P<phaseOrdinal>[0-9]+)-(?P<nightlyOrdinal>(?:[1-9][0-9]*|0))-g[a-f0-9]+(?:-fips)?$`),
regexp.MustCompile(`^v(?P<year>[1-9][0-9]*)\.(?P<ordinal>[1-9][0-9]*)\.(?P<patch>(?:[1-9][0-9]*|0))-(?P<customOrdinal>(?:[1-9][0-9]*|0))-g[a-f0-9]+(?:-fips)?$`),
regexp.MustCompile(`^v(?P<year>[1-9][0-9]*)\.(?P<ordinal>[1-9][0-9]*)\.(?P<patch>(?:[1-9][0-9]*|0))-(?P<phase>alpha|beta|rc|cloudonly).(?P<phaseOrdinal>[0-9]+)-(?P<customOrdinal>(?:[1-9][0-9]*|0))-g[a-f0-9]+(?:-fips)?$`),
regexp.MustCompile(`^v(?P<year>[1-9][0-9]*)\.(?P<ordinal>[1-9][0-9]*)\.(?P<patch>(?:[1-9][0-9]*|0))-(?P<phase>alpha|beta|rc|cloudonly).(?P<phaseOrdinal>[0-9]+)-cloudonly(-rc|\.)(?P<phaseSubOrdinal>(?:[1-9][0-9]*|0))$`),
regexp.MustCompile(`^v(?P<year>[1-9][0-9]*)\.(?P<ordinal>[1-9][0-9]*)\.(?P<patch>(?:[1-9][0-9]*|0))-(?P<phase>cloudonly)-rc(?P<phaseOrdinal>[0-9]+)$`),
regexp.MustCompile(`^v(?P<year>[1-9][0-9]*)\.(?P<ordinal>[1-9][0-9]*)\.(?P<patch>(?:[1-9][0-9]*|0))-(?P<phase>cloudonly)(?P<phaseOrdinal>[0-9]+)?$`),

// vX.Y.Z-<anything> will sort after the corresponding "plain" vX.Y.Z version
regexp.MustCompile(`^v(?P<year>[1-9][0-9]*)\.(?P<ordinal>[1-9][0-9]*)\.(?P<patch>(?:[1-9][0-9]*|0))-(?P<customLabel>[-a-zA-Z0-9\.\+]+)$`),
regexp.MustCompile(`^v(?P<year>[1-9][0-9]*)\.(?P<ordinal>[1-9][0-9]*)\.(?P<patch>(?:[1-9][0-9]*|0))-(?P<adhocLabel>[-a-zA-Z0-9\.\+]+)$`),

// sha256:<hash>:latest-vX.Y-build will sort just after vX.Y.0, but before vX.Y.1
regexp.MustCompile(`^sha256:(?P<customLabel>[^:]+):latest-v(?P<year>[1-9][0-9]*)\.(?P<ordinal>[1-9][0-9]*)-build$`),
regexp.MustCompile(`^sha256:(?P<adhocLabel>[^:]+):latest-v(?P<year>[1-9][0-9]*)\.(?P<ordinal>[1-9][0-9]*)-build$`),
}

preReleasePhase := map[string]releasePhase{
Expand Down Expand Up @@ -257,15 +267,15 @@ func Parse(str string) (Version, error) {
}
}

// nightly/custom builds, eg -10-g7890abcd
if ord := submatch(pat, matches, "nightlyOrdinal"); ord != "" {
v.nightlyOrdinal, _ = strconv.Atoi(ord)
// adhoc/adhoc builds, eg -10-g7890abcd
if ord := submatch(pat, matches, "customOrdinal"); ord != "" {
v.customOrdinal, _ = strconv.Atoi(ord)
}

// arbitrary/custom build tags; we have these old versions and need to parse them
if customLabel := submatch(pat, matches, "customLabel"); customLabel != "" {
v.phase = custom
v.customLabel = customLabel
// arbitrary/adhoc build tags; we have these old versions and need to parse them
if adhocLabel := submatch(pat, matches, "adhocLabel"); adhocLabel != "" {
v.phase = adhoc
v.adhocLabel = adhocLabel
}

return v, nil
Expand Down Expand Up @@ -296,12 +306,12 @@ func MustParse(str string) Version {
// rc, cloudonly. Pre-release versions will look like "v24.1.0-cloudonly.1"
// or "v23.2.0-rc.1".
//
// Additionally, we have custom builds, which have suffixes like "-<n>-g<hex>",
// Additionally, we have adhoc builds, which have suffixes like "-<n>-g<hex>",
// where <n> is an integer commit count past the branch point, and <hex> is
// the git SHA. These versions sort AFTER the corresponding "normal" version,
// eg "v24.1.0-1-g9cbe7c5281" is AFTER "v24.1.0".
//
// A version can have both a pre-release and custom build suffix, like
// A version can have both a pre-release and adhoc build suffix, like
// "v24.1.0-rc.2-14-g<hex>". In these cases, the pre-release portion has precedence,
// so this example would sort after v24.1.0-rc.2, but before v24.1.0-rc.3.
func (v Version) Compare(w Version) int {
Expand All @@ -323,10 +333,10 @@ func (v Version) Compare(w Version) int {
if rslt := cmp.Compare(v.phaseSubOrdinal, w.phaseSubOrdinal); rslt != 0 {
return rslt
}
if rslt := cmp.Compare(v.nightlyOrdinal, w.nightlyOrdinal); rslt != 0 {
if rslt := cmp.Compare(v.customOrdinal, w.customOrdinal); rslt != 0 {
return rslt
}
if rslt := cmp.Compare(v.customLabel, w.customLabel); rslt != 0 {
if rslt := cmp.Compare(v.adhocLabel, w.adhocLabel); rslt != 0 {
return rslt
}
return 0
Expand Down
108 changes: 75 additions & 33 deletions version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestVersion_Getters(t *testing.T) {

require.True(t, v.IsPrerelease())
require.False(t, v.IsCloudOnlyBuild())
require.False(t, v.IsCustomOrNightlyBuild())
require.False(t, v.IsCustomOrAdhocBuild())
}

func TestVersion_IsPrerelease(t *testing.T) {
Expand Down Expand Up @@ -79,31 +79,73 @@ func TestVersion_IsPrerelease(t *testing.T) {
require.False(t, MustParse("v23.2.0-cloudonly2").IsPrerelease())
}

func TestVersion_IsCustomOrNightlyBuild(t *testing.T) {
// Valid pre-release versions
require.False(t, MustParse("v20.2.0-beta.3").IsCustomOrNightlyBuild())
require.False(t, MustParse("v19.1.0-rc.5").IsCustomOrNightlyBuild())
require.False(t, MustParse("v20.2.0-alpha.1").IsCustomOrNightlyBuild())
require.True(t, MustParse("v21.1.0-rc.2-163-g122c66f436").IsCustomOrNightlyBuild())
require.True(t, MustParse("v21.1.0-beta.5-57-gf05a57face").IsCustomOrNightlyBuild())
require.True(t, MustParse("v21.1.0-alpha.3-2846-g7ae3ac92f7").IsCustomOrNightlyBuild())

// Valid [cloudonly] pre-release versions
require.False(t, MustParse("v23.2.0-rc.2-cloudonly-rc2").IsCustomOrNightlyBuild())
func TestVersion_CustomAndAdhocBuilds(t *testing.T) {
builds := []struct {
version string
adhoc bool
custom bool
}{
// Valid pre-release versions
{version: "v20.2.0-beta.3", adhoc: false, custom: false},
{version: "v19.1.0-rc.5", adhoc: false, custom: false},
{version: "v20.2.0-alpha.1", adhoc: false, custom: false},
{version: "v21.1.0-rc.2-163-g122c66f436", adhoc: false, custom: true},
{version: "v21.1.0-beta.5-57-gf05a57face", adhoc: false, custom: true},
{version: "v21.1.0-alpha.3-2846-g7ae3ac92f7", adhoc: false, custom: true},

// Valid [cloudonly] pre-release versions
{version: "v23.2.0-rc.2-cloudonly-rc2", adhoc: false, custom: false},

// Valid production versions
{version: "v19.2.6", adhoc: false, custom: false},
{version: "v21.1.0", adhoc: false, custom: false},
{version: "v21.1.0-247-g5668206478", adhoc: false, custom: true},

// Valid [cloudonly] production versions
{version: "v23.1.12-cloudonly-rc2", adhoc: false, custom: false},

// Valid [cloudonly] v23.2.0 (production) versions may or may not have "rc" after "-cloudonly" suffix.
{version: "v23.2.0-cloudonly-rc2", adhoc: false, custom: false},
{version: "v23.2.0-cloudonly", adhoc: false, custom: false},
{version: "v23.2.0-cloudonly.1", adhoc: false, custom: false},
{version: "v23.2.0-cloudonly2", adhoc: false, custom: false},

// All other adhoc labels
{version: "v23.2.0-arbitrary-adhoc-label", adhoc: true, custom: false},
}

// Valid production versions
require.False(t, MustParse("v19.2.6").IsCustomOrNightlyBuild())
require.False(t, MustParse("v21.1.0").IsCustomOrNightlyBuild())
require.True(t, MustParse("v21.1.0-247-g5668206478").IsCustomOrNightlyBuild())
t.Run("IsCustomOrAdhocBuild", func(t *testing.T) {
for _, tc := range builds {
v := MustParse(tc.version)
if tc.adhoc || tc.custom {
require.True(t, v.IsCustomOrAdhocBuild())
} else {
require.False(t, v.IsCustomOrAdhocBuild())
}
}
})

// Valid [cloudonly] production versions
require.False(t, MustParse("v23.1.12-cloudonly-rc2").IsCustomOrNightlyBuild())
t.Run("IsAdhocBuild", func(t *testing.T) {
for _, tc := range builds {
v := MustParse(tc.version)
if tc.adhoc {
require.True(t, v.IsAdhocBuild())
} else {
require.False(t, v.IsAdhocBuild())
}
}
})

// Valid [cloudonly] v23.2.0 (production) versions may or may not have "rc" after "-cloudonly" suffix.
require.False(t, MustParse("v23.2.0-cloudonly-rc2").IsCustomOrNightlyBuild())
require.False(t, MustParse("v23.2.0-cloudonly").IsCustomOrNightlyBuild())
require.False(t, MustParse("v23.2.0-cloudonly.1").IsCustomOrNightlyBuild())
require.False(t, MustParse("v23.2.0-cloudonly2").IsCustomOrNightlyBuild())
t.Run("IsCustomBuild", func(t *testing.T) {
for _, tc := range builds {
v := MustParse(tc.version)
if tc.custom {
require.True(t, v.IsCustomBuild())
} else {
require.False(t, v.IsCustomBuild())
}
}
})
}

func TestVersion_IsCloudOnlyBuild(t *testing.T) {
Expand Down Expand Up @@ -305,15 +347,15 @@ func TestParse(t *testing.T) {
phaseOrdinal: 2,
},
},
// custom releases
// adhoc releases
{
raw: "v24.2.3-12-gabcd1234",
want: Version{
year: 24,
ordinal: 2,
patch: 3,
phase: stable,
nightlyOrdinal: 12,
year: 24,
ordinal: 2,
patch: 3,
phase: stable,
customOrdinal: 12,
},
},
} {
Expand Down Expand Up @@ -400,7 +442,7 @@ func TestVersionCompare(t *testing.T) {
want: aLessThanB,
},

// even if there's a prerelease or nightly tag
// even if there's a prerelease or custom tag
{
a: "v20.2.7",
b: "v21.1.0-alpha.3",
Expand Down Expand Up @@ -454,7 +496,7 @@ func TestVersionCompare(t *testing.T) {
want: aLessThanB,
},

// nightly & custom builds are greater than "regular" builds, prereleases, and cloudonly
// custom & adhoc builds are greater than "regular" builds, prereleases, and cloudonly
{
a: "v21.1.0-alpha.3",
b: "v21.1.0-1-g9cbe7c5281",
Expand Down Expand Up @@ -533,12 +575,12 @@ func TestVersionOrdering(t *testing.T) {
want: []string{"v20.2.10", "v21.1.0-alpha.1", "v21.1.0-beta.2", "v21.1.0-rc.1", "v21.1.0-cloudonly.1", "v21.1.0"},
},
{
name: "sorts custom builds after corresponding normal version",
name: "sorts adhoc builds after corresponding normal version",
input: []string{"v21.1.0-1-g9cbe7c5281", "v21.1.0", "v21.1.0-rc.1"},
want: []string{"v21.1.0-rc.1", "v21.1.0", "v21.1.0-1-g9cbe7c5281"},
},
{
name: "sorts nonstandard custom builds after corresponding normal version",
name: "sorts nonstandard adhoc builds after corresponding normal version",
input: []string{"v21.1.0-customLabel", "v21.1.0", "v21.1.0-rc.1"},
want: []string{"v21.1.0-rc.1", "v21.1.0", "v21.1.0-customLabel"},
},
Expand Down