2017-07-14 19:03:54 +02:00
|
|
|
package pkgbuild
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Version string
|
|
|
|
type Version string
|
|
|
|
|
|
|
|
type CompleteVersion struct {
|
|
|
|
Version Version
|
|
|
|
Epoch uint8
|
2017-10-30 13:19:05 +01:00
|
|
|
Pkgrel Version
|
2017-07-14 19:03:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *CompleteVersion) String() string {
|
2018-01-19 15:32:07 +01:00
|
|
|
return fmt.Sprintf("%d:%s-%s", c.Epoch, c.Version, c.Pkgrel)
|
2017-07-14 19:03:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewCompleteVersion creates a CompleteVersion including basic version, epoch
|
|
|
|
// and rel from string
|
|
|
|
func NewCompleteVersion(s string) (*CompleteVersion, error) {
|
|
|
|
var err error
|
|
|
|
epoch := 0
|
2017-10-30 13:19:05 +01:00
|
|
|
rel := Version("")
|
2017-07-14 19:03:54 +02:00
|
|
|
|
|
|
|
// 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 {
|
2017-10-30 13:19:05 +01:00
|
|
|
rel = Version(versions[1])
|
2017-07-14 19:03:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// finally check that the actual version is valid
|
|
|
|
if validPkgver(versions[0]) {
|
|
|
|
return &CompleteVersion{
|
|
|
|
Version: Version(versions[0]),
|
|
|
|
Epoch: uint8(epoch),
|
2017-10-30 13:19:05 +01:00
|
|
|
Pkgrel: rel,
|
2017-07-14 19:03:54 +02:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("invalid version format: %s", s)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Older returns true if a is older than the argument version string
|
|
|
|
func (a *CompleteVersion) Older(v string) bool {
|
|
|
|
b, err := NewCompleteVersion(v)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return a.cmp(b) == -1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Newer returns true if a is newer than the argument version string
|
|
|
|
func (a *CompleteVersion) Newer(v string) bool {
|
|
|
|
b, err := NewCompleteVersion(v)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return a.cmp(b) == 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Equal returns true if a is equal to the argument version string
|
|
|
|
func (a *CompleteVersion) Equal(v string) bool {
|
|
|
|
b, err := NewCompleteVersion(v)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return a.cmp(b) == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2017-10-30 13:19:05 +01:00
|
|
|
if a.Pkgrel.bigger(b.Pkgrel) {
|
2017-07-14 19:03:54 +02:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2017-10-30 13:19:05 +01:00
|
|
|
if b.Pkgrel.bigger(a.Pkgrel) {
|
2017-07-14 19:03:54 +02:00
|
|
|
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(a, b Version) int {
|
|
|
|
if a == b {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
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 Version) int8 {
|
|
|
|
if a == 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 uint8) bool {
|
|
|
|
return isDigit(c) || isAlpha(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
// isAlpha reports whether c is an alpha character
|
|
|
|
func isAlpha(c uint8) bool {
|
|
|
|
return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'
|
|
|
|
}
|
|
|
|
|
|
|
|
// isDigit reports whether d is an ASCII digit
|
|
|
|
func isDigit(d uint8) bool {
|
|
|
|
return '0' <= d && d <= '9'
|
|
|
|
}
|