Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only show the latest version in the Arch index #33262

Merged
merged 29 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ddea32e
feat4arch
ExplodingDragon Jan 14, 2025
0fca43e
Merge branch 'main' into feat-version-arch
ExplodingDragon Jan 14, 2025
1dbf859
feat4arch
ExplodingDragon Jan 14, 2025
08b3967
fix lint
ExplodingDragon Jan 14, 2025
3b55815
add tests & fix lint
ExplodingDragon Jan 15, 2025
cae7f0b
rename settings
ExplodingDragon Jan 15, 2025
8570827
add copyright
ExplodingDragon Jan 15, 2025
0aae7c4
Merge branch 'main' into feat-version-arch
ExplodingDragon Jan 15, 2025
a8b043a
rename setting
ExplodingDragon Jan 20, 2025
d9c9b6b
Merge remote-tracking branch 'origin/feat-version-arch' into feat-ver…
ExplodingDragon Jan 20, 2025
ccfda05
Merge branch 'main' into feat-version-arch
ExplodingDragon Jan 20, 2025
97ed2e7
fix typo
ExplodingDragon Jan 21, 2025
220f23a
Merge branch 'main' into feat-version-arch
ExplodingDragon Jan 21, 2025
39efb64
Merge branch 'main' into feat-version-arch
ExplodingDragon Jan 24, 2025
8dba8a5
Merge branch 'main' into feat-version-arch
ExplodingDragon Feb 3, 2025
7f98d5f
Merge branch 'main' into feat-version-arch
ExplodingDragon Feb 5, 2025
fa496e5
Merge branch 'main' into feat-version-arch
ExplodingDragon Feb 7, 2025
9fa32ae
Merge branch 'main' into feat-version-arch
ExplodingDragon Feb 9, 2025
c869dc5
Merge branch 'main' into feat-version-arch
ExplodingDragon Feb 10, 2025
e7bbefb
remove options
ExplodingDragon Feb 10, 2025
5d25559
Merge branch 'main' into feat-version-arch
ExplodingDragon Feb 10, 2025
d98ceb1
Merge branch 'main' into feat-version-arch
GiteaBot Feb 10, 2025
ffab002
Merge branch 'main' into feat-version-arch
GiteaBot Feb 10, 2025
de4db89
Merge branch 'main' into feat-version-arch
ExplodingDragon Feb 11, 2025
219d872
Update services/packages/arch/repository.go
wxiaoguang Feb 11, 2025
fb0e0de
Merge branch 'main' into feat-version-arch
wxiaoguang Feb 11, 2025
2667111
Merge branch 'main' into feat-version-arch
ExplodingDragon Feb 11, 2025
718fc19
Merge branch 'main' into feat-version-arch
ExplodingDragon Feb 12, 2025
fed5aac
Merge branch 'main' into feat-version-arch
ExplodingDragon Feb 13, 2025
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
38 changes: 26 additions & 12 deletions services/packages/arch/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,28 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package
return packages_service.DeletePackageFile(ctx, pf)
}

vpfs := make(map[int64]*entryOptions)
for _, pf := range pfs {
current := &entryOptions{
File: pf,
}
current.Version, err = packages_model.GetVersionByID(ctx, pf.VersionID)
if err != nil {
return err
}

// here we compare the versions but not using SearchLatestVersions because we shouldn't allow "downgrading" to a older version by "latest" one.
// https://wiki.archlinux.org/title/Downgrading_packages : randomly downgrading can mess up dependencies:
// If a downgrade involves a soname change, all dependencies may need downgrading or rebuilding too.
if old, ok := vpfs[current.Version.PackageID]; ok {
if compareVersions(old.Version.Version, current.Version.Version) == -1 {
vpfs[current.Version.PackageID] = current
}
} else {
vpfs[current.Version.PackageID] = current
}
}

indexContent, _ := packages_module.NewHashedBuffer()
defer indexContent.Close()

Expand All @@ -243,15 +265,7 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package

cache := make(map[int64]*packages_model.Package)

for _, pf := range pfs {
opts := &entryOptions{
File: pf,
}

opts.Version, err = packages_model.GetVersionByID(ctx, pf.VersionID)
if err != nil {
return err
}
for _, opts := range vpfs {
if err := json.Unmarshal([]byte(opts.Version.MetadataJSON), &opts.VersionMetadata); err != nil {
return err
}
Expand All @@ -263,12 +277,12 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package
}
cache[opts.Package.ID] = opts.Package
}
opts.Blob, err = packages_model.GetBlobByID(ctx, pf.BlobID)
opts.Blob, err = packages_model.GetBlobByID(ctx, opts.File.BlobID)
if err != nil {
return err
}

sig, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, pf.ID, arch_module.PropertySignature)
sig, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, opts.File.ID, arch_module.PropertySignature)
if err != nil {
return err
}
Expand All @@ -277,7 +291,7 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package
}
opts.Signature = sig[0].Value

meta, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, pf.ID, arch_module.PropertyMetadata)
meta, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, opts.File.ID, arch_module.PropertyMetadata)
if err != nil {
return err
}
Expand Down
113 changes: 113 additions & 0 deletions services/packages/arch/vercmp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package arch

import (
"strings"
"unicode"
)

// https://gitlab.archlinux.org/pacman/pacman/-/blob/d55b47e5512808b67bc944feb20c2bcc6c1a4c45/lib/libalpm/version.c

import (
"strconv"
)

func parseEVR(evr string) (epoch, version, release string) {
if before, after, f := strings.Cut(evr, ":"); f {
epoch = before
evr = after
} else {
epoch = "0"
}

if before, after, f := strings.Cut(evr, "-"); f {
version = before
release = after
} else {
version = evr
release = "1"
}
return epoch, version, release
}

func compareSegments(a, b []string) int {
lenA, lenB := len(a), len(b)
var l int
if lenA > lenB {
l = lenB
} else {
l = lenA
}
for i := 0; i < l; i++ {
if r := compare(a[i], b[i]); r != 0 {
return r
}
}
if lenA == lenB {
return 0
} else if l == lenA {
return -1
}
return 1
}

func compare(a, b string) int {
if a == b {
return 0
}

aNumeric := isNumeric(a)
bNumeric := isNumeric(b)

if aNumeric && bNumeric {
aInt, _ := strconv.Atoi(a)
bInt, _ := strconv.Atoi(b)
switch {
case aInt < bInt:
return -1
case aInt > bInt:
return 1
default:
return 0
}
}

if aNumeric {
return 1
}
if bNumeric {
return -1
}

return strings.Compare(a, b)
}

func isNumeric(s string) bool {
for _, c := range s {
if !unicode.IsDigit(c) {
return false
}
}
return true
}

func compareVersions(a, b string) int {
if a == b {
return 0
}

epochA, versionA, releaseA := parseEVR(a)
epochB, versionB, releaseB := parseEVR(b)

if res := compareSegments([]string{epochA}, []string{epochB}); res != 0 {
return res
}

if res := compareSegments(strings.Split(versionA, "."), strings.Split(versionB, ".")); res != 0 {
return res
}

return compareSegments([]string{releaseA}, []string{releaseB})
}
27 changes: 27 additions & 0 deletions services/packages/arch/vercmp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package arch

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestCompareVersions(t *testing.T) {
// https://man.archlinux.org/man/vercmp.8.en
checks := [][]string{
{"1.0a", "1.0b", "1.0beta", "1.0p", "1.0pre", "1.0rc", "1.0", "1.0.a", "1.0.1"},
{"1", "1.0", "1.1", "1.1.1", "1.2", "2.0", "3.0.0"},
}
for _, check := range checks {
for i := 0; i < len(check)-1; i++ {
require.Equal(t, -1, compareVersions(check[i], check[i+1]))
require.Equal(t, 1, compareVersions(check[i+1], check[i]))
}
}
require.Equal(t, 1, compareVersions("1.0-2", "1.0"))
require.Equal(t, 0, compareVersions("0:1.0-1", "1.0"))
require.Equal(t, 1, compareVersions("1:1.0-1", "2.0"))
}
92 changes: 63 additions & 29 deletions tests/integration/api_packages_arch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,34 @@ license = MIT`)

return buf.Bytes()
}
readIndexContent := func(r io.Reader) (map[string]string, error) {
gzr, err := gzip.NewReader(r)
if err != nil {
return nil, err
}

content := make(map[string]string)

tr := tar.NewReader(gzr)
for {
hd, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}

buf, err := io.ReadAll(tr)
if err != nil {
return nil, err
}

content[hd.Name] = string(buf)
}

return content, nil
}

compressions := []string{"gz", "xz", "zst"}
repositories := []string{"main", "testing", "with/slash", ""}
Expand Down Expand Up @@ -171,35 +199,6 @@ license = MIT`)
MakeRequest(t, req, http.StatusConflict)
})

readIndexContent := func(r io.Reader) (map[string]string, error) {
gzr, err := gzip.NewReader(r)
if err != nil {
return nil, err
}

content := make(map[string]string)

tr := tar.NewReader(gzr)
for {
hd, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}

buf, err := io.ReadAll(tr)
if err != nil {
return nil, err
}

content[hd.Name] = string(buf)
}

return content, nil
}

t.Run("Index", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()

Expand Down Expand Up @@ -299,4 +298,39 @@ license = MIT`)
})
}
}
t.Run("KeepLastVersion", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
pkgVer1 := createPackage("gz", "gitea-test", "1.0.0", "aarch64")
pkgVer2 := createPackage("gz", "gitea-test", "1.0.1", "aarch64")
req := NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgVer1)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
req = NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgVer2)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)

req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename))
resp := MakeRequest(t, req, http.StatusOK)

content, err := readIndexContent(resp.Body)
assert.NoError(t, err)
assert.Len(t, content, 2)

_, has := content["gitea-test-1.0.0/desc"]
assert.False(t, has)
_, has = content["gitea-test-1.0.1/desc"]
assert.True(t, has)

req = NewRequest(t, "DELETE", fmt.Sprintf("%s/gitea-test/1.0.1/aarch64", rootURL)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNoContent)

req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename))
resp = MakeRequest(t, req, http.StatusOK)
content, err = readIndexContent(resp.Body)
assert.NoError(t, err)
assert.Len(t, content, 2)
_, has = content["gitea-test-1.0.0/desc"]
assert.True(t, has)
})
}
Loading