mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-05-05 22:08:27 +00:00
When performing an *aptly snapshot pull*, users might list dependency versions that can potentially match multiple packages in the source snapshot. However, the current implementation of the 'snapshot pull' command only allows one package to be pulled from a snapshot at a time for a given dependency. The newly implemented all-matches flag allows users to pull all the matching packages from a source snapshot, provided that they satisfy the version requirements indicated by the dependencies. The all-matches flag defaults to false and only produces the described behaviour when it is explicitly set to true.
311 lines
6.6 KiB
Go
311 lines
6.6 KiB
Go
package deb
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
// Using documentation from: http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
|
|
|
|
// CompareVersions compares two package versions
|
|
func CompareVersions(ver1, ver2 string) int {
|
|
e1, u1, d1 := parseVersion(ver1)
|
|
e2, u2, d2 := parseVersion(ver2)
|
|
|
|
r := compareVersionPart(e1, e2)
|
|
if r != 0 {
|
|
return r
|
|
}
|
|
|
|
r = compareVersionPart(u1, u2)
|
|
if r != 0 {
|
|
return r
|
|
}
|
|
|
|
return compareVersionPart(d1, d2)
|
|
}
|
|
|
|
// parseVersions breaks down full version to components (possibly empty)
|
|
func parseVersion(ver string) (epoch, upstream, debian string) {
|
|
i := strings.LastIndex(ver, "-")
|
|
if i != -1 {
|
|
debian, ver = ver[i+1:], ver[:i]
|
|
}
|
|
|
|
i = strings.Index(ver, ":")
|
|
if i != -1 {
|
|
epoch, ver = ver[:i], ver[i+1:]
|
|
}
|
|
|
|
upstream = ver
|
|
|
|
return
|
|
}
|
|
|
|
// compareLexicographic compares in "Debian lexicographic" way, see below compareVersionPart for details
|
|
func compareLexicographic(s1, s2 string) int {
|
|
i := 0
|
|
l1, l2 := len(s1), len(s2)
|
|
|
|
for {
|
|
if i == l1 && i == l2 {
|
|
// s1 equal to s2
|
|
break
|
|
}
|
|
|
|
if i == l2 {
|
|
// s1 is longer than s2
|
|
if s1[i] == '~' {
|
|
return -1 // s1 < s2
|
|
}
|
|
return 1 // s1 > s2
|
|
}
|
|
|
|
if i == l1 {
|
|
// s2 is longer than s1
|
|
if s2[i] == '~' {
|
|
return 1 // s1 > s2
|
|
}
|
|
return -1 // s1 < s2
|
|
}
|
|
|
|
if s1[i] == s2[i] {
|
|
i++
|
|
continue
|
|
}
|
|
|
|
if s1[i] == '~' {
|
|
return -1
|
|
}
|
|
|
|
if s2[i] == '~' {
|
|
return 1
|
|
}
|
|
|
|
c1, c2 := unicode.IsLetter(rune(s1[i])), unicode.IsLetter(rune(s2[i]))
|
|
if c1 && !c2 {
|
|
return -1
|
|
}
|
|
if !c1 && c2 {
|
|
return 1
|
|
}
|
|
|
|
if s1[i] < s2[i] {
|
|
return -1
|
|
}
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// compareVersionPart compares parts of full version
|
|
//
|
|
// From Debian Policy Manual:
|
|
//
|
|
// "The strings are compared from left to right.
|
|
//
|
|
// First the initial part of each string consisting entirely of non-digit characters is
|
|
// determined. These two parts (one of which may be empty) are compared lexically. If a
|
|
// difference is found it is returned. The lexical comparison is a comparison of ASCII values
|
|
// modified so that all the letters sort earlier than all the non-letters and so that a tilde
|
|
// sorts before anything, even the end of a part. For example, the following parts are in sorted
|
|
// order from earliest to latest: ~~, ~~a, ~, the empty part.
|
|
//
|
|
// Then the initial part of the remainder of each string which consists entirely of digit
|
|
// characters is determined. The numerical values of these two parts are compared, and any difference
|
|
// found is returned as the result of the comparison. For these purposes an empty string (which can only occur at
|
|
// the end of one or both version strings being compared) counts as zero.
|
|
|
|
// These two steps (comparing and removing initial non-digit strings and initial digit strings) are
|
|
// repeated until a difference is found or both strings are exhausted."
|
|
func compareVersionPart(part1, part2 string) int {
|
|
i1, i2 := 0, 0
|
|
l1, l2 := len(part1), len(part2)
|
|
|
|
for {
|
|
j1, j2 := i1, i2
|
|
for j1 < l1 && !unicode.IsDigit(rune(part1[j1])) {
|
|
j1++
|
|
}
|
|
|
|
for j2 < l2 && !unicode.IsDigit(rune(part2[j2])) {
|
|
j2++
|
|
}
|
|
|
|
s1, s2 := part1[i1:j1], part2[i2:j2]
|
|
r := compareLexicographic(s1, s2)
|
|
if r != 0 {
|
|
return r
|
|
}
|
|
|
|
i1, i2 = j1, j2
|
|
|
|
for j1 < l1 && unicode.IsDigit(rune(part1[j1])) {
|
|
j1++
|
|
}
|
|
|
|
for j2 < l2 && unicode.IsDigit(rune(part2[j2])) {
|
|
j2++
|
|
}
|
|
|
|
s1, s2 = part1[i1:j1], part2[i2:j2]
|
|
n1, _ := strconv.Atoi(s1)
|
|
n2, _ := strconv.Atoi(s2)
|
|
|
|
if n1 < n2 {
|
|
return -1
|
|
}
|
|
if n1 > n2 {
|
|
return 1
|
|
}
|
|
|
|
i1, i2 = j1, j2
|
|
|
|
if i1 == l1 && i2 == l2 {
|
|
break
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Version relations
|
|
const (
|
|
VersionDontCare = iota
|
|
VersionLess
|
|
VersionLessOrEqual
|
|
VersionEqual
|
|
VersionGreaterOrEqual
|
|
VersionGreater
|
|
)
|
|
|
|
// Dependency is a parsed version of Debian dependency to package
|
|
type Dependency struct {
|
|
Pkg string
|
|
Relation int
|
|
Version string
|
|
Architecture string
|
|
}
|
|
|
|
// NextVersion returns the next version of a dependency (eg. if d.Version = 1.9, it returns 1.10)
|
|
func (d *Dependency) NextVersion() string {
|
|
l := len(d.Version)
|
|
|
|
if l == 0 {
|
|
return ""
|
|
}
|
|
|
|
i := l
|
|
for i > 0 {
|
|
_, err := strconv.ParseUint(d.Version[i-1:l],10,0)
|
|
if err != nil { break }
|
|
i--
|
|
}
|
|
|
|
v, err := strconv.ParseUint(d.Version[i:l],10,0)
|
|
if err != nil {
|
|
return d.Version
|
|
}
|
|
|
|
return d.Version[0:i] + strconv.Itoa(int(v)+1)
|
|
}
|
|
|
|
// Hash calculates some predefined unique ID of Dependency
|
|
func (d *Dependency) Hash() string {
|
|
return fmt.Sprintf("%s:%s:%d:%s", d.Architecture, d.Pkg, d.Relation, d.Version)
|
|
}
|
|
|
|
// String produces human-readable representation
|
|
func (d *Dependency) String() string {
|
|
var rel string
|
|
switch d.Relation {
|
|
case VersionEqual:
|
|
rel = "="
|
|
case VersionGreater:
|
|
rel = ">>"
|
|
case VersionLess:
|
|
rel = "<<"
|
|
case VersionGreaterOrEqual:
|
|
rel = ">="
|
|
case VersionLessOrEqual:
|
|
rel = "<="
|
|
case VersionDontCare:
|
|
return fmt.Sprintf("%s [%s]", d.Pkg, d.Architecture)
|
|
}
|
|
return fmt.Sprintf("%s (%s %s) [%s]", d.Pkg, rel, d.Version, d.Architecture)
|
|
}
|
|
|
|
// ParseDependencyVariants parses dependencies in format "pkg (>= 1.35) | other-package"
|
|
func ParseDependencyVariants(variants string) (l []Dependency, err error) {
|
|
parts := strings.Split(variants, "|")
|
|
l = make([]Dependency, len(parts))
|
|
|
|
for i, part := range parts {
|
|
l[i], err = ParseDependency(strings.TrimSpace(part))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// ParseDependency parses dependency in format "pkg (>= 1.35) [arch]" into parts
|
|
func ParseDependency(dep string) (d Dependency, err error) {
|
|
if strings.HasSuffix(dep, "}") {
|
|
i := strings.LastIndex(dep, "{")
|
|
if i == -1 {
|
|
err = fmt.Errorf("unable to parse dependency: %s", dep)
|
|
return
|
|
}
|
|
d.Architecture = dep[i+1 : len(dep)-1]
|
|
|
|
dep = strings.TrimSpace(dep[:i])
|
|
}
|
|
|
|
if !strings.HasSuffix(dep, ")") {
|
|
d.Pkg = strings.TrimSpace(dep)
|
|
d.Relation = VersionDontCare
|
|
return
|
|
}
|
|
|
|
i := strings.Index(dep, "(")
|
|
if i == -1 {
|
|
err = fmt.Errorf("unable to parse dependency: %s", dep)
|
|
return
|
|
}
|
|
|
|
d.Pkg = strings.TrimSpace(dep[0:i])
|
|
|
|
rel := ""
|
|
if dep[i+1] == '>' || dep[i+1] == '<' || dep[i+1] == '=' {
|
|
rel += dep[i+1 : i+2]
|
|
if dep[i+2] == '>' || dep[i+2] == '<' || dep[i+2] == '=' {
|
|
rel += dep[i+2 : i+3]
|
|
d.Version = strings.TrimSpace(dep[i+3 : len(dep)-1])
|
|
} else {
|
|
d.Version = strings.TrimSpace(dep[i+2 : len(dep)-1])
|
|
}
|
|
} else {
|
|
d.Version = strings.TrimSpace(dep[i+1 : len(dep)-1])
|
|
}
|
|
|
|
switch rel {
|
|
case "<", "<=":
|
|
d.Relation = VersionLessOrEqual
|
|
case ">", ">=":
|
|
d.Relation = VersionGreaterOrEqual
|
|
case "<<":
|
|
d.Relation = VersionLess
|
|
case ">>":
|
|
d.Relation = VersionGreater
|
|
case "", "=":
|
|
d.Relation = VersionEqual
|
|
default:
|
|
err = fmt.Errorf("relation unknown %#v in dependency %s", rel, dep)
|
|
}
|
|
|
|
return
|
|
}
|