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
6 changes: 6 additions & 0 deletions internal/promote/promote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,12 @@ func TestStripRCSuffix(t *testing.T) {
}
}

func TestStripRCSuffix_HotfixVersion(t *testing.T) {
if got := stripRCSuffix("v1.4.0-rc.2.hotfix.1"); got != "v1.4.0" {
t.Errorf("stripRCSuffix(%q) = %q, want %q", "v1.4.0-rc.2.hotfix.1", got, "v1.4.0")
}
}

func TestPromotionResultToJSON(t *testing.T) {
result := &PromotionResult{
Success: true,
Expand Down
107 changes: 104 additions & 3 deletions internal/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Version struct {
Minor int
Patch int
PreRelease int // -1 means no pre-release suffix, >= 0 is the RC number
Hotfix int // -1 means no hotfix segment, >= 0 is the hotfix number
Prefix string // e.g., "v" or custom prefix
}

Expand All @@ -29,8 +30,9 @@ const (
BumpMajor
)

// semverRegex matches versions like v1.2.3 or v1.2.3-rc.4
var semverRegex = regexp.MustCompile(`^([a-zA-Z]*)(\d+)\.(\d+)\.(\d+)(?:-rc\.(\d+))?$`)
// semverRegex matches versions like v1.2.3, v1.2.3-rc.4, or v1.2.3-rc.4.hotfix.5.
// The hotfix segment is only valid nested after an rc segment.
var semverRegex = regexp.MustCompile(`^([a-zA-Z]*)(\d+)\.(\d+)\.(\d+)(?:-rc\.(\d+)(?:\.hotfix\.(\d+))?)?$`)

// Parse parses a version string into a Version struct
func Parse(s string) (*Version, error) {
Expand All @@ -48,11 +50,17 @@ func Parse(s string) (*Version, error) {
preRelease, _ = strconv.Atoi(matches[5])
}

hotfix := -1
if matches[6] != "" {
hotfix, _ = strconv.Atoi(matches[6])
}

return &Version{
Major: major,
Minor: minor,
Patch: patch,
PreRelease: preRelease,
Hotfix: hotfix,
Prefix: matches[1],
}, nil
}
Expand All @@ -61,7 +69,11 @@ func Parse(s string) (*Version, error) {
func (v *Version) String() string {
base := fmt.Sprintf("%s%d.%d.%d", v.Prefix, v.Major, v.Minor, v.Patch)
if v.PreRelease >= 0 {
return fmt.Sprintf("%s-rc.%d", base, v.PreRelease)
rc := fmt.Sprintf("%s-rc.%d", base, v.PreRelease)
if v.Hotfix >= 0 {
return fmt.Sprintf("%s.hotfix.%d", rc, v.Hotfix)
}
return rc
}
return base
}
Expand All @@ -78,6 +90,7 @@ func (v *Version) BaseVersion() *Version {
Minor: v.Minor,
Patch: v.Patch,
PreRelease: -1,
Hotfix: -1,
Prefix: v.Prefix,
}
}
Expand All @@ -89,6 +102,7 @@ func (v *Version) WithRC(rc int) *Version {
Minor: v.Minor,
Patch: v.Patch,
PreRelease: rc,
Hotfix: -1,
Prefix: v.Prefix,
}
}
Expand All @@ -100,6 +114,7 @@ func (v *Version) Bump(bump BumpType) *Version {
Minor: v.Minor,
Patch: v.Patch,
PreRelease: -1,
Hotfix: -1,
Prefix: v.Prefix,
}

Expand Down Expand Up @@ -181,6 +196,7 @@ func (c *Calculator) CalculateNext(currentDevVersion, nextEnvVersion string, com
Minor: 0,
Patch: 0,
PreRelease: -1,
Hotfix: -1,
Prefix: c.prefix,
}
} else {
Expand Down Expand Up @@ -266,6 +282,91 @@ func GetLatestRelease(tags []string) (*Version, error) {
return latest, nil
}

// WithHotfix returns a copy of the version with the given hotfix number,
// preserving the major, minor, patch, pre-release, and prefix.
func (v *Version) WithHotfix(m int) *Version {
return &Version{
Major: v.Major,
Minor: v.Minor,
Patch: v.Patch,
PreRelease: v.PreRelease,
Hotfix: m,
Prefix: v.Prefix,
}
}

// NextHotfix returns a copy of the version with its hotfix number incremented.
// If the version has no hotfix segment yet, the result is hotfix 1, so an
// rc.2 version becomes rc.2.hotfix.1.
func (v *Version) NextHotfix() *Version {
next := 1
if v.Hotfix >= 0 {
next = v.Hotfix + 1
}
return v.WithHotfix(next)
}

// Compare returns -1, 0, or +1 reporting whether v sorts before, equal to, or
// after other under semver precedence. It compares major, minor, and patch
// numerically; then a version with a pre-release sorts before one without; then
// pre-release numbers compare numerically; then a version without a hotfix
// segment sorts before one with a hotfix; then hotfix numbers compare
// numerically.
func (v *Version) Compare(other *Version) int {
if c := compareInt(v.Major, other.Major); c != 0 {
return c
}
if c := compareInt(v.Minor, other.Minor); c != 0 {
return c
}
if c := compareInt(v.Patch, other.Patch); c != 0 {
return c
}

// A pre-release version has lower precedence than the associated release.
vPre := v.PreRelease >= 0
oPre := other.PreRelease >= 0
switch {
case vPre && !oPre:
return -1
case !vPre && oPre:
return 1
case !vPre && !oPre:
return 0
}

if c := compareInt(v.PreRelease, other.PreRelease); c != 0 {
return c
}

// No-hotfix sorts before any hotfix on the same rc.
vHot := v.Hotfix >= 0
oHot := other.Hotfix >= 0
switch {
case !vHot && oHot:
return -1
case vHot && !oHot:
return 1
case !vHot && !oHot:
return 0
}

return compareInt(v.Hotfix, other.Hotfix)
}

// compareInt returns -1, 0, or +1 reporting whether a is less than, equal to,
// or greater than b.
func compareInt(a, b int) int {
switch {
case a < b:
return -1
case a > b:
return 1
default:
return 0
}
}

// StripRC removes the pre-release suffix for publishing
func StripRC(version string) (string, error) {
v, err := Parse(version)
Expand Down
Loading
Loading