From d6496c61561d90f56f0b5ec3f75d4de8e36a5c9f Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Wed, 18 Mar 2026 00:50:32 +0000 Subject: [PATCH 1/5] [skip ci] Updated translations via Crowdin --- options/locale/locale_ko-KR.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/options/locale/locale_ko-KR.json b/options/locale/locale_ko-KR.json index 39e6b4226b..8f572e9f9b 100644 --- a/options/locale/locale_ko-KR.json +++ b/options/locale/locale_ko-KR.json @@ -3644,6 +3644,7 @@ "actions.runners.id": "ID", "actions.runners.name": "이름", "actions.runners.owner_type": "유형", + "actions.runners.availability": "가용성", "actions.runners.description": "설명", "actions.runners.labels": "레이블", "actions.runners.last_online": "최근 접속 시간", @@ -3659,6 +3660,12 @@ "actions.runners.update_runner": "변경사항 업데이트", "actions.runners.update_runner_success": "러너가 성공적으로 업데이트됨", "actions.runners.update_runner_failed": "러너 업데이트 실패함", + "actions.runners.enable_runner": "이 러너를 활성화", + "actions.runners.enable_runner_success": "러너가 성공적으로 활성화됨", + "actions.runners.enable_runner_failed": "러너 활성화 실패함", + "actions.runners.disable_runner": "이 러너를 비활성화", + "actions.runners.disable_runner_success": "러너 비활성화가 성공함", + "actions.runners.disable_runner_failed": "러너 비활성화 실패함", "actions.runners.delete_runner": "이 러너를 삭제", "actions.runners.delete_runner_success": "러너가 성공적으로 삭제됨", "actions.runners.delete_runner_failed": "러너 삭제를 실패함", From 18c65965abea6a97d075678edd55cf9eccc561a9 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 19 Mar 2026 07:13:55 +0800 Subject: [PATCH 2/5] Fix various trivial problems (#36921) * Fix #36915 * Fix #36919 * Close #36600 * Close #36601 * Fix incorrect oauth2 error message display --- go.mod | 2 +- models/issues/pull.go | 7 ++++--- models/migrations/base/db.go | 14 ++++++------- modules/markup/common/linkify.go | 3 ++- routers/web/auth/oauth.go | 2 +- services/issue/pull.go | 4 +++- services/repository/files/tree.go | 35 ++++++++----------------------- 7 files changed, 27 insertions(+), 40 deletions(-) diff --git a/go.mod b/go.mod index f0bb361f2d..24c18d5703 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,7 @@ require ( github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21 github.com/chi-middleware/proxy v1.1.1 github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 + github.com/dlclark/regexp2 v1.11.5 github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 github.com/dustin/go-humanize v1.0.1 github.com/editorconfig/editorconfig-core-go/v2 v2.6.4 @@ -183,7 +184,6 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidmz/go-pageant v1.0.2 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/dlclark/regexp2 v1.11.5 // indirect github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect diff --git a/models/issues/pull.go b/models/issues/pull.go index 9f180f9ac9..c07044f301 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "io" - "regexp" "strings" "code.gitea.io/gitea/models/db" @@ -24,6 +23,7 @@ import ( "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" + "github.com/dlclark/regexp2" "xorm.io/builder" ) @@ -861,7 +861,7 @@ func GetCodeOwnersFromContent(ctx context.Context, data string) ([]*CodeOwnerRul } type CodeOwnerRule struct { - Rule *regexp.Regexp + Rule *regexp2.Regexp // it supports negative lookahead, does better for end users Negative bool Users []*user_model.User Teams []*org_model.Team @@ -877,7 +877,8 @@ func ParseCodeOwnersLine(ctx context.Context, tokens []string) (*CodeOwnerRule, warnings := make([]string, 0) - rule.Rule, err = regexp.Compile(fmt.Sprintf("^%s$", strings.TrimPrefix(tokens[0], "!"))) + expr := fmt.Sprintf("^%s$", strings.TrimPrefix(tokens[0], "!")) + rule.Rule, err = regexp2.Compile(expr, regexp2.None) if err != nil { warnings = append(warnings, fmt.Sprintf("incorrect codeowner regexp: %s", err)) return nil, warnings diff --git a/models/migrations/base/db.go b/models/migrations/base/db.go index 3b8f0e00a0..16048c2073 100644 --- a/models/migrations/base/db.go +++ b/models/migrations/base/db.go @@ -402,7 +402,7 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin cols += "DROP COLUMN `" + col + "` CASCADE" } if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil { - return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err) + return fmt.Errorf("drop table `%s` columns %v: %w", tableName, columnNames, err) } case setting.Database.Type.IsMySQL(): // Drop indexes on columns first @@ -430,7 +430,7 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin cols += "DROP COLUMN `" + col + "`" } if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil { - return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err) + return fmt.Errorf("drop table `%s` columns %v: %w", tableName, columnNames, err) } case setting.Database.Type.IsMSSQL(): cols := "" @@ -444,27 +444,27 @@ func DropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin tableName, strings.ReplaceAll(cols, "`", "'")) constraints := make([]string, 0) if err := sess.SQL(sql).Find(&constraints); err != nil { - return fmt.Errorf("Find constraints: %v", err) + return fmt.Errorf("find constraints: %w", err) } for _, constraint := range constraints { if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP CONSTRAINT `%s`", tableName, constraint)); err != nil { - return fmt.Errorf("Drop table `%s` default constraint `%s`: %v", tableName, constraint, err) + return fmt.Errorf("drop table `%s` default constraint `%s`: %w", tableName, constraint, err) } } sql = fmt.Sprintf("SELECT DISTINCT Name FROM sys.indexes INNER JOIN sys.index_columns ON indexes.index_id = index_columns.index_id AND indexes.object_id = index_columns.object_id WHERE indexes.object_id = OBJECT_ID('%[1]s') AND index_columns.column_id IN (SELECT column_id FROM sys.columns WHERE LOWER(name) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))", tableName, strings.ReplaceAll(cols, "`", "'")) constraints = make([]string, 0) if err := sess.SQL(sql).Find(&constraints); err != nil { - return fmt.Errorf("Find constraints: %v", err) + return fmt.Errorf("find constraints: %w", err) } for _, constraint := range constraints { if _, err := sess.Exec(fmt.Sprintf("DROP INDEX `%[2]s` ON `%[1]s`", tableName, constraint)); err != nil { - return fmt.Errorf("Drop index `%[2]s` on `%[1]s`: %v", tableName, constraint, err) + return fmt.Errorf("drop index `%[2]s` on `%[1]s`: %[3]w", tableName, constraint, err) } } if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP COLUMN %s", tableName, cols)); err != nil { - return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err) + return fmt.Errorf("drop table `%s` columns %v: %w", tableName, columnNames, err) } default: log.Fatal("Unrecognized DB") diff --git a/modules/markup/common/linkify.go b/modules/markup/common/linkify.go index aff1813803..54e40a02d6 100644 --- a/modules/markup/common/linkify.go +++ b/modules/markup/common/linkify.go @@ -96,6 +96,7 @@ func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Cont m[1] -= closing } } else if lastChar == ';' { + // exclude HTML entity reference, e.g.: exclude " " from "http://example.com?foo=1 " i := m[1] - 2 for ; i >= m[0]; i-- { if util.IsAlphaNumeric(line[i]) { @@ -105,7 +106,7 @@ func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Cont } if i != m[1]-2 { if line[i] == '&' { - m[1] -= m[1] - i + m[1] = i } } } diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index bb0784e434..3b1744669d 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -118,7 +118,7 @@ func SignInOAuthCallback(ctx *context.Context) { return } if err, ok := err.(*go_oauth2.RetrieveError); ok { - ctx.Flash.Error("OAuth2 RetrieveError: "+err.Error(), true) + ctx.Flash.Error("OAuth2 RetrieveError: " + err.Error()) ctx.Redirect(setting.AppSubURL + "/user/login") return } diff --git a/services/issue/pull.go b/services/issue/pull.go index 778bbefc28..f415ebe759 100644 --- a/services/issue/pull.go +++ b/services/issue/pull.go @@ -95,7 +95,9 @@ func PullRequestCodeOwnersReview(ctx context.Context, pr *issues_model.PullReque uniqTeams := make(map[string]*org_model.Team) for _, rule := range rules { for _, f := range changedFiles { - if (rule.Rule.MatchString(f) && !rule.Negative) || (!rule.Rule.MatchString(f) && rule.Negative) { + shouldMatch := !rule.Negative + matched, _ := rule.Rule.MatchString(f) // err only happens when timeouts, any error can be considered as not matched + if matched == shouldMatch { for _, u := range rule.Users { uniqUsers[u.ID] = u } diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go index e481a3e7d2..e678b07f56 100644 --- a/services/repository/files/tree.go +++ b/services/repository/files/tree.go @@ -61,33 +61,18 @@ func GetTreeBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git return nil, err } apiURL := repo.APIURL() - apiURLLen := len(apiURL) - objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName) - hashLen := objectFormat.FullLength() - - const gitBlobsPath = "/git/blobs/" - blobURL := make([]byte, apiURLLen+hashLen+len(gitBlobsPath)) - copy(blobURL, apiURL) - copy(blobURL[apiURLLen:], []byte(gitBlobsPath)) - - const gitTreePath = "/git/trees/" - treeURL := make([]byte, apiURLLen+hashLen+len(gitTreePath)) - copy(treeURL, apiURL) - copy(treeURL[apiURLLen:], []byte(gitTreePath)) - - // copyPos is at the start of the hash - copyPos := len(treeURL) - hashLen + blobURLBase := apiURL + "/git/blobs/" + treeURLBase := apiURL + "/git/trees/" if perPage <= 0 || perPage > setting.API.DefaultGitTreesPerPage { perPage = setting.API.DefaultGitTreesPerPage } - if page <= 0 { - page = 1 - } + page = max(page, 1) + tree.Page = page tree.TotalCount = len(entries) - rangeStart := perPage * (page - 1) - if rangeStart >= len(entries) { + rangeStart := perPage * (page - 1) // int might overflow + if rangeStart < 0 || rangeStart >= len(entries) { return tree, nil } rangeEnd := min(rangeStart+perPage, len(entries)) @@ -103,16 +88,14 @@ func GetTreeBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git tree.Entries[i].SHA = entries[e].ID.String() if entries[e].IsDir() { - copy(treeURL[copyPos:], entries[e].ID.String()) - tree.Entries[i].URL = string(treeURL) + tree.Entries[i].URL = treeURLBase + entries[e].ID.String() } else if entries[e].IsSubModule() { - // In Github Rest API Version=2022-11-28, if a tree entry is a submodule, + // In GitHub Rest API Version=2022-11-28, if a tree entry is a submodule, // its url will be returned as an empty string. // So the URL will be set to "" here. tree.Entries[i].URL = "" } else { - copy(blobURL[copyPos:], entries[e].ID.String()) - tree.Entries[i].URL = string(blobURL) + tree.Entries[i].URL = blobURLBase + entries[e].ID.String() } } return tree, nil From 00060ff73c12f468008b0fb7e9b4fd320861b7fd Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 19 Mar 2026 07:43:44 +0800 Subject: [PATCH 3/5] Make container registry support Apple Container (basic auth) (#36920) Fix #36907 --- routers/api/packages/api.go | 24 ++++++++++++++----- routers/api/packages/container/container.go | 10 ++++---- .../api_packages_container_test.go | 5 +++- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 7f81242db4..44bb80018b 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -88,7 +88,11 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) { } } -func verifyAuth(r *web.Router, authMethods []auth.Method) { +type verifyAuthOptions struct { + afterAuthCallback func(ctx *context.Context, err error) +} + +func verifyAuth(r *web.Router, authMethods []auth.Method, opts verifyAuthOptions) { if setting.Service.EnableReverseProxyAuth { authMethods = append(authMethods, &auth.ReverseProxy{}) } @@ -97,12 +101,13 @@ func verifyAuth(r *web.Router, authMethods []auth.Method) { r.AfterRouting(func(ctx *context.Context) { var err error ctx.Doer, err = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session) - if err != nil { + ctx.IsSigned = ctx.Doer != nil + if opts.afterAuthCallback != nil { + opts.afterAuthCallback(ctx, err) + } else if err != nil { log.Error("Failed to verify user: %v", err) ctx.HTTPError(http.StatusUnauthorized, "Failed to authenticate user") - return } - ctx.IsSigned = ctx.Doer != nil }) } @@ -119,7 +124,7 @@ func CommonRoutes() *web.Router { &nuget.Auth{}, &Auth{}, &chef.Auth{}, - }) + }, verifyAuthOptions{}) r.Group("/{username}", func() { r.Group("/alpine", func() { @@ -537,8 +542,15 @@ func ContainerRoutes() *web.Router { verifyAuth(r, []auth.Method{ &auth.Basic{}, - // container auth requires an token, so container.Authenticate issues a Ghost user token for anonymous access + // container auth requires token, so container.Authenticate issues a Ghost user token for anonymous access &Auth{AllowGhostUser: true}, + }, verifyAuthOptions{ + afterAuthCallback: func(ctx *context.Context, err error) { + if err != nil { + log.Error("Failed to verify container user: %v", err) + container.APIUnauthorizedError(ctx) + } + }, }) // TODO: Content Discovery / References (not implemented yet) diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go index 7cf1c36375..a6512181e0 100644 --- a/routers/api/packages/container/container.go +++ b/routers/api/packages/container/container.go @@ -120,17 +120,19 @@ func apiErrorDefined(ctx *context.Context, err *namedError) { }) } -func apiUnauthorizedError(ctx *context.Context) { +func APIUnauthorizedError(ctx *context.Context) { // container registry requires that the "/v2" must be in the root, so the sub-path in AppURL should be removed realmURL := httplib.GuessCurrentHostURL(ctx) + "/v2/token" ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+realmURL+`",service="container_registry",scope="*"`) + // support apple container like: container registry login -u + ctx.Resp.Header().Add("WWW-Authenticate", `Basic realm="Gitea Container Registry"`) apiErrorDefined(ctx, errUnauthorized) } // ReqContainerAccess is a middleware which checks the current user valid (real user or ghost if anonymous access is enabled) func ReqContainerAccess(ctx *context.Context) { if ctx.Doer == nil || (setting.Service.RequireSignInViewStrict && ctx.Doer.IsGhost()) { - apiUnauthorizedError(ctx) + APIUnauthorizedError(ctx) } } @@ -156,7 +158,7 @@ func Authenticate(ctx *context.Context) { packageScope := auth_service.GetAccessScope(ctx.Data) if u == nil { if setting.Service.RequireSignInViewStrict { - apiUnauthorizedError(ctx) + APIUnauthorizedError(ctx) return } @@ -170,7 +172,7 @@ func Authenticate(ctx *context.Context) { if err != nil { log.Error("Error checking access scope: %v", err) } - apiUnauthorizedError(ctx) + APIUnauthorizedError(ctx) return } } diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go index efa04684af..d5d243f9ab 100644 --- a/tests/integration/api_packages_container_test.go +++ b/tests/integration/api_packages_container_test.go @@ -88,7 +88,10 @@ func TestPackageContainer(t *testing.T) { Token string `json:"token"` } - defaultAuthenticateValues := []string{`Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*"`} + defaultAuthenticateValues := []string{ + `Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*"`, + `Basic realm="Gitea Container Registry"`, + } t.Run("Anonymous", func(t *testing.T) { defer tests.PrintCurrentTest(t)() From 79f96b3e2427e87dcc1754404c2bd4cd24cb1e69 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Thu, 19 Mar 2026 00:50:44 +0000 Subject: [PATCH 4/5] [skip ci] Updated translations via Crowdin --- options/locale/locale_ga-IE.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/options/locale/locale_ga-IE.json b/options/locale/locale_ga-IE.json index 4669252a6e..f43c9ad355 100644 --- a/options/locale/locale_ga-IE.json +++ b/options/locale/locale_ga-IE.json @@ -3644,6 +3644,7 @@ "actions.runners.id": "ID", "actions.runners.name": "Ainm", "actions.runners.owner_type": "Cineál", + "actions.runners.availability": "Infhaighteacht", "actions.runners.description": "Cur síos", "actions.runners.labels": "Lipéid", "actions.runners.last_online": "Am Ar Líne Deiridh", @@ -3659,6 +3660,12 @@ "actions.runners.update_runner": "Nuashonrú Athruithe", "actions.runners.update_runner_success": "Nuashonraíodh an Reathaí", "actions.runners.update_runner_failed": "Theip ar an reathaí a nuashonrú", + "actions.runners.enable_runner": "Cumasaigh an ritheoir seo", + "actions.runners.enable_runner_success": "Cumasaíodh an rithire go rathúil", + "actions.runners.enable_runner_failed": "Theip ar an rithire a chumasú", + "actions.runners.disable_runner": "Díchumasaigh an ritheoir seo", + "actions.runners.disable_runner_success": "Díchumasaíodh an ritheoir go rathúil", + "actions.runners.disable_runner_failed": "Theip ar an ritheoir a dhíchumasú", "actions.runners.delete_runner": "Scrios an reathaí seo", "actions.runners.delete_runner_success": "Scriosadh an reathaí go rathúil", "actions.runners.delete_runner_failed": "Theip ar an reathaí a scriosadh", From 068d7a513aef1d9b037122b99ded889b1774aa8e Mon Sep 17 00:00:00 2001 From: Xijiang Yu Date: Thu, 19 Mar 2026 19:12:53 +0100 Subject: [PATCH 5/5] fix(upgrade.sh): use HTTPS for GPG key import and restore SELinux context after upgrade (#36930) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Two bug fixes for `contrib/upgrade.sh` found during a real-world upgrade from 1.24.3 to 1.25.5 on Fedora. --- ### Fix 1: GPG key import fails when HKP port 11371 is blocked (closes #36928) **Before:** ```bash gpg --keyserver keys.openpgp.org --recv 7C9E68152594688862D62AF62D9AE806EC1592E2 ``` This uses HKP port **11371**, which is blocked by many firewalls. The upgrade aborts with: ``` gpg: keyserver receive failed: Connection timed out ``` **After:** ```bash curl -fsSL --connect-timeout 10 \ "https://keys.openpgp.org/vks/v1/by-fingerprint/7C9E68152594688862D62AF62D9AE806EC1592E2" \ | gpg --import \ || gpg --keyserver keyserver.ubuntu.com --recv 7C9E68152594688862D62AF62D9AE806EC1592E2 \ || gpg --keyserver keys.openpgp.org --recv 7C9E68152594688862D62AF62D9AE806EC1592E2 ``` Same `keys.openpgp.org` server, same key — but fetched over **HTTPS port 443** which is universally accessible. Keyservers remain as fallbacks. --- ### Fix 2: Gitea fails to start after upgrade on SELinux systems (closes #36929) **Problem:** After `mv`-ing the binary from `$giteahome` to `/usr/local/bin/gitea`, the file retains the SELinux context of the source directory. Systemd refuses to execute it, exiting with `status=203/EXEC`. **Fix:** Add a `restorecon` call guarded by `command -v` so it is a no-op on non-SELinux systems: ```bash command -v restorecon &>/dev/null && restorecon -v "$giteabin" || true ``` Verified: `restorecon -v /usr/local/bin/gitea` immediately restored service on the affected machine. --------- Signed-off-by: wxiaoguang Co-authored-by: wxiaoguang --- contrib/upgrade.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contrib/upgrade.sh b/contrib/upgrade.sh index e5e296ea8b..2593d24509 100755 --- a/contrib/upgrade.sh +++ b/contrib/upgrade.sh @@ -108,7 +108,9 @@ curl --connect-timeout 10 --silent --show-error --fail --location -O "$binurl{,. sha256sum -c "${binname}.xz.sha256" if [[ -z "${ignore_gpg:-}" ]]; then require gpg - gpg --keyserver keys.openpgp.org --recv 7C9E68152594688862D62AF62D9AE806EC1592E2 + # try to use curl first, it uses standard tcp 443 port and works better behind strict firewall rules + curl -fsSL --connect-timeout 10 "https://keys.openpgp.org/vks/v1/by-fingerprint/7C9E68152594688862D62AF62D9AE806EC1592E2" | gpg --import \ + || gpg --keyserver keys.openpgp.org --recv 7C9E68152594688862D62AF62D9AE806EC1592E2 gpg --verify "${binname}.xz.asc" "${binname}.xz" || { echo 'Signature does not match'; exit 1; } fi rm "${binname}".xz.{sha256,asc} @@ -127,6 +129,8 @@ echo "Creating backup in $giteahome" giteacmd dump $backupopts echo "Updating binary at $giteabin" cp -f "$giteabin" "$giteabin.bak" && mv -f "$binname" "$giteabin" +# Restore SELinux context if applicable (e.g. RHEL/Fedora) +command -v restorecon &>/dev/null && restorecon -v "$giteabin" || true $service_start $service_status