From 1aec043eebd3725f3e0b762baa719561323d09e5 Mon Sep 17 00:00:00 2001 From: Maceo Thompson Date: Wed, 2 Oct 2024 16:53:45 -0400 Subject: [PATCH] internal/semver: add SemverToGoTag Add SemverToGoTag, which reverses GoTagToSemver. The exception is when converting a go tag to the semver would have resulted in an invalid semver (such as go RC versions). GoTagToSemver returns an empty string in that case, and as such is not reversible. Change-Id: I8d68c2372261b1ec7ec8b72354ac1919ad24d6f6 Reviewed-on: https://go-review.googlesource.com/c/vuln/+/617396 LUCI-TryBot-Result: Go LUCI Reviewed-by: Zvonimir Pavlinovic --- internal/semver/semver.go | 45 ++++++++++++++++++++++++++++++++++ internal/semver/semver_test.go | 21 ++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/internal/semver/semver.go b/internal/semver/semver.go index fe0f701b..efe1e006 100644 --- a/internal/semver/semver.go +++ b/internal/semver/semver.go @@ -7,6 +7,7 @@ package semver import ( + "fmt" "regexp" "strings" @@ -93,3 +94,47 @@ func GoTagToSemver(tag string) string { } return version } + +// This is a modified copy of pkgsite/internal/stlib:TagForVersion +func SemverToGoTag(v string) string { + // Special case: v1.0.0 => go1. + if v == "v1.0.0" { + return "go1" + } + + goVersion := semver.Canonical(v) + prerelease := semver.Prerelease(goVersion) + versionWithoutPrerelease := strings.TrimSuffix(goVersion, prerelease) + patch := strings.TrimPrefix(versionWithoutPrerelease, semver.MajorMinor(goVersion)+".") + if patch == "0" && (semver.Compare(v, "v1.21.0") < 0 || prerelease != "") { + // Starting with go1.21.0, the first patch version includes .0. + // Prereleases do not include .0 (we don't do prereleases for other patch releases). + versionWithoutPrerelease = strings.TrimSuffix(versionWithoutPrerelease, ".0") + } + goVersion = fmt.Sprintf("go%s", strings.TrimPrefix(versionWithoutPrerelease, "v")) + if prerelease != "" { + i := finalDigitsIndex(prerelease) + if i >= 1 { + // Remove the dot. + prerelease = prerelease[:i-1] + prerelease[i:] + } + goVersion += prerelease + } + return goVersion +} + +// finalDigitsIndex returns the index of the first digit in the sequence of digits ending s. +// If s doesn't end in digits, it returns -1. +func finalDigitsIndex(s string) int { + // Assume ASCII (since the semver package does anyway). + var i int + for i = len(s) - 1; i >= 0; i-- { + if s[i] < '0' || s[i] > '9' { + break + } + } + if i == len(s)-1 { + return -1 + } + return i + 1 +} diff --git a/internal/semver/semver_test.go b/internal/semver/semver_test.go index 4c4a3c84..115da1bd 100644 --- a/internal/semver/semver_test.go +++ b/internal/semver/semver_test.go @@ -39,6 +39,27 @@ func TestGoTagToSemver(t *testing.T) { } } +func TestSemverToGoTag(t *testing.T) { + for _, test := range []struct { + v string + want string + }{ + {"v1.19.0", "go1.19"}, + {"v1.20.0-pre.4", "go1.20-pre4"}, + {"v1.0.0", "go1"}, + {"v1.20.0-rc.1", "go1.20-rc1"}, + {"v1.20.0-beta.1", "go1.20-beta1"}, + {"v1.20.0-alpha.1", "go1.20-alpha1"}, + {"v1.20.0-alpha.1+incompatible", "go1.20-alpha1"}, + {"v1.20.0-alpha.1-0.20220101230456-1234abcd", "go1.20-alpha.1-0.20220101230456-1234abcd"}, + } { + got := SemverToGoTag(test.v) + if got != test.want { + t.Errorf("want %s; got %s", test.want, got) + } + } +} + func TestLess(t *testing.T) { for _, test := range []struct { v1 string