mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-01-12 03:21:33 +00:00
The previous reflist logic would early-exit the loop body if one of the lists was empty, but that skips the compacting logic entirely. Instead of doing the early-exit, we can leave a list's ref as nil when the list end is reached and then flip the comparison result, which will essentially treat it as being greater than all others. This should preserve the general behavior without omitting the compaction. Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
394 lines
9.9 KiB
Go
394 lines
9.9 KiB
Go
package deb
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"sort"
|
|
|
|
"github.com/AlekSi/pointer"
|
|
"github.com/ugorji/go/codec"
|
|
)
|
|
|
|
// PackageRefList is a list of keys of packages, this is basis for snapshot
|
|
// and similar stuff
|
|
//
|
|
// Refs are sorted in lexicographical order
|
|
type PackageRefList struct {
|
|
// List of package keys
|
|
Refs [][]byte
|
|
}
|
|
|
|
// Verify interface
|
|
var (
|
|
_ sort.Interface = &PackageRefList{}
|
|
)
|
|
|
|
// NewPackageRefList creates empty PackageRefList
|
|
func NewPackageRefList() *PackageRefList {
|
|
return &PackageRefList{}
|
|
}
|
|
|
|
// NewPackageRefListFromPackageList creates PackageRefList from PackageList
|
|
func NewPackageRefListFromPackageList(list *PackageList) *PackageRefList {
|
|
reflist := &PackageRefList{}
|
|
reflist.Refs = make([][]byte, list.Len())
|
|
|
|
i := 0
|
|
for _, p := range list.packages {
|
|
reflist.Refs[i] = p.Key("")
|
|
i++
|
|
}
|
|
|
|
sort.Sort(reflist)
|
|
|
|
return reflist
|
|
}
|
|
|
|
// Len returns number of refs
|
|
func (l *PackageRefList) Len() int {
|
|
return len(l.Refs)
|
|
}
|
|
|
|
// Swap swaps two refs
|
|
func (l *PackageRefList) Swap(i, j int) {
|
|
l.Refs[i], l.Refs[j] = l.Refs[j], l.Refs[i]
|
|
}
|
|
|
|
// Compare compares two refs in lexographical order
|
|
func (l *PackageRefList) Less(i, j int) bool {
|
|
return bytes.Compare(l.Refs[i], l.Refs[j]) < 0
|
|
}
|
|
|
|
// Encode does msgpack encoding of PackageRefList
|
|
func (l *PackageRefList) Encode() []byte {
|
|
var buf bytes.Buffer
|
|
|
|
encoder := codec.NewEncoder(&buf, &codec.MsgpackHandle{})
|
|
encoder.Encode(l)
|
|
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// Decode decodes msgpack representation into PackageRefLit
|
|
func (l *PackageRefList) Decode(input []byte) error {
|
|
handle := &codec.MsgpackHandle{}
|
|
handle.ZeroCopy = true
|
|
decoder := codec.NewDecoderBytes(input, handle)
|
|
return decoder.Decode(l)
|
|
}
|
|
|
|
// ForEach calls handler for each package ref in list
|
|
func (l *PackageRefList) ForEach(handler func([]byte) error) error {
|
|
var err error
|
|
for _, p := range l.Refs {
|
|
err = handler(p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Has checks whether package is part of reflist
|
|
func (l *PackageRefList) Has(p *Package) bool {
|
|
key := p.Key("")
|
|
|
|
i := sort.Search(len(l.Refs), func(j int) bool { return bytes.Compare(l.Refs[j], key) >= 0 })
|
|
return i < len(l.Refs) && bytes.Equal(l.Refs[i], key)
|
|
}
|
|
|
|
// Strings builds list of strings with package keys
|
|
func (l *PackageRefList) Strings() []string {
|
|
if l == nil {
|
|
return []string{}
|
|
}
|
|
|
|
result := make([]string, l.Len())
|
|
|
|
for i := 0; i < l.Len(); i++ {
|
|
result[i] = string(l.Refs[i])
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Subtract returns all packages in l that are not in r
|
|
func (l *PackageRefList) Subtract(r *PackageRefList) *PackageRefList {
|
|
result := &PackageRefList{Refs: make([][]byte, 0, 128)}
|
|
|
|
// pointer to left and right reflists
|
|
il, ir := 0, 0
|
|
// length of reflists
|
|
ll, lr := l.Len(), r.Len()
|
|
|
|
for il < ll || ir < lr {
|
|
if il == ll {
|
|
// left list exhausted, we got the result
|
|
break
|
|
}
|
|
if ir == lr {
|
|
// right list exhausted, append what is left to result
|
|
result.Refs = append(result.Refs, l.Refs[il:]...)
|
|
break
|
|
}
|
|
|
|
rel := bytes.Compare(l.Refs[il], r.Refs[ir])
|
|
if rel == 0 {
|
|
// r contains entry from l, so we skip it
|
|
il++
|
|
ir++
|
|
} else if rel < 0 {
|
|
// item il is not in r, append
|
|
result.Refs = append(result.Refs, l.Refs[il])
|
|
il++
|
|
} else {
|
|
// skip over to next item in r
|
|
ir++
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// PackageDiff is a difference between two packages in a list.
|
|
//
|
|
// If left & right are present, difference is in package version
|
|
// If left is nil, package is present only in right
|
|
// If right is nil, package is present only in left
|
|
type PackageDiff struct {
|
|
Left, Right *Package
|
|
}
|
|
|
|
// Check interface
|
|
var (
|
|
_ json.Marshaler = PackageDiff{}
|
|
)
|
|
|
|
// MarshalJSON implements json.Marshaler interface
|
|
func (d PackageDiff) MarshalJSON() ([]byte, error) {
|
|
serialized := struct {
|
|
Left, Right *string
|
|
}{}
|
|
|
|
if d.Left != nil {
|
|
serialized.Left = pointer.ToString(string(d.Left.Key("")))
|
|
}
|
|
if d.Right != nil {
|
|
serialized.Right = pointer.ToString(string(d.Right.Key("")))
|
|
}
|
|
|
|
return json.Marshal(serialized)
|
|
}
|
|
|
|
// PackageDiffs is a list of PackageDiff records
|
|
type PackageDiffs []PackageDiff
|
|
|
|
// Diff calculates difference between two reflists
|
|
func (l *PackageRefList) Diff(r *PackageRefList, packageCollection *PackageCollection) (result PackageDiffs, err error) {
|
|
result = make(PackageDiffs, 0, 128)
|
|
|
|
// pointer to left and right reflists
|
|
il, ir := 0, 0
|
|
// length of reflists
|
|
ll, lr := l.Len(), r.Len()
|
|
// cached loaded packages on the left & right
|
|
pl, pr := (*Package)(nil), (*Package)(nil)
|
|
|
|
// until we reached end of both lists
|
|
for il < ll || ir < lr {
|
|
var rl, rr []byte
|
|
if il < ll {
|
|
rl = l.Refs[il]
|
|
}
|
|
if ir < lr {
|
|
rr = r.Refs[ir]
|
|
}
|
|
|
|
// compare refs
|
|
rel := bytes.Compare(rl, rr)
|
|
// an unset ref is less than all others, but since it represents the end
|
|
// of a reflist, it should be *greater*, so flip the comparison result
|
|
if rl == nil || rr == nil {
|
|
rel = -rel
|
|
}
|
|
|
|
if rel == 0 {
|
|
// refs are identical, so are packages, advance pointer
|
|
il++
|
|
ir++
|
|
pl, pr = nil, nil
|
|
} else {
|
|
// load pl & pr if they haven't been loaded before
|
|
if pl == nil && rl != nil {
|
|
pl, err = packageCollection.ByKey(rl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if pr == nil && rr != nil {
|
|
pr, err = packageCollection.ByKey(rr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// otherwise pl or pr is missing on one of the sides
|
|
if rel < 0 {
|
|
// compaction: +(,A) -(B,) --> !(A,B)
|
|
if len(result) > 0 && result[len(result)-1].Left == nil && result[len(result)-1].Right.Name == pl.Name &&
|
|
result[len(result)-1].Right.Architecture == pl.Architecture {
|
|
result[len(result)-1] = PackageDiff{Left: pl, Right: result[len(result)-1].Right}
|
|
} else {
|
|
result = append(result, PackageDiff{Left: pl, Right: nil})
|
|
}
|
|
il++
|
|
pl = nil
|
|
} else {
|
|
// compaction: -(A,) +(,B) --> !(A,B)
|
|
if len(result) > 0 && result[len(result)-1].Right == nil && result[len(result)-1].Left.Name == pr.Name &&
|
|
result[len(result)-1].Left.Architecture == pr.Architecture {
|
|
result[len(result)-1] = PackageDiff{Left: result[len(result)-1].Left, Right: pr}
|
|
} else {
|
|
result = append(result, PackageDiff{Left: nil, Right: pr})
|
|
}
|
|
ir++
|
|
pr = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Merge merges reflist r into current reflist. If overrideMatching, merge
|
|
// replaces matching packages (by architecture/name) with reference from r.
|
|
// If ignoreConflicting is set, all packages are preserved, otherwise conflicting
|
|
// packages are overwritten with packages from "right" snapshot.
|
|
func (l *PackageRefList) Merge(r *PackageRefList, overrideMatching, ignoreConflicting bool) (result *PackageRefList) {
|
|
var overriddenArch, overridenName []byte
|
|
|
|
// pointer to left and right reflists
|
|
il, ir := 0, 0
|
|
// length of reflists
|
|
ll, lr := l.Len(), r.Len()
|
|
|
|
result = &PackageRefList{}
|
|
result.Refs = make([][]byte, 0, ll+lr)
|
|
|
|
// until we reached end of both lists
|
|
for il < ll || ir < lr {
|
|
// if we've exhausted left list, pull the rest from the right
|
|
if il == ll {
|
|
result.Refs = append(result.Refs, r.Refs[ir:]...)
|
|
break
|
|
}
|
|
// if we've exhausted right list, pull the rest from the left
|
|
if ir == lr {
|
|
result.Refs = append(result.Refs, l.Refs[il:]...)
|
|
break
|
|
}
|
|
|
|
// refs on both sides are present, load them
|
|
rl, rr := l.Refs[il], r.Refs[ir]
|
|
// compare refs
|
|
rel := bytes.Compare(rl, rr)
|
|
|
|
if rel == 0 {
|
|
// refs are identical, so are packages, advance pointer
|
|
result.Refs = append(result.Refs, l.Refs[il])
|
|
il++
|
|
ir++
|
|
overridenName = nil
|
|
overriddenArch = nil
|
|
} else {
|
|
if !ignoreConflicting || overrideMatching {
|
|
partsL := bytes.Split(rl, []byte(" "))
|
|
archL, nameL, versionL := partsL[0][1:], partsL[1], partsL[2]
|
|
|
|
partsR := bytes.Split(rr, []byte(" "))
|
|
archR, nameR, versionR := partsR[0][1:], partsR[1], partsR[2]
|
|
|
|
if !ignoreConflicting && bytes.Equal(archL, archR) &&
|
|
bytes.Equal(nameL, nameR) && bytes.Equal(versionL, versionR) {
|
|
// conflicting duplicates with same arch, name, version, but different file hash
|
|
result.Refs = append(result.Refs, r.Refs[ir])
|
|
il++
|
|
ir++
|
|
overridenName = nil
|
|
overriddenArch = nil
|
|
continue
|
|
}
|
|
|
|
if overrideMatching {
|
|
if bytes.Equal(archL, overriddenArch) && bytes.Equal(nameL, overridenName) {
|
|
// this package has already been overridden on the right
|
|
il++
|
|
continue
|
|
}
|
|
|
|
if bytes.Equal(archL, archR) && bytes.Equal(nameL, nameR) {
|
|
// override with package from the right
|
|
result.Refs = append(result.Refs, r.Refs[ir])
|
|
il++
|
|
ir++
|
|
overriddenArch = archL
|
|
overridenName = nameL
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// otherwise append smallest of two
|
|
if rel < 0 {
|
|
result.Refs = append(result.Refs, l.Refs[il])
|
|
il++
|
|
} else {
|
|
result.Refs = append(result.Refs, r.Refs[ir])
|
|
ir++
|
|
overridenName = nil
|
|
overriddenArch = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// FilterLatestRefs takes in a reflist with potentially multiples of the same
|
|
// packages and reduces it to only the latest of each package. The operations
|
|
// are done in-place. This implements a "latest wins" approach which can be used
|
|
// while merging two or more snapshots together.
|
|
func (l *PackageRefList) FilterLatestRefs() {
|
|
var (
|
|
lastArch, lastName, lastVer []byte
|
|
arch, name, ver []byte
|
|
parts [][]byte
|
|
)
|
|
|
|
for i := 0; i < len(l.Refs); i++ {
|
|
parts = bytes.Split(l.Refs[i][1:], []byte(" "))
|
|
arch, name, ver = parts[0], parts[1], parts[2]
|
|
|
|
if bytes.Equal(arch, lastArch) && bytes.Equal(name, lastName) {
|
|
// Two packages are identical, check version and only one wins
|
|
vres := CompareVersions(string(ver), string(lastVer))
|
|
|
|
// Remove the older refs from the result
|
|
if vres > 0 {
|
|
// ver[i] > ver[i-1], remove element i-1
|
|
l.Refs = append(l.Refs[:i-1], l.Refs[i:]...)
|
|
} else {
|
|
// ver[i] < ver[i-1], remove element i
|
|
l.Refs = append(l.Refs[:i], l.Refs[i+1:]...)
|
|
arch, name, ver = lastArch, lastName, lastVer
|
|
}
|
|
|
|
// Compensate for the reduced set
|
|
i--
|
|
}
|
|
|
|
lastArch, lastName, lastVer = arch, name, ver
|
|
}
|
|
}
|