-
Notifications
You must be signed in to change notification settings - Fork 44
/
version.go
140 lines (121 loc) · 3.13 KB
/
version.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package rpm
import (
"math"
"regexp"
"strings"
"unicode"
)
// alphanumPattern is a regular expression to match all sequences of numeric
// characters or alphanumeric characters.
var alphanumPattern = regexp.MustCompile("([a-zA-Z]+)|([0-9]+)|(~)")
// Version is an interface which holds version information for a package in EVR
// form.
type Version interface {
Epoch() int
Version() string
Release() string
}
// Compare compares the version details of two packages. Versions are
// compared by Epoch, Version and Release (EVR) in descending order of
// precedence.
//
// If a is more recent than b, 1 is returned. If a is less recent than b, -1 is
// returned. If a and b are equal, 0 is returned.
//
// This function does not consider if the two packages have the same name or if
// either package has been made obsolete by the other.
func Compare(a, b Version) int {
// compare nils
if a == nil && b == nil {
return 0
} else if a == nil {
return -1
} else if b == nil {
return 1
}
// compare epoch
ae := a.Epoch()
be := b.Epoch()
if ae != be {
if ae > be {
return 1
}
return -1
}
// compare version
if rc := CompareVersions(a.Version(), b.Version()); rc != 0 {
return rc
}
// compare release
return CompareVersions(a.Release(), b.Release())
}
// CompareVersion compares version strings. It does not consider package epochs
// or release numbers like Compare.
//
// If a is more recent than b, 1 is returned. If a is less recent than b, -1 is
// returned. If a and b are equal, 0 is returned.
func CompareVersions(a, b string) int {
// For the original C implementation, see:
// https://github.com/rpm-software-management/rpm/blob/master/lib/rpmvercmp.c#L16
if a == b {
return 0
}
// get alpha/numeric segements
segsa := alphanumPattern.FindAllString(a, -1)
segsb := alphanumPattern.FindAllString(b, -1)
segs := int(math.Min(float64(len(segsa)), float64(len(segsb))))
// compare each segment
for i := 0; i < segs; i++ {
a := segsa[i]
b := segsb[i]
// compare tildes
if []rune(a)[0] == '~' || []rune(b)[0] == '~' {
if []rune(a)[0] != '~' {
return 1
}
if []rune(b)[0] != '~' {
return -1
}
}
if unicode.IsNumber([]rune(a)[0]) {
// numbers are always greater than alphas
if !unicode.IsNumber([]rune(b)[0]) {
// a is numeric, b is alpha
return 1
}
// trim leading zeros
a = strings.TrimLeft(a, "0")
b = strings.TrimLeft(b, "0")
// longest string wins without further comparison
if len(a) > len(b) {
return 1
} else if len(b) > len(a) {
return -1
}
} else if unicode.IsNumber([]rune(b)[0]) {
// a is alpha, b is numeric
return -1
}
// string compare
if a < b {
return -1
} else if a > b {
return 1
}
}
// segments were all the same but separators must have been different
if len(segsa) == len(segsb) {
return 0
}
// If there is a tilde in a segment past the min number of segments, find it.
if len(segsa) > segs && []rune(segsa[segs])[0] == '~' {
return -1
} else if len(segsb) > segs && []rune(segsb[segs])[0] == '~' {
return 1
}
// whoever has the most segments wins
if len(segsa) > len(segsb) {
return 1
}
return -1
}