-
Notifications
You must be signed in to change notification settings - Fork 5
/
version.go
324 lines (270 loc) · 6.57 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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
package pkgbuild
import (
"fmt"
"strconv"
"strings"
"unicode"
)
// Version string
type Version string
type CompleteVersion struct {
Version Version
Epoch uint8
Pkgrel Version
}
func (c *CompleteVersion) String() string {
str := ""
if c.Epoch > 0 {
str = fmt.Sprintf("%d:", c.Epoch)
}
str = fmt.Sprintf("%s%s", str, c.Version)
if c.Pkgrel != "" {
str = fmt.Sprintf("%s-%s", str, c.Pkgrel)
}
return str
}
// NewCompleteVersion creates a CompleteVersion including basic version, epoch
// and rel from string
func NewCompleteVersion(s string) (*CompleteVersion, error) {
var err error
epoch := 0
rel := Version("")
// handle possible epoch
versions := strings.Split(s, ":")
if len(versions) > 2 {
return nil, fmt.Errorf("invalid version format: %s", s)
}
if len(versions) > 1 {
epoch, err = strconv.Atoi(versions[0])
if err != nil {
return nil, err
}
}
// handle possible rel
versions = strings.Split(versions[len(versions)-1], "-")
if len(versions) > 2 {
return nil, fmt.Errorf("invalid version format: %s", s)
}
if len(versions) > 1 {
rel = Version(versions[1])
}
// finally check that the actual version is valid
if validPkgver(versions[0]) {
return &CompleteVersion{
Version: Version(versions[0]),
Epoch: uint8(epoch),
Pkgrel: rel,
}, nil
}
return nil, fmt.Errorf("invalid version format: %s", s)
}
// Older returns true if a is older than the argument version
func (a *CompleteVersion) Older(b *CompleteVersion) bool {
return a.cmp(b) == -1
}
// Newer returns true if a is newer than the argument version
func (a *CompleteVersion) Newer(b *CompleteVersion) bool {
return a.cmp(b) == 1
}
// Equal returns true if a is equal to the argument version
func (a *CompleteVersion) Equal(b *CompleteVersion) bool {
return a.cmp(b) == 0
}
// Satisfies tests whether or not version fits inside the bounds specified by
// dep
func (version *CompleteVersion) Satisfies(dep *Dependency) bool {
var cmpMax int8
var cmpMin int8
if dep.MaxVer != nil {
cmpMax = version.cmp(dep.MaxVer)
if cmpMax == 1 {
return false
}
if cmpMax == 0 && dep.slt {
return false
}
}
if dep.MinVer != nil {
if dep.MaxVer == dep.MinVer {
cmpMin = cmpMax
} else {
cmpMin = version.cmp(dep.MinVer)
}
if cmpMin == -1 {
return false
}
if cmpMin == 0 && dep.sgt {
return false
}
}
return true
}
// Compare a to b:
// return 1: a is newer than b
// 0: a and b are the same version
// -1: b is newer than a
func (a *CompleteVersion) cmp(b *CompleteVersion) int8 {
if a.Epoch > b.Epoch {
return 1
}
if a.Epoch < b.Epoch {
return -1
}
if a.Version.bigger(b.Version) {
return 1
}
if b.Version.bigger(a.Version) {
return -1
}
if a.Pkgrel == "" || b.Pkgrel == "" {
return 0
}
if a.Pkgrel.bigger(b.Pkgrel) {
return 1
}
if b.Pkgrel.bigger(a.Pkgrel) {
return -1
}
return 0
}
// Compare alpha and numeric segments of two versions.
// return 1: a is newer than b
// 0: a and b are the same version
// -1: b is newer than a
//
// This is based on the rpmvercmp function used in libalpm
// https://projects.archlinux.org/pacman.git/tree/lib/libalpm/version.c
func rpmvercmp(av, bv Version) int {
if av == bv {
return 0
}
a, b := []rune(string(av)), []rune(string(bv))
var one, two, ptr1, ptr2 int
var isNum bool
one, two, ptr1, ptr2 = 0, 0, 0, 0
// loop through each version segment of a and b and compare them
for len(a) > one && len(b) > two {
for len(a) > one && !isAlphaNumeric(a[one]) {
one++
}
for len(b) > two && !isAlphaNumeric(b[two]) {
two++
}
// if we ran to the end of either, we are finished with the loop
if !(len(a) > one && len(b) > two) {
break
}
// if the seperator lengths were different, we are also finished
if one-ptr1 != two-ptr2 {
if one-ptr1 < two-ptr2 {
return -1
}
return 1
}
ptr1 = one
ptr2 = two
// grab first completely alpha or completely numeric segment
// leave one and two pointing to the start of the alpha or numeric
// segment and walk ptr1 and ptr2 to end of segment
if isDigit(a[ptr1]) {
for len(a) > ptr1 && isDigit(a[ptr1]) {
ptr1++
}
for len(b) > ptr2 && isDigit(b[ptr2]) {
ptr2++
}
isNum = true
} else {
for len(a) > ptr1 && isAlpha(a[ptr1]) {
ptr1++
}
for len(b) > ptr2 && isAlpha(b[ptr2]) {
ptr2++
}
isNum = false
}
// take care of the case where the two version segments are
// different types: one numeric, the other alpha (i.e. empty)
// numeric segments are always newer than alpha segments
if two == ptr2 {
if isNum {
return 1
}
return -1
}
if isNum {
// we know this part of the strings only contains digits
// so we can ignore the error value since it should
// always be nil
as, _ := strconv.ParseInt(string(a[one:ptr1]), 10, 0)
bs, _ := strconv.ParseInt(string(b[two:ptr2]), 10, 0)
// whichever number has more digits wins
if as > bs {
return 1
}
if as < bs {
return -1
}
} else {
cmp := alphaCompare(a[one:ptr1], b[two:ptr2])
if cmp < 0 {
return -1
}
if cmp > 0 {
return 1
}
}
// advance one and two to next segment
one = ptr1
two = ptr2
}
// this catches the case where all numeric and alpha segments have
// compared identically but the segment separating characters were
// different
if len(a) <= one && len(b) <= two {
return 0
}
// the final showdown. we never want a remaining alpha string to
// beat an empty string. the logic is a bit weird, but:
// - if one is empty and two is not an alpha, two is newer.
// - if one is an alpha, two is newer.
// - otherwise one is newer.
if (len(a) <= one && !isAlpha(b[two])) || len(a) > one && isAlpha(a[one]) {
return -1
}
return 1
}
// alphaCompare compares two alpha version segments and will return a positive
// value if a is bigger than b and a negative if b is bigger than a else 0
func alphaCompare(a, b []rune) int8 {
if string(a) == string(b) {
return 0
}
i := 0
for len(a) > i && len(b) > i && a[i] == b[i] {
i++
}
if len(a) == i && len(b) > i {
return -1
}
if len(b) == i {
return 1
}
return int8(a[i]) - int8(b[i])
}
// check if version number v is bigger than v2
func (v Version) bigger(v2 Version) bool {
return rpmvercmp(v, v2) == 1
}
// isAlphaNumeric reports whether c is an alpha character or digit
func isAlphaNumeric(c rune) bool {
return isDigit(c) || isAlpha(c)
}
// isAlpha reports whether c is an alpha character
func isAlpha(c rune) bool {
return unicode.IsLetter(c)
}
// isDigit reports whether d is an ASCII digit
func isDigit(d rune) bool {
return unicode.IsDigit(d)
}