Compare commits

...
2 Commits
Author SHA1 Message Date
f15868d442 fix(packages): serve noarch Alpine index for any requested architecture (#38479)
Fixes #38456

## Problem
The Alpine package registry serves one `APKINDEX.tar.gz` per
architecture. When a repository contains only `noarch` packages (no
architecture-specific packages), only the `noarch` index is built.
Because `apk` substitutes `$ARCH` with the host architecture and
requests e.g. `x86_64/APKINDEX.tar.gz`, such a repository returned HTTP
404 and was unusable, matching the report in #38456.

## Fix
`GetRepositoryFile` now falls back to the `noarch` index when the
requested architecture has no index of its own, mirroring the fallback
already present in the sibling `DownloadPackageFile` handler. `noarch`
packages are installable on every architecture, so serving them for any
requested architecture is correct. The index-build side is unchanged;
only the serving path gains the fallback, so mixed repositories (which
already merge `noarch` into each per-architecture index) are unaffected.

## AI assistance disclosure
This change was implemented with the help of an AI coding assistant,
which gitea's CONTRIBUTING.md explicitly welcomes when disclosed. I have
reviewed the change, understand it, and can explain and defend it.

## Tests
Added a `NoArchOnly` subtest to `TestPackageAlpine` that publishes only
a `noarch` package to a fresh repository and asserts that `GET
.../x86_64/APKINDEX.tar.gz` now returns `200` (previously `404`) and
that the served index lists the noarch package. Verified locally with
`go build`/`go vet` on the changed package and a compile of the
integration test package (`go test -c`); the full integration run relies
on CI.

---------

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-07-16 11:51:36 +02:00
Zettat123andGitHub a77bf48b41 fix: 500 error when updating user visibility (#38480)
## How to reproduce this bug

1. Create a user with `visibility=public`
2. Update `ALLOWED_USER_VISIBILITY_MODES` setting to `limited, private`
3. Modify any field other than "User visibility" (e.g. "Full Name")
4. UI shows 500 error

## Fix

Only update the visibility field when it actually changes.
2026-07-16 09:53:43 +02:00
4 changed files with 84 additions and 2 deletions
+20 -1
View File
@@ -67,15 +67,34 @@ func GetRepositoryFile(ctx *context.Context) {
return
}
branch := ctx.PathParam("branch")
repository := ctx.PathParam("repository")
architecture := ctx.PathParam("architecture")
s, u, pf, err := packages_service.OpenFileForDownloadByPackageVersion(
ctx,
pv,
&packages_service.PackageFileInfo{
Filename: alpine_service.IndexArchiveFilename,
CompositeKey: fmt.Sprintf("%s|%s|%s", ctx.PathParam("branch"), ctx.PathParam("repository"), ctx.PathParam("architecture")),
CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, architecture),
},
ctx.Req.Method,
)
// A repository that only contains "noarch" packages has no per-architecture
// index. Since noarch packages are installable on every architecture, fall
// back to the noarch index so clients requesting their own architecture
// (e.g. x86_64) can still discover them.
if errors.Is(err, util.ErrNotExist) && architecture != alpine_module.NoArch {
s, u, pf, err = packages_service.OpenFileForDownloadByPackageVersion(
ctx,
pv,
&packages_service.PackageFileInfo{
Filename: alpine_service.IndexArchiveFilename,
CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, alpine_module.NoArch),
},
ctx.Req.Method,
)
}
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
+2 -1
View File
@@ -145,7 +145,8 @@ func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) er
}
}
if opts.Visibility.Has() {
// only validate and persist the visibility when it actually changes
if opts.Visibility.Has() && opts.Visibility.Value() != u.Visibility {
if !u.IsOrganization() && !setting.Service.AllowedUserVisibilityModesSlice.IsAllowedVisibility(opts.Visibility.Value()) {
return fmt.Errorf("visibility mode not allowed: %s", opts.Visibility.Value().String())
}
+32
View File
@@ -10,7 +10,9 @@ import (
user_model "gitea.dev/models/user"
password_module "gitea.dev/modules/auth/password"
"gitea.dev/modules/optional"
"gitea.dev/modules/setting"
"gitea.dev/modules/structs"
"gitea.dev/modules/test"
"github.com/stretchr/testify/assert"
)
@@ -121,3 +123,33 @@ func TestUpdateAuth(t *testing.T) {
Password: optional.Some("aaaa"),
}), password_module.ErrMinLength)
}
func TestUpdateUserVisibility(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
// user28's current visibility is public, e.g. an account created before public was disallowed
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28})
assert.Equal(t, structs.VisibleTypePublic, user.Visibility)
// public is no longer an allowed visibility mode, e.g. ALLOWED_USER_VISIBILITY_MODES = limited, private
defer test.MockVariableValue(&setting.Service.AllowedUserVisibilityModesSlice, setting.AllowedVisibility{false, true, true})()
// re-submitting the unchanged (now-disallowed) visibility must not fail the whole update
assert.NoError(t, UpdateUser(t.Context(), user, &UpdateOptions{
FullName: optional.Some("Changed Name"),
Visibility: optional.Some(structs.VisibleTypePublic),
}))
assert.Equal(t, "Changed Name", user.FullName)
assert.Equal(t, structs.VisibleTypePublic, user.Visibility)
// changing to an allowed visibility still works
assert.NoError(t, UpdateUser(t.Context(), user, &UpdateOptions{
Visibility: optional.Some(structs.VisibleTypePrivate),
}))
assert.Equal(t, structs.VisibleTypePrivate, user.Visibility)
// genuinely changing to a disallowed visibility is still rejected
assert.Error(t, UpdateUser(t.Context(), user, &UpdateOptions{
Visibility: optional.Some(structs.VisibleTypePublic),
}))
}
@@ -268,6 +268,36 @@ AACAX/AKARNTyAAoAAA=`
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNoContent)
})
t.Run("NoArchOnly", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// A repository that only contains noarch packages has no per-architecture index,
// but apk always requests the index for its own architecture (e.g. x86_64).
// That request must fall back to the noarch index instead of 404ing.
noarchRepository := repository + "-noarchonly"
req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/%s/%s", rootURL, branch, noarchRepository), bytes.NewReader(noarchContent)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusCreated)
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/x86_64/APKINDEX.tar.gz", rootURL, branch, noarchRepository))
resp := MakeRequest(t, req, http.StatusOK)
content, err := readIndexContent(resp.Body)
assert.NoError(t, err)
assert.Contains(t, content, "C:Q1kbH5WoIPFccQYyATanaKXd2cJcc=\n")
assert.Contains(t, content, "A:noarch\n")
// The noarch index is still directly retrievable too.
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s/noarch/APKINDEX.tar.gz", rootURL, branch, noarchRepository))
MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/noarch/gitea-noarch-1.4-r0.apk", rootURL, branch, noarchRepository)).
AddBasicAuth(user.Name)
MakeRequest(t, req, http.StatusNoContent)
})
})
}
}