Skip to content

Commit cc6ec56

Browse files
GiteaBotExplodingDragonwxiaoguang
authored
Only show the latest version in the Arch index (go-gitea#33262) (go-gitea#33580)
Backport go-gitea#33262 by ExplodingDragon Only show the latest version of the package in the arch repo. closes go-gitea#33534 Co-authored-by: Exploding Dragon <[email protected]> Co-authored-by: wxiaoguang <[email protected]>
1 parent 76bd60f commit cc6ec56

File tree

4 files changed

+229
-41
lines changed

4 files changed

+229
-41
lines changed

services/packages/arch/repository.go

+26-12
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,28 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package
235235
return packages_service.DeletePackageFile(ctx, pf)
236236
}
237237

238+
vpfs := make(map[int64]*entryOptions)
239+
for _, pf := range pfs {
240+
current := &entryOptions{
241+
File: pf,
242+
}
243+
current.Version, err = packages_model.GetVersionByID(ctx, pf.VersionID)
244+
if err != nil {
245+
return err
246+
}
247+
248+
// here we compare the versions but not using SearchLatestVersions because we shouldn't allow "downgrading" to a older version by "latest" one.
249+
// https://wiki.archlinux.org/title/Downgrading_packages : randomly downgrading can mess up dependencies:
250+
// If a downgrade involves a soname change, all dependencies may need downgrading or rebuilding too.
251+
if old, ok := vpfs[current.Version.PackageID]; ok {
252+
if compareVersions(old.Version.Version, current.Version.Version) == -1 {
253+
vpfs[current.Version.PackageID] = current
254+
}
255+
} else {
256+
vpfs[current.Version.PackageID] = current
257+
}
258+
}
259+
238260
indexContent, _ := packages_module.NewHashedBuffer()
239261
defer indexContent.Close()
240262

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

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

246-
for _, pf := range pfs {
247-
opts := &entryOptions{
248-
File: pf,
249-
}
250-
251-
opts.Version, err = packages_model.GetVersionByID(ctx, pf.VersionID)
252-
if err != nil {
253-
return err
254-
}
268+
for _, opts := range vpfs {
255269
if err := json.Unmarshal([]byte(opts.Version.MetadataJSON), &opts.VersionMetadata); err != nil {
256270
return err
257271
}
@@ -263,12 +277,12 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package
263277
}
264278
cache[opts.Package.ID] = opts.Package
265279
}
266-
opts.Blob, err = packages_model.GetBlobByID(ctx, pf.BlobID)
280+
opts.Blob, err = packages_model.GetBlobByID(ctx, opts.File.BlobID)
267281
if err != nil {
268282
return err
269283
}
270284

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

280-
meta, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, pf.ID, arch_module.PropertyMetadata)
294+
meta, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, opts.File.ID, arch_module.PropertyMetadata)
281295
if err != nil {
282296
return err
283297
}

services/packages/arch/vercmp.go

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package arch
5+
6+
import (
7+
"strings"
8+
"unicode"
9+
)
10+
11+
// https://gitlab.archlinux.org/pacman/pacman/-/blob/d55b47e5512808b67bc944feb20c2bcc6c1a4c45/lib/libalpm/version.c
12+
13+
import (
14+
"strconv"
15+
)
16+
17+
func parseEVR(evr string) (epoch, version, release string) {
18+
if before, after, f := strings.Cut(evr, ":"); f {
19+
epoch = before
20+
evr = after
21+
} else {
22+
epoch = "0"
23+
}
24+
25+
if before, after, f := strings.Cut(evr, "-"); f {
26+
version = before
27+
release = after
28+
} else {
29+
version = evr
30+
release = "1"
31+
}
32+
return epoch, version, release
33+
}
34+
35+
func compareSegments(a, b []string) int {
36+
lenA, lenB := len(a), len(b)
37+
var l int
38+
if lenA > lenB {
39+
l = lenB
40+
} else {
41+
l = lenA
42+
}
43+
for i := 0; i < l; i++ {
44+
if r := compare(a[i], b[i]); r != 0 {
45+
return r
46+
}
47+
}
48+
if lenA == lenB {
49+
return 0
50+
} else if l == lenA {
51+
return -1
52+
}
53+
return 1
54+
}
55+
56+
func compare(a, b string) int {
57+
if a == b {
58+
return 0
59+
}
60+
61+
aNumeric := isNumeric(a)
62+
bNumeric := isNumeric(b)
63+
64+
if aNumeric && bNumeric {
65+
aInt, _ := strconv.Atoi(a)
66+
bInt, _ := strconv.Atoi(b)
67+
switch {
68+
case aInt < bInt:
69+
return -1
70+
case aInt > bInt:
71+
return 1
72+
default:
73+
return 0
74+
}
75+
}
76+
77+
if aNumeric {
78+
return 1
79+
}
80+
if bNumeric {
81+
return -1
82+
}
83+
84+
return strings.Compare(a, b)
85+
}
86+
87+
func isNumeric(s string) bool {
88+
for _, c := range s {
89+
if !unicode.IsDigit(c) {
90+
return false
91+
}
92+
}
93+
return true
94+
}
95+
96+
func compareVersions(a, b string) int {
97+
if a == b {
98+
return 0
99+
}
100+
101+
epochA, versionA, releaseA := parseEVR(a)
102+
epochB, versionB, releaseB := parseEVR(b)
103+
104+
if res := compareSegments([]string{epochA}, []string{epochB}); res != 0 {
105+
return res
106+
}
107+
108+
if res := compareSegments(strings.Split(versionA, "."), strings.Split(versionB, ".")); res != 0 {
109+
return res
110+
}
111+
112+
return compareSegments([]string{releaseA}, []string{releaseB})
113+
}

services/packages/arch/vercmp_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package arch
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestCompareVersions(t *testing.T) {
13+
// https://man.archlinux.org/man/vercmp.8.en
14+
checks := [][]string{
15+
{"1.0a", "1.0b", "1.0beta", "1.0p", "1.0pre", "1.0rc", "1.0", "1.0.a", "1.0.1"},
16+
{"1", "1.0", "1.1", "1.1.1", "1.2", "2.0", "3.0.0"},
17+
}
18+
for _, check := range checks {
19+
for i := 0; i < len(check)-1; i++ {
20+
require.Equal(t, -1, compareVersions(check[i], check[i+1]))
21+
require.Equal(t, 1, compareVersions(check[i+1], check[i]))
22+
}
23+
}
24+
require.Equal(t, 1, compareVersions("1.0-2", "1.0"))
25+
require.Equal(t, 0, compareVersions("0:1.0-1", "1.0"))
26+
require.Equal(t, 1, compareVersions("1:1.0-1", "2.0"))
27+
}

tests/integration/api_packages_arch_test.go

+63-29
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,34 @@ license = MIT`)
7979

8080
return buf.Bytes()
8181
}
82+
readIndexContent := func(r io.Reader) (map[string]string, error) {
83+
gzr, err := gzip.NewReader(r)
84+
if err != nil {
85+
return nil, err
86+
}
87+
88+
content := make(map[string]string)
89+
90+
tr := tar.NewReader(gzr)
91+
for {
92+
hd, err := tr.Next()
93+
if err == io.EOF {
94+
break
95+
}
96+
if err != nil {
97+
return nil, err
98+
}
99+
100+
buf, err := io.ReadAll(tr)
101+
if err != nil {
102+
return nil, err
103+
}
104+
105+
content[hd.Name] = string(buf)
106+
}
107+
108+
return content, nil
109+
}
82110

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

174-
readIndexContent := func(r io.Reader) (map[string]string, error) {
175-
gzr, err := gzip.NewReader(r)
176-
if err != nil {
177-
return nil, err
178-
}
179-
180-
content := make(map[string]string)
181-
182-
tr := tar.NewReader(gzr)
183-
for {
184-
hd, err := tr.Next()
185-
if err == io.EOF {
186-
break
187-
}
188-
if err != nil {
189-
return nil, err
190-
}
191-
192-
buf, err := io.ReadAll(tr)
193-
if err != nil {
194-
return nil, err
195-
}
196-
197-
content[hd.Name] = string(buf)
198-
}
199-
200-
return content, nil
201-
}
202-
203202
t.Run("Index", func(t *testing.T) {
204203
defer tests.PrintCurrentTest(t)()
205204

@@ -299,4 +298,39 @@ license = MIT`)
299298
})
300299
}
301300
}
301+
t.Run("KeepLastVersion", func(t *testing.T) {
302+
defer tests.PrintCurrentTest(t)()
303+
pkgVer1 := createPackage("gz", "gitea-test", "1.0.0", "aarch64")
304+
pkgVer2 := createPackage("gz", "gitea-test", "1.0.1", "aarch64")
305+
req := NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgVer1)).
306+
AddBasicAuth(user.Name)
307+
MakeRequest(t, req, http.StatusCreated)
308+
req = NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgVer2)).
309+
AddBasicAuth(user.Name)
310+
MakeRequest(t, req, http.StatusCreated)
311+
312+
req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename))
313+
resp := MakeRequest(t, req, http.StatusOK)
314+
315+
content, err := readIndexContent(resp.Body)
316+
assert.NoError(t, err)
317+
assert.Len(t, content, 2)
318+
319+
_, has := content["gitea-test-1.0.0/desc"]
320+
assert.False(t, has)
321+
_, has = content["gitea-test-1.0.1/desc"]
322+
assert.True(t, has)
323+
324+
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/gitea-test/1.0.1/aarch64", rootURL)).
325+
AddBasicAuth(user.Name)
326+
MakeRequest(t, req, http.StatusNoContent)
327+
328+
req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename))
329+
resp = MakeRequest(t, req, http.StatusOK)
330+
content, err = readIndexContent(resp.Body)
331+
assert.NoError(t, err)
332+
assert.Len(t, content, 2)
333+
_, has = content["gitea-test-1.0.0/desc"]
334+
assert.True(t, has)
335+
})
302336
}

0 commit comments

Comments
 (0)