Skip to content
Open
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
83 changes: 71 additions & 12 deletions pkg/apk/apk/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import (
// 2. allows pulling in dependencies for the tagged package from the tagged repository (though it prefers to use untagged repositories to satisfy dependencies if possible)

var (
versionRegex = regexp.MustCompile(`^([0-9]+)((\.[0-9]+)*)([a-z]?)((_alpha|_beta|_pre|_rc)([0-9]*))?((_cvs|_svn|_git|_hg|_p)([0-9]*))?((-r)([0-9]+))?$`)
versionRegex = regexp.MustCompile(`^([0-9]+)((\.[0-9]+)*)([a-z]?)((_alpha|_beta|_pre|_rc)([0-9]*))?((_cvs|_svn|_git|_hg|_p)([0-9]*))?$`)
packageNameRegex = regexp.MustCompile(`^([^@=><~]+)(([=><~]+)([^@]+))?(@([a-zA-Z0-9]+))?$`)
)

Expand Down Expand Up @@ -75,17 +75,20 @@ type Version struct {
postSuffix packageVersionPostModifier
postSuffixNumber int
revision int
revisions []int
}

// ParseVersion parses a version string into a Version struct.
func ParseVersion(version string) (Version, error) {
parts := versionRegex.FindAllStringSubmatch(version, -1)
baseVersion, revisions := splitRevisionSuffixes(version)

parts := versionRegex.FindAllStringSubmatch(baseVersion, -1)
if len(parts) == 0 {
return Version{}, fmt.Errorf("invalid version %s, could not parse", version)
}
actuals := parts[0]
numbers := make([]int, 0, 10)
if len(actuals) != 14 {
if len(actuals) != 11 {
return Version{}, fmt.Errorf("invalid version %s, could not find enough components", version)
}

Expand Down Expand Up @@ -165,13 +168,14 @@ func ParseVersion(version string) (Version, error) {
}

var revision int
if actuals[13] != "" {
num, err := strconv.Atoi(actuals[13])
if err != nil {
return Version{}, fmt.Errorf("invalid version %s, revision %s is not number: %w", version, actuals[13], err)
}
revision = num
if len(revisions) != 0 {
revision = revisions[len(revisions)-1]
}
storedRevisions := revisions
if len(storedRevisions) <= 1 {
storedRevisions = nil
}

return Version{
numbers: numbers,
letter: letter,
Expand All @@ -180,9 +184,47 @@ func ParseVersion(version string) (Version, error) {
postSuffix: postSuffix,
postSuffixNumber: postSuffixNumber,
revision: revision,
revisions: storedRevisions,
}, nil
}

func splitRevisionSuffixes(version string) (string, []int) {
baseVersion := version
revisions := []int{}

for {
idx := strings.LastIndex(baseVersion, "-r")
if idx == -1 {
break
}

rawRevision := baseVersion[idx+len("-r"):]
revision, err := strconv.Atoi(rawRevision)
if err != nil {
break
}

revisions = append(revisions, revision)
baseVersion = baseVersion[:idx]
}

for i, j := 0, len(revisions)-1; i < j; i, j = i+1, j-1 {
revisions[i], revisions[j] = revisions[j], revisions[i]
}

return baseVersion, revisions
}

func revisionNumbers(v Version) []int {
if len(v.revisions) != 0 {
return v.revisions
}
if v.revision != 0 {
return []int{v.revision}
}
return nil
}

const (
greater = 1
equal = 0
Expand Down Expand Up @@ -259,10 +301,20 @@ func CompareVersions(actual, required Version) int {
}
// same post-suffix numbers
// compare revisions
if actual.revision > required.revision {
actualRevisions := revisionNumbers(actual)
requiredRevisions := revisionNumbers(required)
for i := 0; i < len(actualRevisions) && i < len(requiredRevisions); i++ {
if actualRevisions[i] > requiredRevisions[i] {
return greater
}
if actualRevisions[i] < requiredRevisions[i] {
return less
}
}
if len(actualRevisions) > len(requiredRevisions) {
return greater
}
if actual.revision < required.revision {
if len(actualRevisions) < len(requiredRevisions) {
return less
}
return equal
Expand Down Expand Up @@ -308,9 +360,16 @@ func includesVersion(actual, required Version) bool {
}

// compare revisions
if required.revision != 0 && actual.revision != required.revision {
actualRevisions := revisionNumbers(actual)
requiredRevisions := revisionNumbers(required)
if len(actualRevisions) < len(requiredRevisions) {
return false
}
for i, requiredRevision := range requiredRevisions {
if actualRevisions[i] != requiredRevision {
return false
}
}
return true
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/apk/apk/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func TestParseVersion(t *testing.T) {
{"1.1.1s_alpha2-r2", Version{numbers: []int{1, 1, 1}, preSuffix: packageVersionPreModifierAlpha, preSuffixNumber: 2, letter: 's', postSuffix: packageVersionPostModifierNone, revision: 2}},
{"1.1.1-r2", Version{numbers: []int{1, 1, 1}, preSuffix: packageVersionPreModifierNone, postSuffix: packageVersionPostModifierNone, revision: 2}},
{"1.1.1-r29", Version{numbers: []int{1, 1, 1}, preSuffix: packageVersionPreModifierNone, postSuffix: packageVersionPostModifierNone, revision: 29}},
{"1.1.0-r1-r0", Version{numbers: []int{1, 1, 0}, preSuffix: packageVersionPreModifierNone, postSuffix: packageVersionPostModifierNone, revision: 0, revisions: []int{1, 0}}},
}
for _, tt := range tests {
actual, err := ParseVersion(tt.version)
Expand All @@ -85,6 +86,7 @@ func TestParseVersion(t *testing.T) {
"1_illegal",
"1_illegal",
"1.1.1-rQ",
"1.1.0-r1-rbad",
}
for _, version := range tests {
_, err := ParseVersion(version)
Expand Down Expand Up @@ -315,6 +317,9 @@ func TestCompareVersion(t *testing.T) {
{"4.1.4", greater, "1.2.10-r5"},
{"1.2.10-r5", less, "4.1.4-r3"},
{"4.1.4-r3", equal, "4.1.4-r3"},
{"1.1.0-r1-r0", greater, "1.1.0-r1"},
{"1.1.0-r1-r0", less, "1.1.0-r1-r1"},
{"1.1.0-r1-r0", greater, "1.1.0-r0"},
{"4.1.4-r3", less, "4.2.1"},
{"4.2.1", greater, "4.1.0"},
{"4.1.0", less, "8.11"},
Expand Down