fix(cli): use numeric comparison for pre-release version segments (#201)
isNewer compares pre-release suffixes lexicographically, which gives wrong results for multi-digit numbers: "beta.10" < "beta.2" because '1' < '2' in ASCII. This causes the update checker to miss newer pre-release versions (e.g. v1.2.3-beta.10 not recognized as newer than v1.2.3-beta.2). Split pre-release strings on "." and compare numeric segments as integers. Add unit tests covering stable, pre-release, and dev version comparisons.
This commit is contained in:
1 parent
84ce254bbe
commit
d475173477
2 files changed
+88
-2
No files matched your search
@@ -537,10 +537,55 @@ func isNewer(latest, current string) bool {
|
||||
if cPre == "" && lPre != "" {
|
||||
return false
|
||||
}
|
||||
// Both have pre-release: compare lexicographically (beta.2 > beta.1, rc.1 > beta.9)
|
||||
// Both have pre-release: split on "." and compare each segment
|
||||
// numerically where possible so beta.10 > beta.2.
|
||||
if lPre != "" && cPre != "" {
|
||||
return lPre > cPre
|
||||
return comparePreRelease(lPre, cPre) > 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// comparePreRelease compares two pre-release strings segment by segment.
|
||||
// Numeric segments are compared as integers; non-numeric segments are
|
||||
// compared lexicographically. Returns >0 if a is greater, <0 if b is
|
||||
// greater, 0 if equal.
|
||||
func comparePreRelease(a, b string) int {
|
||||
aParts := strings.Split(a, ".")
|
||||
bParts := strings.Split(b, ".")
|
||||
|
||||
max := len(aParts)
|
||||
if len(bParts) > max {
|
||||
max = len(bParts)
|
||||
}
|
||||
for i := 0; i < max; i++ {
|
||||
var ap, bp string
|
||||
if i < len(aParts) {
|
||||
ap = aParts[i]
|
||||
}
|
||||
if i < len(bParts) {
|
||||
bp = bParts[i]
|
||||
}
|
||||
|
||||
var an, bn int
|
||||
aN, _ := fmt.Sscanf(ap, "%d", &an)
|
||||
bN, _ := fmt.Sscanf(bp, "%d", &bn)
|
||||
aIsNum := aN == 1 && fmt.Sprintf("%d", an) == ap
|
||||
bIsNum := bN == 1 && fmt.Sprintf("%d", bn) == bp
|
||||
|
||||
if aIsNum && bIsNum {
|
||||
if an != bn {
|
||||
return an - bn
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Non-numeric: lexicographic
|
||||
if ap < bp {
|
||||
return -1
|
||||
}
|
||||
if ap > bp {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestIsNewer(t *testing.T) {
|
||||
tests := []struct {
|
||||
latest, current string
|
||||
want bool
|
||||
}{
|
||||
// Basic semver
|
||||
{"v1.2.3", "v1.2.2", true},
|
||||
{"v1.2.2", "v1.2.3", false},
|
||||
{"v1.2.3", "v1.2.3", false},
|
||||
{"v2.0.0", "v1.9.9", true},
|
||||
|
||||
// Pre-release vs stable
|
||||
{"v1.2.3", "v1.2.3-beta.1", true},
|
||||
{"v1.2.3-beta.1", "v1.2.3", false},
|
||||
|
||||
// Pre-release numeric ordering
|
||||
{"v1.2.3-beta.10", "v1.2.3-beta.2", true},
|
||||
{"v1.2.3-beta.2", "v1.2.3-beta.10", false},
|
||||
{"v1.2.3-beta.2", "v1.2.3-beta.2", false},
|
||||
|
||||
// rc > beta lexicographically
|
||||
{"v1.2.3-rc.1", "v1.2.3-beta.9", true},
|
||||
|
||||
// Dev builds always upgradeable
|
||||
{"v1.0.0", "dev", true},
|
||||
|
||||
// Empty
|
||||
{"", "v1.0.0", false},
|
||||
{"v1.0.0", "", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := isNewer(tt.latest, tt.current)
|
||||
if got != tt.want {
|
||||
t.Errorf("isNewer(%q, %q) = %v, want %v", tt.latest, tt.current, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user