From 9d737a6400d34e4df2212f75109bef1abec7ddaf Mon Sep 17 00:00:00 2001 From: Giteabot Date: Thu, 21 May 2026 23:44:13 -0700 Subject: [PATCH 01/10] fix(deps): update module golang.org/x/crypto to v0.52.0 [security] (#37806) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) | [`v0.51.0` → `v0.52.0`](https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.51.0...refs/tags/v0.52.0) | ![age](https://developer.mend.io/api/mc/badges/age/go/golang.org%2fx%2fcrypto/v0.52.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/go/golang.org%2fx%2fcrypto/v0.51.0/v0.52.0?slim=true) | --- ### Invoking key constraints not enforced in golang.org/x/crypto/ssh/agent [CVE-2026-39833](https://nvd.nist.gov/vuln/detail/CVE-2026-39833) / [GO-2026-5005](https://pkg.go.dev/vuln/GO-2026-5005)
More information #### Details The in-memory keyring returned by NewKeyring() silently accepted keys with the ConfirmBeforeUse constraint but never enforced it. The key would sign without any confirmation prompt, with no indication to the caller that the constraint was not in effect. NewKeyring() now returns an error when unsupported constraints are requested. #### Severity Unknown #### References - [https://go.dev/issue/79436](https://go.dev/issue/79436) - [https://go.dev/cl/778640](https://go.dev/cl/778640) - [https://go.dev/cl/778641](https://go.dev/cl/778641) - [https://groups.google.com/g/golang-announce/c/a082jnz-LvI](https://groups.google.com/g/golang-announce/c/a082jnz-LvI) This data is provided by [OSV](https://osv.dev/vulnerability/GO-2026-5005) and the [Go Vulnerability Database](https://redirect.github.com/golang/vulndb) ([CC-BY 4.0](https://redirect.github.com/golang/vulndb#license)).
--- ### Invoking agent constraints dropped when forwarding keys in golang.org/x/crypto/ssh/agent [CVE-2026-39832](https://nvd.nist.gov/vuln/detail/CVE-2026-39832) / [GO-2026-5006](https://pkg.go.dev/vuln/GO-2026-5006)
More information #### Details When adding a key to a remote agent constraint extensions such as restrict-destination-v00@​openssh.com were not serialized in the request. Destination restrictions were silently stripped when forwarding keys, allowing unrestricted use of the key on the remote host. The client now serializes all constraint extensions. Additionally, the in-memory keyring returned by NewKeyring() now rejects keys with unsupported constraint extensions instead of silently ignoring them. #### Severity Unknown #### References - [https://go.dev/issue/79435](https://go.dev/issue/79435) - [https://go.dev/cl/778642](https://go.dev/cl/778642) - [https://groups.google.com/g/golang-announce/c/a082jnz-LvI](https://groups.google.com/g/golang-announce/c/a082jnz-LvI) This data is provided by [OSV](https://osv.dev/vulnerability/GO-2026-5006) and the [Go Vulnerability Database](https://redirect.github.com/golang/vulndb) ([CC-BY 4.0](https://redirect.github.com/golang/vulndb#license)).
--- ### Invoking byte arithmetic causes underflow and panic in golang.org/x/crypto/ssh [CVE-2026-46597](https://nvd.nist.gov/vuln/detail/CVE-2026-46597) / [GO-2026-5013](https://pkg.go.dev/vuln/GO-2026-5013)
More information #### Details An incorrectly placed cast from bytes to int allowed for server-side panic in the AES-GCM packet decoder for well-crafted inputs. #### Severity Unknown #### References - [https://go.dev/issue/79561](https://go.dev/issue/79561) - [https://groups.google.com/g/golang-announce/c/a082jnz-LvI](https://groups.google.com/g/golang-announce/c/a082jnz-LvI) - [https://go.dev/cl/781620](https://go.dev/cl/781620) This data is provided by [OSV](https://osv.dev/vulnerability/GO-2026-5013) and the [Go Vulnerability Database](https://redirect.github.com/golang/vulndb) ([CC-BY 4.0](https://redirect.github.com/golang/vulndb#license)).
--- ### Invoking bypass of certificate restrictions in golang.org/x/crypto/ssh [CVE-2026-39828](https://nvd.nist.gov/vuln/detail/CVE-2026-39828) / [GO-2026-5014](https://pkg.go.dev/vuln/GO-2026-5014)
More information #### Details When an SSH server authentication callback returned PartialSuccessError with non-nil Permissions, those permissions were silently discarded, potentially dropping certificate restrictions such as force-command after a second factor succeeded. Returning non-nil Permissions with PartialSuccessError now results in a connection error. #### Severity Unknown #### References - [https://go.dev/issue/79562](https://go.dev/issue/79562) - [https://groups.google.com/g/golang-announce/c/a082jnz-LvI](https://groups.google.com/g/golang-announce/c/a082jnz-LvI) - [https://go.dev/cl/781621](https://go.dev/cl/781621) This data is provided by [OSV](https://osv.dev/vulnerability/GO-2026-5014) and the [Go Vulnerability Database](https://redirect.github.com/golang/vulndb) ([CC-BY 4.0](https://redirect.github.com/golang/vulndb#license)).
--- ### Invoking server panic during CheckHostKey/Authenticate in golang.org/x/crypto/ssh [CVE-2026-39835](https://nvd.nist.gov/vuln/detail/CVE-2026-39835) / [GO-2026-5015](https://pkg.go.dev/vuln/GO-2026-5015)
More information #### Details SSH servers which use CertChecker as a public key callback without setting IsUserAuthority or IsHostAuthority could be caused to panic by a client presenting a certificate. CertChecker now returns an error instead of panicking when these callbacks are nil. #### Severity Unknown #### References - [https://go.dev/issue/79563](https://go.dev/issue/79563) - [https://groups.google.com/g/golang-announce/c/a082jnz-LvI](https://groups.google.com/g/golang-announce/c/a082jnz-LvI) - [https://go.dev/cl/781660](https://go.dev/cl/781660) This data is provided by [OSV](https://osv.dev/vulnerability/GO-2026-5015) and the [Go Vulnerability Database](https://redirect.github.com/golang/vulndb) ([CC-BY 4.0](https://redirect.github.com/golang/vulndb#license)).
--- ### Invoking memory leak when rejecting channels can lead to DoS in golang.org/x/crypto/ssh [CVE-2026-39827](https://nvd.nist.gov/vuln/detail/CVE-2026-39827) / [GO-2026-5016](https://pkg.go.dev/vuln/GO-2026-5016)
More information #### Details An authenticated SSH client that repeatedly opened channels which were rejected by the server caused unbounded memory growth, eventually crashing the server process and affecting all connected users. Rejected channels are now properly removed from the connection's internal state and released for garbage collection. #### Severity Unknown #### References - [https://go.dev/issue/35127](https://go.dev/issue/35127) - [https://go.dev/cl/781320](https://go.dev/cl/781320) - [https://groups.google.com/g/golang-announce/c/a082jnz-LvI](https://groups.google.com/g/golang-announce/c/a082jnz-LvI) This data is provided by [OSV](https://osv.dev/vulnerability/GO-2026-5016) and the [Go Vulnerability Database](https://redirect.github.com/golang/vulndb) ([CC-BY 4.0](https://redirect.github.com/golang/vulndb#license)).
--- ### Invoking client can cause server deadlock on unexpected responses in golang.org/x/crypto/ssh [CVE-2026-39830](https://nvd.nist.gov/vuln/detail/CVE-2026-39830) / [GO-2026-5017](https://pkg.go.dev/vuln/GO-2026-5017)
More information #### Details A malicious SSH peer could send unsolicited global request responses to fill an internal buffer, blocking the connection's read loop. The blocked goroutine could not be released by calling Close(), resulting in a resource leak per connection. Unsolicited global responses are now discarded. #### Severity Unknown #### References - [https://go.dev/issue/79564](https://go.dev/issue/79564) - [https://groups.google.com/g/golang-announce/c/a082jnz-LvI](https://groups.google.com/g/golang-announce/c/a082jnz-LvI) - [https://go.dev/cl/781640](https://go.dev/cl/781640) - [https://go.dev/cl/781664](https://go.dev/cl/781664) This data is provided by [OSV](https://osv.dev/vulnerability/GO-2026-5017) and the [Go Vulnerability Database](https://redirect.github.com/golang/vulndb) ([CC-BY 4.0](https://redirect.github.com/golang/vulndb#license)).
--- ### Invoking pathological RSA/DSA parameters may cause DoS in golang.org/x/crypto/ssh [CVE-2026-39829](https://nvd.nist.gov/vuln/detail/CVE-2026-39829) / [GO-2026-5018](https://pkg.go.dev/vuln/GO-2026-5018)
More information #### Details The RSA and DSA public key parsers did not enforce size limits on key parameters. A crafted public key with an excessively large modulus or DSA parameter could cause several minutes of CPU consumption during signature verification. This could be triggered by unauthenticated clients during public key authentication. RSA moduli are now limited to 8192 bits, and DSA parameters are validated per FIPS 186-2. #### Severity Unknown #### References - [https://go.dev/issue/79565](https://go.dev/issue/79565) - [https://groups.google.com/g/golang-announce/c/a082jnz-LvI](https://groups.google.com/g/golang-announce/c/a082jnz-LvI) - [https://go.dev/cl/781641](https://go.dev/cl/781641) - [https://go.dev/cl/781661](https://go.dev/cl/781661) This data is provided by [OSV](https://osv.dev/vulnerability/GO-2026-5018) and the [Go Vulnerability Database](https://redirect.github.com/golang/vulndb) ([CC-BY 4.0](https://redirect.github.com/golang/vulndb#license)).
--- ### Invoking bypass of FIDO/U2F security keys physical interaction in golang.org/x/crypto/ssh [CVE-2026-39831](https://nvd.nist.gov/vuln/detail/CVE-2026-39831) / [GO-2026-5019](https://pkg.go.dev/vuln/GO-2026-5019)
More information #### Details The Verify() method for FIDO/U2F security key types (sk-ecdsa-sha2-nistp256@​openssh.com, sk-ssh-ed25519@​openssh.com) did not check the User Presence flag. Signatures generated without physical touch were accepted, allowing unattended use of a hardware security key. To restore the previous behavior, return a "no-touch-required" extension in Permissions.Extensions from PublicKeyCallback. #### Severity Unknown #### References - [https://go.dev/issue/79566](https://go.dev/issue/79566) - [https://groups.google.com/g/golang-announce/c/a082jnz-LvI](https://groups.google.com/g/golang-announce/c/a082jnz-LvI) - [https://go.dev/cl/781662](https://go.dev/cl/781662) This data is provided by [OSV](https://osv.dev/vulnerability/GO-2026-5019) and the [Go Vulnerability Database](https://redirect.github.com/golang/vulndb) ([CC-BY 4.0](https://redirect.github.com/golang/vulndb#license)).
--- ### Invoking infinite loop on large channel writes in golang.org/x/crypto/ssh [CVE-2026-39834](https://nvd.nist.gov/vuln/detail/CVE-2026-39834) / [GO-2026-5020](https://pkg.go.dev/vuln/GO-2026-5020)
More information #### Details When writing data larger than 4GB in a single Write call on an SSH channel, an integer overflow in the internal payload size calculation caused the write loop to spin indefinitely, sending empty packets without making progress. The size comparison now uses int64 to prevent truncation. #### Severity Unknown #### References - [https://go.dev/issue/79567](https://go.dev/issue/79567) - [https://groups.google.com/g/golang-announce/c/a082jnz-LvI](https://groups.google.com/g/golang-announce/c/a082jnz-LvI) - [https://go.dev/cl/781663](https://go.dev/cl/781663) This data is provided by [OSV](https://osv.dev/vulnerability/GO-2026-5020) and the [Go Vulnerability Database](https://redirect.github.com/golang/vulndb) ([CC-BY 4.0](https://redirect.github.com/golang/vulndb#license)).
--- ### Invoking auth bypass via unenforced @​revoked status in golang.org/x/crypto/ssh/knownhosts [CVE-2026-42508](https://nvd.nist.gov/vuln/detail/CVE-2026-42508) / [GO-2026-5021](https://pkg.go.dev/vuln/GO-2026-5021)
More information #### Details Previously, a revoked 'SignatureKey' belonging to a CA was not correctly checked for revocation. Now, both the 'key' and 'key.SignatureKey' are checked for @​revoked. #### Severity Unknown #### References - [https://go.dev/issue/79568](https://go.dev/issue/79568) - [https://go.dev/cl/781220](https://go.dev/cl/781220) - [https://groups.google.com/g/golang-announce/c/a082jnz-LvI](https://groups.google.com/g/golang-announce/c/a082jnz-LvI) This data is provided by [OSV](https://osv.dev/vulnerability/GO-2026-5021) and the [Go Vulnerability Database](https://redirect.github.com/golang/vulndb) ([CC-BY 4.0](https://redirect.github.com/golang/vulndb#license)).
--- ### Invoking VerifiedPublicKeyCallback permissions skip enforcement in golang.org/x/crypto/ssh [CVE-2026-46595](https://nvd.nist.gov/vuln/detail/CVE-2026-46595) / [GO-2026-5023](https://pkg.go.dev/vuln/GO-2026-5023)
More information #### Details Previously, CVE-2024-45337 fixed an authorization bypass for misused ssh server configurations; if any other type of callback is passed other than public key, then the source-address validation would be skipped. #### Severity Unknown #### References - [https://go.dev/issue/79570](https://go.dev/issue/79570) - [https://groups.google.com/g/golang-announce/c/a082jnz-LvI](https://groups.google.com/g/golang-announce/c/a082jnz-LvI) - [https://go.dev/cl/781642](https://go.dev/cl/781642) This data is provided by [OSV](https://osv.dev/vulnerability/GO-2026-5023) and the [Go Vulnerability Database](https://redirect.github.com/golang/vulndb) ([CC-BY 4.0](https://redirect.github.com/golang/vulndb#license)).
--- ### Invoking pathological inputs can lead to client panic in golang.org/x/crypto/ssh/agent [CVE-2026-46598](https://nvd.nist.gov/vuln/detail/CVE-2026-46598) / [GO-2026-5033](https://pkg.go.dev/vuln/GO-2026-5033)
More information #### Details For certain crafted inputs, a 'ed25519.PrivateKey' was created by casting malformed wire bytes, leading to a panic when used. #### Severity Unknown #### References - [https://go.dev/issue/79596](https://go.dev/issue/79596) - [https://go.dev/cl/781360](https://go.dev/cl/781360) - [https://groups.google.com/g/golang-announce/c/a082jnz-LvI](https://groups.google.com/g/golang-announce/c/a082jnz-LvI) This data is provided by [OSV](https://osv.dev/vulnerability/GO-2026-5033) and the [Go Vulnerability Database](https://redirect.github.com/golang/vulndb) ([CC-BY 4.0](https://redirect.github.com/golang/vulndb#license)).
--- This PR has been generated by [Mend Renovate](https://redirect.github.com/renovatebot/renovate). --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 76eea6cc7d5..1636ce976fd 100644 --- a/go.mod +++ b/go.mod @@ -109,12 +109,12 @@ require ( github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc gitlab.com/gitlab-org/api/client-go/v2 v2.26.0 go.yaml.in/yaml/v4 v4.0.0-rc.3 - golang.org/x/crypto v0.51.0 + golang.org/x/crypto v0.52.0 golang.org/x/image v0.40.0 golang.org/x/net v0.54.0 golang.org/x/oauth2 v0.36.0 golang.org/x/sync v0.20.0 - golang.org/x/sys v0.44.0 + golang.org/x/sys v0.45.0 golang.org/x/text v0.37.0 google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.11 diff --git a/go.sum b/go.sum index 0510378cb4b..2864a2fec11 100644 --- a/go.sum +++ b/go.sum @@ -790,8 +790,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= -golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8= @@ -870,8 +870,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From bf1b54c3e34df542ea7448f0962009b3951bd48c Mon Sep 17 00:00:00 2001 From: Mohamed Sekour Date: Fri, 22 May 2026 11:53:19 +0200 Subject: [PATCH 02/10] fix(api): handle partial failures in push mirror synchronization gracefully (#37782) This MR fixes an issue in the sync push mirrors endpoint. Previously, when triggering the synchronization of all push mirrors for a specific repository, the entire operation would stop if a single mirror failed for any reason. As a result, the remaining mirrors were not processed. With this fix, failures on individual push mirrors no longer abort the whole synchronization process. --------- Signed-off-by: Nicolas Co-authored-by: Nicolas Co-authored-by: silverwind Co-authored-by: Claude (Opus 4.7) --- routers/api/v1/repo/mirror.go | 13 ++++++-- routers/api/v1/repo/mirror_test.go | 40 +++++++++++++++++++++++++ templates/swagger/v1_json.tmpl | 3 ++ templates/swagger/v1_openapi3_json.tmpl | 3 ++ 4 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 routers/api/v1/repo/mirror_test.go diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go index ac2d8bba06a..bdca030b765 100644 --- a/routers/api/v1/repo/mirror.go +++ b/routers/api/v1/repo/mirror.go @@ -6,6 +6,7 @@ package repo import ( "errors" "net/http" + "strings" "time" "code.gitea.io/gitea/models/db" @@ -101,6 +102,8 @@ func PushMirrorSync(ctx *context.APIContext) { // "$ref": "#/responses/forbidden" // "404": // "$ref": "#/responses/notFound" + // "422": + // "$ref": "#/responses/validationError" if !setting.Mirror.Enabled { ctx.APIError(http.StatusBadRequest, "Mirror feature is disabled") @@ -112,14 +115,18 @@ func PushMirrorSync(ctx *context.APIContext) { ctx.APIError(http.StatusNotFound, err) return } + + failedPushMirrors := make([]string, 0) for _, mirror := range pushMirrors { ok := mirror_service.SyncPushMirror(ctx, mirror.ID) if !ok { - ctx.APIErrorInternal(errors.New("error occurred when syncing push mirror " + mirror.RemoteName)) - return + failedPushMirrors = append(failedPushMirrors, mirror.RemoteName) } } - + if len(failedPushMirrors) != 0 { + ctx.APIError(http.StatusUnprocessableEntity, "error occurred when syncing push mirrors: "+strings.Join(failedPushMirrors, ", ")) + return + } ctx.Status(http.StatusOK) } diff --git a/routers/api/v1/repo/mirror_test.go b/routers/api/v1/repo/mirror_test.go new file mode 100644 index 00000000000..6cbed49d899 --- /dev/null +++ b/routers/api/v1/repo/mirror_test.go @@ -0,0 +1,40 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/services/contexttest" + + "github.com/stretchr/testify/assert" +) + +// TestPushMirrorSync verifies the endpoint attempts every push mirror instead +// of aborting on the first failure, reporting all failed remotes with a 422. +// Each remote name is not a configured git remote, so SyncPushMirror fails fast +// without any network access. +func TestPushMirrorSync(t *testing.T) { + unittest.PrepareTestEnv(t) + defer test.MockVariableValue(&setting.Mirror.Enabled, true)() + + for _, remoteName := range []string{"broken_remote_1", "broken_remote_2"} { + assert.NoError(t, db.Insert(t.Context(), &repo_model.PushMirror{RepoID: 1, RemoteName: remoteName})) + } + + ctx, resp := contexttest.MockAPIContext(t, "user2/repo1") + contexttest.LoadRepo(t, ctx, 1) + + PushMirrorSync(ctx) + + assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus()) + assert.Contains(t, resp.Body.String(), "broken_remote_1") + assert.Contains(t, resp.Body.String(), "broken_remote_2") +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 312eb25fd8d..d95699a48d6 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -15646,6 +15646,9 @@ }, "404": { "$ref": "#/responses/notFound" + }, + "422": { + "$ref": "#/responses/validationError" } } } diff --git a/templates/swagger/v1_openapi3_json.tmpl b/templates/swagger/v1_openapi3_json.tmpl index 04b8bd2d622..642154c5538 100644 --- a/templates/swagger/v1_openapi3_json.tmpl +++ b/templates/swagger/v1_openapi3_json.tmpl @@ -27446,6 +27446,9 @@ }, "404": { "$ref": "#/components/responses/notFound" + }, + "422": { + "$ref": "#/components/responses/validationError" } }, "summary": "Sync all push mirrored repository", From bc9d53a5a89d9e1debf08ee52374c6d4e8cac038 Mon Sep 17 00:00:00 2001 From: Alaa Abdelwahab <82750565+Powerscore@users.noreply.github.com> Date: Fri, 22 May 2026 16:18:32 +0200 Subject: [PATCH 03/10] =?UTF-8?q?fix(issues):=20clear=20stale=20ReviewType?= =?UTF-8?q?Request=20when=20submitting=20pending=20re=E2=80=A6=20(#37809)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When SubmitReview updates an existing pending review in-place, it was not deleting the reviewer's ReviewTypeRequest row, unlike the CreateReview path. That leftover row causes AddReviewRequest to bail out silently, making the re-request icon in the PR sidebar a no-op. Fixes #37808 (Claude Opus 4.7) --------- Co-authored-by: silverwind Co-authored-by: Claude (Opus 4.7) Co-authored-by: Nicolas --- models/issues/review.go | 8 ++++++++ models/issues/review_test.go | 40 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/models/issues/review.go b/models/issues/review.go index 78ef0d20c23..99366222198 100644 --- a/models/issues/review.go +++ b/models/issues/review.go @@ -483,6 +483,14 @@ func SubmitReview(ctx context.Context, doer *user_model.User, issue *Issue, revi if _, err := sess.ID(review.ID).Cols("content, type, official, commit_id, stale").Update(review); err != nil { return nil, nil, err } + + // make sure the leftover review request is cleared, consistent with CreateReview + if reviewType != ReviewTypePending { + if _, err := sess.Where(builder.Eq{"reviewer_id": doer.ID, "issue_id": issue.ID, "type": ReviewTypeRequest}). + Delete(new(Review)); err != nil { + return nil, nil, err + } + } } comm, err := CreateComment(ctx, &CreateCommentOptions{ diff --git a/models/issues/review_test.go b/models/issues/review_test.go index 092d88d1749..a384dbd30fe 100644 --- a/models/issues/review_test.go +++ b/models/issues/review_test.go @@ -303,6 +303,46 @@ func TestDeleteDismissedReview(t *testing.T) { unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: comment.ID}) } +func TestSubmitReviewClearsStaleReviewRequest(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3}) + assert.NoError(t, issue.LoadRepo(t.Context())) + assert.NoError(t, issue.Repo.LoadOwner(t.Context())) + reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + // the reviewer is requested to review the pull request + requestReview, err := issues_model.CreateReview(t.Context(), issues_model.CreateReviewOptions{ + Type: issues_model.ReviewTypeRequest, + Issue: issue, + Reviewer: reviewer, + }) + assert.NoError(t, err) + + // the reviewer starts a pending review (e.g. by adding code comments) + pendingReview, err := issues_model.CreateReview(t.Context(), issues_model.CreateReviewOptions{ + Type: issues_model.ReviewTypePending, + Issue: issue, + Reviewer: reviewer, + }) + assert.NoError(t, err) + + // submitting the pending review must clear the leftover review request, + // otherwise the reviewer can no longer be re-requested afterwards + review, _, err := issues_model.SubmitReview(t.Context(), reviewer, issue, issues_model.ReviewTypeComment, "looks good", "", false, nil) + assert.NoError(t, err) + assert.Equal(t, pendingReview.ID, review.ID) + assert.Equal(t, issues_model.ReviewTypeComment, review.Type) + + unittest.AssertNotExistsBean(t, &issues_model.Review{ID: requestReview.ID}) + + // the reviewer can be re-requested afterwards (no-op before the fix) + comment, err := issues_model.AddReviewRequest(t.Context(), issue, reviewer, doer, false) + assert.NoError(t, err) + assert.NotNil(t, comment) +} + func TestAddReviewRequest(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) From 4c37f4dacbac022f7beca75272439331f0368830 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Fri, 22 May 2026 10:28:54 -0700 Subject: [PATCH 04/10] fix(deps): update module golang.org/x/net to v0.55.0 [security] (#37813) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1636ce976fd..9015b9ec497 100644 --- a/go.mod +++ b/go.mod @@ -111,7 +111,7 @@ require ( go.yaml.in/yaml/v4 v4.0.0-rc.3 golang.org/x/crypto v0.52.0 golang.org/x/image v0.40.0 - golang.org/x/net v0.54.0 + golang.org/x/net v0.55.0 golang.org/x/oauth2 v0.36.0 golang.org/x/sync v0.20.0 golang.org/x/sys v0.45.0 diff --git a/go.sum b/go.sum index 2864a2fec11..9ade1fab9e9 100644 --- a/go.sum +++ b/go.sum @@ -822,8 +822,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= -golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 7c12446c1f14d91ca9118ff1a98a81e9bd9ed684 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 22 May 2026 20:52:04 +0200 Subject: [PATCH 05/10] test(e2e): add comment, release, star, PR and fork tests (#37800) Adds Playwright e2e coverage for five high-value workflows, each driven through semantic locators with API-based setup: - comment on and close an issue - publish a release - star and watch a repository - create a pull request from the compare page - fork a repository Also passes `autoInit: false` in existing tests that only exercise DB-backed units (issues, reactions, milestones, projects, events), skipping an unused initial commit to speed up their setup and reduce parallel git contention. --- This PR was written with the help of Claude Opus 4.7 --------- Co-authored-by: Claude (Opus 4.7) Co-authored-by: Nicolas --- tests/e2e/events.test.ts | 4 ++-- tests/e2e/fork.test.ts | 18 ++++++++++++++++++ tests/e2e/issue-comment.test.ts | 24 ++++++++++++++++++++++++ tests/e2e/issue-project.test.ts | 18 +++++++++--------- tests/e2e/mermaid.test.ts | 2 +- tests/e2e/milestone.test.ts | 2 +- tests/e2e/pr-create.test.ts | 23 +++++++++++++++++++++++ tests/e2e/reactions.test.ts | 2 +- tests/e2e/release.test.ts | 19 +++++++++++++++++++ tests/e2e/repo-star-watch.test.ts | 20 ++++++++++++++++++++ 10 files changed, 118 insertions(+), 14 deletions(-) create mode 100644 tests/e2e/fork.test.ts create mode 100644 tests/e2e/issue-comment.test.ts create mode 100644 tests/e2e/pr-create.test.ts create mode 100644 tests/e2e/release.test.ts create mode 100644 tests/e2e/repo-star-watch.test.ts diff --git a/tests/e2e/events.test.ts b/tests/e2e/events.test.ts index c7cfef6e9c2..54b4774a5cc 100644 --- a/tests/e2e/events.test.ts +++ b/tests/e2e/events.test.ts @@ -12,7 +12,7 @@ test.describe('events', () => { // Create repo and login in parallel — repo is needed for the issue, login for the event stream await Promise.all([ - apiCreateRepo(request, {name: repoName, headers: apiUserHeaders(owner)}), + apiCreateRepo(request, {name: repoName, autoInit: false, headers: apiUserHeaders(owner)}), loginUser(page, owner), ]); await page.goto('/'); @@ -36,7 +36,7 @@ test.describe('events', () => { await Promise.all([ loginUser(page, name), (async () => { - await apiCreateRepo(request, {name, headers}); + await apiCreateRepo(request, {name, autoInit: false, headers}); await apiCreateIssue(request, {owner: name, repo: name, title: 'events stopwatch test', headers}); await apiStartStopwatch(request, name, name, 1, {headers}); })(), diff --git a/tests/e2e/fork.test.ts b/tests/e2e/fork.test.ts new file mode 100644 index 00000000000..77ccda1242e --- /dev/null +++ b/tests/e2e/fork.test.ts @@ -0,0 +1,18 @@ +import {env} from 'node:process'; +import {test, expect} from '@playwright/test'; +import {login, apiCreateRepo, apiCreateUser, apiUserHeaders, randomString} from './utils.ts'; + +test('fork a repository', async ({page, request}) => { + const upstream = `fork-owner-${randomString(8)}`; + const repoName = `e2e-fork-${randomString(8)}`; + await apiCreateUser(request, upstream); + await Promise.all([ + apiCreateRepo(request, {name: repoName, headers: apiUserHeaders(upstream)}), + login(page), + ]); + await page.goto(`/${upstream}/${repoName}/fork`); + + await page.getByRole('button', {name: 'Fork Repository'}).click(); + await page.waitForURL(new RegExp(`/${env.GITEA_TEST_E2E_USER}/${repoName}$`)); + await expect(page.getByRole('link', {name: `${upstream}/${repoName}`})).toBeVisible(); +}); diff --git a/tests/e2e/issue-comment.test.ts b/tests/e2e/issue-comment.test.ts new file mode 100644 index 00000000000..d3de59ba5f6 --- /dev/null +++ b/tests/e2e/issue-comment.test.ts @@ -0,0 +1,24 @@ +import {env} from 'node:process'; +import {test, expect} from '@playwright/test'; +import {login, apiCreateRepo, apiCreateIssue, randomString} from './utils.ts'; + +test('comment on and close an issue', async ({page, request}) => { + const repoName = `e2e-issue-comment-${randomString(8)}`; + const owner = env.GITEA_TEST_E2E_USER; + await apiCreateRepo(request, {name: repoName, autoInit: false}); + await Promise.all([ + apiCreateIssue(request, {owner, repo: repoName, title: 'Comment test'}), + login(page), + ]); + await page.goto(`/${owner}/${repoName}/issues/1`); + + const body = `e2e-comment-${randomString(8)}`; + await page.getByPlaceholder('Leave a comment').fill(body); + // exact match: the status button reads "Close with Comment" while the box has content, which substring-matches "Comment" + await page.getByRole('button', {name: 'Comment', exact: true}).click(); + await expect(page.locator('.comment-body').filter({hasText: body})).toBeVisible(); + + // posting reloaded the page with an empty box, so the status button now reads "Close Issue" + await page.getByRole('button', {name: 'Close Issue'}).click(); + await expect(page.getByRole('button', {name: 'Reopen Issue'})).toBeVisible(); +}); diff --git a/tests/e2e/issue-project.test.ts b/tests/e2e/issue-project.test.ts index b1fed72a6f9..1595cfadc5a 100644 --- a/tests/e2e/issue-project.test.ts +++ b/tests/e2e/issue-project.test.ts @@ -5,7 +5,7 @@ import {login, apiCreateRepo, apiCreateIssue, apiDeleteRepo, createProject, crea test('assign issue to project and change column', async ({page}) => { const repoName = `e2e-issue-project-${randomString(8)}`; const user = env.GITEA_TEST_E2E_USER; - await Promise.all([login(page), apiCreateRepo(page.request, {name: repoName})]); + await Promise.all([login(page), apiCreateRepo(page.request, {name: repoName, autoInit: false})]); await page.goto(`/${user}/${repoName}/projects/new`); await page.locator('input[name="title"]').fill('Kanban Board'); await page.getByRole('button', {name: 'Create Project'}).click(); @@ -33,7 +33,7 @@ test('create a project', async ({page}) => { const projectTitle = 'Test Project'; await login(page); - await apiCreateRepo(page.request, {name: repoName}); + await apiCreateRepo(page.request, {name: repoName, autoInit: false}); try { // Navigate to new project page @@ -62,7 +62,7 @@ test('assign issue to multiple projects via sidebar', async ({page}) => { const issueTitle = 'Test issue for multiple projects'; await login(page); - await apiCreateRepo(page.request, {name: repoName}); + await apiCreateRepo(page.request, {name: repoName, autoInit: false}); try { // Create two projects via UI @@ -112,7 +112,7 @@ test('create issue with multiple projects pre-selected', async ({page}) => { const issueTitle = 'Issue with multiple projects'; await login(page); - await apiCreateRepo(page.request, {name: repoName}); + await apiCreateRepo(page.request, {name: repoName, autoInit: false}); try { // Create two projects via UI @@ -163,7 +163,7 @@ test('filter issues by multiple projects in issue list', async ({page}) => { const project2Title = 'Filter Project B'; await login(page); - await apiCreateRepo(page.request, {name: repoName}); + await apiCreateRepo(page.request, {name: repoName, autoInit: false}); try { // Create two projects via UI @@ -229,7 +229,7 @@ test('remove issue from one project keeping others', async ({page}) => { const issueTitle = 'Issue to modify projects'; await login(page); - await apiCreateRepo(page.request, {name: repoName}); + await apiCreateRepo(page.request, {name: repoName, autoInit: false}); try { // Create two projects via UI @@ -288,7 +288,7 @@ test('filter issues with no project using project=-1', async ({page}) => { const projectTitle = 'Some Project'; await login(page); - await apiCreateRepo(page.request, {name: repoName}); + await apiCreateRepo(page.request, {name: repoName, autoInit: false}); try { // Create a project via UI @@ -349,7 +349,7 @@ test('close project and view in closed projects list', async ({page}) => { const closedProjectTitle = 'Project To Close'; await login(page); - await apiCreateRepo(page.request, {name: repoName}); + await apiCreateRepo(page.request, {name: repoName, autoInit: false}); try { // Create two projects via UI @@ -404,7 +404,7 @@ test('select projects on new issue page shows in sidebar', async ({page}) => { const project2Title = 'Project Two'; await login(page); - await apiCreateRepo(page.request, {name: repoName}); + await apiCreateRepo(page.request, {name: repoName, autoInit: false}); try { // Create two projects diff --git a/tests/e2e/mermaid.test.ts b/tests/e2e/mermaid.test.ts index b2e066e5510..1523577eaa7 100644 --- a/tests/e2e/mermaid.test.ts +++ b/tests/e2e/mermaid.test.ts @@ -5,7 +5,7 @@ import {apiCreateRepo, apiCreateIssue, assertNoJsError, randomString} from './ut test('mermaid diagram in issue', async ({page, request}) => { const repoName = `e2e-mermaid-${randomString(8)}`; const owner = env.GITEA_TEST_E2E_USER; - await apiCreateRepo(request, {name: repoName}); + await apiCreateRepo(request, {name: repoName, autoInit: false}); const body = '```mermaid\nflowchart LR\n Alpha --> Beta\n Beta --> Gamma\n```\n'; const {index} = await apiCreateIssue(request, {owner, repo: repoName, title: 'mermaid test', body}); await page.goto(`/${owner}/${repoName}/issues/${index}`); diff --git a/tests/e2e/milestone.test.ts b/tests/e2e/milestone.test.ts index 5a688fb1282..106381e64f0 100644 --- a/tests/e2e/milestone.test.ts +++ b/tests/e2e/milestone.test.ts @@ -4,7 +4,7 @@ import {login, apiCreateRepo, randomString} from './utils.ts'; test('create a milestone', async ({page}) => { const repoName = `e2e-milestone-${randomString(8)}`; - await Promise.all([login(page), apiCreateRepo(page.request, {name: repoName})]); + await Promise.all([login(page), apiCreateRepo(page.request, {name: repoName, autoInit: false})]); await page.goto(`/${env.GITEA_TEST_E2E_USER}/${repoName}/milestones/new`); await page.getByPlaceholder('Title').fill('Test Milestone'); await page.getByRole('button', {name: 'Create Milestone'}).click(); diff --git a/tests/e2e/pr-create.test.ts b/tests/e2e/pr-create.test.ts new file mode 100644 index 00000000000..592512683fc --- /dev/null +++ b/tests/e2e/pr-create.test.ts @@ -0,0 +1,23 @@ +import {env} from 'node:process'; +import {test, expect} from '@playwright/test'; +import {login, apiCreateRepo, apiCreateFile, randomString} from './utils.ts'; + +test('create a pull request from the compare page', async ({page, request}) => { + const repoName = `e2e-pr-create-${randomString(8)}`; + const owner = env.GITEA_TEST_E2E_USER; + await apiCreateRepo(request, {name: repoName}); + await Promise.all([ + apiCreateFile(request, owner, repoName, 'feat.txt', 'feature content\n', {branch: 'main', newBranch: 'feat'}), + login(page), + ]); + // expand=1 renders the PR form directly, skipping the "New Pull Request" toggle click + await page.goto(`/${owner}/${repoName}/compare/main...feat?expand=1`); + + const title = `e2e-pr-${randomString(8)}`; + await page.getByPlaceholder('Title').fill(title); + await page.getByRole('button', {name: 'Create Pull Request'}).click(); + + // commit, not full load: the PR title heading is server-rendered, so the assertion can resolve before the heavy diff/timeline finishes + await page.waitForURL(new RegExp(`/${owner}/${repoName}/pulls/\\d+$`), {waitUntil: 'commit'}); + await expect(page.getByRole('heading', {name: title})).toBeVisible(); +}); diff --git a/tests/e2e/reactions.test.ts b/tests/e2e/reactions.test.ts index 2048b938cf2..a6b61837416 100644 --- a/tests/e2e/reactions.test.ts +++ b/tests/e2e/reactions.test.ts @@ -5,7 +5,7 @@ import {login, apiCreateRepo, apiCreateIssue, randomString} from './utils.ts'; test('toggle issue reactions', async ({page, request}) => { const repoName = `e2e-reactions-${randomString(8)}`; const owner = env.GITEA_TEST_E2E_USER; - await apiCreateRepo(request, {name: repoName}); + await apiCreateRepo(request, {name: repoName, autoInit: false}); await Promise.all([ apiCreateIssue(request, {owner, repo: repoName, title: 'Reaction test'}), login(page), diff --git a/tests/e2e/release.test.ts b/tests/e2e/release.test.ts new file mode 100644 index 00000000000..219a16d2c5f --- /dev/null +++ b/tests/e2e/release.test.ts @@ -0,0 +1,19 @@ +import {env} from 'node:process'; +import {test, expect} from '@playwright/test'; +import {login, apiCreateRepo, randomString} from './utils.ts'; + +test('create a release', async ({page, request}) => { + const repoName = `e2e-release-${randomString(8)}`; + const owner = env.GITEA_TEST_E2E_USER; + await Promise.all([apiCreateRepo(request, {name: repoName}), login(page)]); + await page.goto(`/${owner}/${repoName}/releases/new`); + + const tag = `v1.0.0-${randomString(8)}`; + const title = `e2e-release-${randomString(8)}`; + await page.getByLabel('Tag name').fill(tag); + await page.getByLabel('Release title').fill(title); + await page.getByRole('button', {name: 'Publish Release'}).click(); + + await page.waitForURL(new RegExp(`/${owner}/${repoName}/releases$`)); + await expect(page.locator('.release-list-title')).toContainText(title); +}); diff --git a/tests/e2e/repo-star-watch.test.ts b/tests/e2e/repo-star-watch.test.ts new file mode 100644 index 00000000000..d5f84491651 --- /dev/null +++ b/tests/e2e/repo-star-watch.test.ts @@ -0,0 +1,20 @@ +import {test, expect} from '@playwright/test'; +import {login, apiCreateRepo, apiCreateUser, apiUserHeaders, randomString} from './utils.ts'; + +test('star and watch a repository', async ({page, request}) => { + const owner = `sw-owner-${randomString(8)}`; + const repoName = `e2e-star-watch-${randomString(8)}`; + await apiCreateUser(request, owner); + await Promise.all([ + apiCreateRepo(request, {name: repoName, autoInit: false, headers: apiUserHeaders(owner)}), + login(page), + ]); + await page.goto(`/${owner}/${repoName}`); + + // exact match so "Star"/"Watch" don't also match "Unstar"/"Unwatch" + await page.getByRole('button', {name: 'Star', exact: true}).click(); + await expect(page.getByRole('button', {name: 'Unstar'})).toBeVisible(); + + await page.getByRole('button', {name: 'Watch', exact: true}).click(); + await expect(page.getByRole('button', {name: 'Unwatch'})).toBeVisible(); +}); From f95c210abe7333808fc7b140d91000b0b33efaf4 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 22 May 2026 21:07:35 +0200 Subject: [PATCH 06/10] ci: split pgsql shards into plain jobs, dedupe setup actions (#37802) 1. Split the psql matrix jobs into composite actions. Matrix jobs that can skip do not work with required checks on GitHub because skipped and unskipped emit different job names (GitHub bug https://github.com/orgs/community/discussions/9141). 2. Dedupe node and go setup steps into composite actions Currently test-psql branch protection is disabled, will re-enable when merging this. --------- Signed-off-by: silverwind Co-authored-by: Claude (Opus 4.7) Co-authored-by: Nicolas --- .github/actions/go-setup/action.yml | 23 +++++++ .github/actions/node-setup/action.yml | 22 ++++++ .github/actions/pgsql-shard/action.yml | 40 +++++++++++ .github/workflows/cache-seeder.yml | 15 +--- .github/workflows/files-changed.yml | 1 + .github/workflows/pull-compliance.yml | 39 +++-------- .github/workflows/pull-db-tests.yml | 95 ++++++++++++-------------- .github/workflows/pull-e2e-tests.yml | 14 +--- 8 files changed, 143 insertions(+), 106 deletions(-) create mode 100644 .github/actions/go-setup/action.yml create mode 100644 .github/actions/node-setup/action.yml create mode 100644 .github/actions/pgsql-shard/action.yml diff --git a/.github/actions/go-setup/action.yml b/.github/actions/go-setup/action.yml new file mode 100644 index 00000000000..d6f88bbc6da --- /dev/null +++ b/.github/actions/go-setup/action.yml @@ -0,0 +1,23 @@ +name: go-setup +description: Set up go and restore caches + +inputs: + cache: + description: Restore go caches + default: "true" + lint-cache: + description: Also restore the golangci-lint cache + default: "false" + +runs: + using: composite + steps: + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: go.mod + check-latest: true + cache: false + - if: ${{ inputs.cache == 'true' }} + uses: ./.github/actions/go-cache + with: + lint-cache: ${{ inputs.lint-cache }} diff --git a/.github/actions/node-setup/action.yml b/.github/actions/node-setup/action.yml new file mode 100644 index 00000000000..c9ab484d57e --- /dev/null +++ b/.github/actions/node-setup/action.yml @@ -0,0 +1,22 @@ +name: node-setup +description: Set up pnpm and node and restore caches + +inputs: + cache: + description: Cache pnpm downloads + default: "true" + +runs: + using: composite + steps: + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + - if: ${{ inputs.cache == 'true' }} + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24 + cache: pnpm + cache-dependency-path: pnpm-lock.yaml + - if: ${{ inputs.cache != 'true' }} + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 24 diff --git a/.github/actions/pgsql-shard/action.yml b/.github/actions/pgsql-shard/action.yml new file mode 100644 index 00000000000..9a5a03ae0de --- /dev/null +++ b/.github/actions/pgsql-shard/action.yml @@ -0,0 +1,40 @@ +name: pgsql-shard +description: Run one pgsql integration test shard + +inputs: + shard: + description: Shard index + required: true + total-shards: + description: Total shard count + required: true + run-migration: + description: Also run migration tests + default: "false" + +runs: + using: composite + steps: + - name: Add hosts to /etc/hosts + shell: bash + run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 pgsql ldap minio" | sudo tee -a /etc/hosts' + - shell: bash + run: make deps-backend + - shell: bash + run: make backend + env: + TAGS: bindata + - name: run migration tests + if: ${{ inputs.run-migration == 'true' }} + shell: bash + run: GITEA_TEST_DATABASE=pgsql make test-migration + - name: run tests + shell: bash + run: GITEA_TEST_DATABASE=pgsql make test-integration + env: + # pgsql is chosen to be the unlucky one to run with the slow "race detector", it is about 60% slower. + GOTEST_FLAGS: -race -timeout=40m + TAGS: bindata gogit + TEST_LDAP: 1 + TEST_SHARD: ${{ inputs.shard }} + TEST_TOTAL_SHARDS: ${{ inputs.total-shards }} diff --git a/.github/workflows/cache-seeder.yml b/.github/workflows/cache-seeder.yml index 91109b1c06d..8ec7adee07d 100644 --- a/.github/workflows/cache-seeder.yml +++ b/.github/workflows/cache-seeder.yml @@ -15,6 +15,7 @@ on: - "go.sum" - ".golangci.yml" - ".github/actions/go-cache/action.yml" + - ".github/actions/go-setup/action.yml" - ".github/workflows/cache-seeder.yml" concurrency: @@ -29,12 +30,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: go.mod - check-latest: true - cache: false - - uses: ./.github/actions/go-cache + - uses: ./.github/actions/go-setup - run: make deps-backend deps-tools - run: TAGS="bindata" make backend - run: TAGS="bindata gogit" GOEXPERIMENT="" make backend @@ -64,12 +60,7 @@ jobs: - { tags: "bindata", target: "lint-backend" } steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: go.mod - check-latest: true - cache: false - - uses: ./.github/actions/go-cache + - uses: ./.github/actions/go-setup with: lint-cache: "true" - run: make deps-backend deps-tools diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml index ac2bd1a5ba5..2975a0cfcbf 100644 --- a/.github/workflows/files-changed.yml +++ b/.github/workflows/files-changed.yml @@ -81,6 +81,7 @@ jobs: actions: - ".github/workflows/*" + - ".github/actions/**" - "Makefile" templates: diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index ada537a188c..9ccfc76315e 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -20,12 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: go.mod - check-latest: true - cache: false - - uses: ./.github/actions/go-cache + - uses: ./.github/actions/go-setup with: lint-cache: "true" - run: make deps-backend deps-tools @@ -37,15 +32,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + - uses: ./.github/actions/go-setup with: - go-version-file: go.mod - check-latest: true - cache: false - - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + cache: "false" + - uses: ./.github/actions/node-setup with: - node-version: 24 + cache: "false" - run: make lint-spell @@ -68,12 +60,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: go.mod - check-latest: true - cache: false - - uses: ./.github/actions/go-cache + - uses: ./.github/actions/go-setup - run: make deps-backend deps-tools - run: make --always-make checks-backend # ensure the "go-licenses" make target runs @@ -83,12 +70,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: 24 - cache: pnpm - cache-dependency-path: pnpm-lock.yaml + - uses: ./.github/actions/node-setup - run: make deps-frontend - run: make lint-frontend - run: make checks-frontend @@ -101,12 +83,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: go.mod - check-latest: true - cache: false - - uses: ./.github/actions/go-cache + - uses: ./.github/actions/go-setup - run: make deps-backend generate-go # no frontend build here as backend should be able to build, even without any frontend files # CGO is not used when cross-compile, so these steps also test if the code is compatible with CGO disabled diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml index bc73d6391ca..e4de01e711a 100644 --- a/.github/workflows/pull-db-tests.yml +++ b/.github/workflows/pull-db-tests.yml @@ -14,14 +14,11 @@ jobs: files-changed: uses: ./.github/workflows/files-changed.yml - test-pgsql-shards: + test-pgsql-shard-1: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - shard: [1, 2] + timeout-minutes: 50 services: pgsql: image: postgres:14 @@ -46,31 +43,47 @@ jobs: - "9000:9000" steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + - uses: ./.github/actions/go-setup + - uses: ./.github/actions/pgsql-shard with: - go-version-file: go.mod - check-latest: true - cache: false - - uses: ./.github/actions/go-cache - - name: Add hosts to /etc/hosts - run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 pgsql ldap minio" | sudo tee -a /etc/hosts' - - run: make deps-backend - - run: make backend + shard: 1 + total-shards: 2 + run-migration: "true" + + test-pgsql-shard-2: + if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' + needs: files-changed + runs-on: ubuntu-latest + timeout-minutes: 50 + services: + pgsql: + image: postgres:14 env: - TAGS: bindata - - name: run migration tests - if: matrix.shard == 1 - run: GITEA_TEST_DATABASE=pgsql make test-migration - - name: run tests - run: GITEA_TEST_DATABASE=pgsql make test-integration - timeout-minutes: 50 + POSTGRES_DB: test + POSTGRES_PASSWORD: postgres + ports: + - "5432:5432" + ldap: + image: gitea/test-openldap:latest@sha256:4ac633b01d684e6b2a458cc0c8530c92f9b3702f6e040ce5f365607df34fbda0 + ports: + - "389:389" + - "636:636" + minio: + # as github actions doesn't support "entrypoint", we need to use a non-official image + # that has a custom entrypoint set to "minio server /data" + image: bitnamilegacy/minio:2025.7.23 env: - # pgsql is chosen to be the unlucky one to run with the slow "race detector", it is about 60% slower. - GOTEST_FLAGS: -race -timeout=40m - TAGS: bindata gogit - TEST_LDAP: 1 - TEST_SHARD: ${{ matrix.shard }} - TEST_TOTAL_SHARDS: ${{ strategy.job-total }} + MINIO_ROOT_USER: 123456 + MINIO_ROOT_PASSWORD: 12345678 + ports: + - "9000:9000" + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: ./.github/actions/go-setup + - uses: ./.github/actions/pgsql-shard + with: + shard: 2 + total-shards: 2 test-sqlite: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' @@ -78,12 +91,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: go.mod - check-latest: true - cache: false - - uses: ./.github/actions/go-cache + - uses: ./.github/actions/go-setup - run: make deps-backend - run: make backend env: @@ -141,12 +149,7 @@ jobs: - 10000:10000 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: go.mod - check-latest: true - cache: false - - uses: ./.github/actions/go-cache + - uses: ./.github/actions/go-setup - name: Add hosts to /etc/hosts run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 minio devstoreaccount1.azurite.local mysql elasticsearch meilisearch smtpimap" | sudo tee -a /etc/hosts' - run: make deps-backend @@ -200,12 +203,7 @@ jobs: - "993:993" steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: go.mod - check-latest: true - cache: false - - uses: ./.github/actions/go-cache + - uses: ./.github/actions/go-setup - name: Add hosts to /etc/hosts run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql elasticsearch smtpimap" | sudo tee -a /etc/hosts' - run: make deps-backend @@ -238,12 +236,7 @@ jobs: - 10000:10000 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: go.mod - check-latest: true - cache: false - - uses: ./.github/actions/go-cache + - uses: ./.github/actions/go-setup - name: Add hosts to /etc/hosts run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mssql devstoreaccount1.azurite.local" | sudo tee -a /etc/hosts' - run: make deps-backend diff --git a/.github/workflows/pull-e2e-tests.yml b/.github/workflows/pull-e2e-tests.yml index 7bf73d4a0f9..f0283f40227 100644 --- a/.github/workflows/pull-e2e-tests.yml +++ b/.github/workflows/pull-e2e-tests.yml @@ -20,18 +20,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 - with: - go-version-file: go.mod - check-latest: true - cache: false - - uses: ./.github/actions/go-cache - - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: 24 - cache: pnpm - cache-dependency-path: pnpm-lock.yaml + - uses: ./.github/actions/go-setup + - uses: ./.github/actions/node-setup - run: make deps-frontend - run: make frontend - run: make deps-backend From 12fcd78e8e1b16c580e0b3a26ba2fb663b47af2f Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Fri, 22 May 2026 15:27:09 -0400 Subject: [PATCH 07/10] build(snap): publish nightly version to snapcraft via actions (#37814) The secret is added to the repo already. Right now this only publishes commits to main branch to the "latest/edge" snap channel, but if this is successful we can add more workflows/logic to be able to publish RCs/fully tagged versions too. --------- Co-authored-by: Nicolas --- .../workflows/release-nightly-snapcraft.yml | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/release-nightly-snapcraft.yml diff --git a/.github/workflows/release-nightly-snapcraft.yml b/.github/workflows/release-nightly-snapcraft.yml new file mode 100644 index 00000000000..0f9ac1d423b --- /dev/null +++ b/.github/workflows/release-nightly-snapcraft.yml @@ -0,0 +1,41 @@ +name: release-nightly-snapcraft + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +jobs: + build-and-publish: + runs-on: ubuntu-latest + + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install snapcraft + run: sudo snap install snapcraft --classic + + - name: Remote build + run: | + snapcraft remote-build \ + --launchpad-accept-public-upload \ + --build-for=amd64,arm64,armhf + + - name: List built snaps + run: find . -maxdepth 1 -type f -name '*.snap' -print + + - name: Upload and release snapcraft nightly build + run: | + set -euo pipefail + + for snap in ./*.snap; do + echo "Uploading $snap to edge" + snapcraft upload --release="latest/edge" "$snap" + done From f0eb065df7597f4364124f8b62b79b4741d159c4 Mon Sep 17 00:00:00 2001 From: metiftikci Date: Fri, 22 May 2026 22:44:48 +0300 Subject: [PATCH 08/10] enhance(ui): add gap between file tree and diff on pr changes page (#37798) ### Before file-tree-before ### After file-tree-after Co-authored-by: Lunny Xiao --- web_src/css/repo.css | 1 + 1 file changed, 1 insertion(+) diff --git a/web_src/css/repo.css b/web_src/css/repo.css index f3b169aeb91..b47e03b8ce4 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -1578,6 +1578,7 @@ tbody.commit-list { #diff-container { display: flex; + gap: var(--page-spacing); } #diff-file-boxes { From 570173b4093409e461f53f1f021310a5aeb32a15 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 22 May 2026 22:22:04 +0200 Subject: [PATCH 09/10] ci: add shellcheck linter (#37682) - Adds `make lint-shell`. uses local `shellcheck` if its version matches, otherwise runs the pinned image via docker or podman - `.shellcheckrc` disables the most annoying rules - Fixes findings across existing scripts Fixes: #37648 Signed-off-by: silverwind Co-authored-by: Claude (Opus 4.7) Co-authored-by: TheFox0x7 Co-authored-by: Nicolas --- .github/workflows/files-changed.yml | 7 ++++++ .github/workflows/pull-compliance.yml | 3 +++ .shellcheckrc | 1 + Makefile | 13 +++++++--- contrib/upgrade.sh | 1 + .../usr/local/bin/docker-entrypoint.sh | 2 +- docker/rootless/usr/local/bin/docker-setup.sh | 24 +++++++++---------- renovate.json5 | 1 + snap/part-gitea-build.sh | 2 +- snap/part-gitea-pull.sh | 2 +- tools/lint-shell.sh | 11 +++++++++ tools/test-e2e.sh | 2 ++ 12 files changed, 51 insertions(+), 18 deletions(-) create mode 100644 .shellcheckrc create mode 100755 tools/lint-shell.sh diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml index 2975a0cfcbf..a7b116fec79 100644 --- a/.github/workflows/files-changed.yml +++ b/.github/workflows/files-changed.yml @@ -25,6 +25,8 @@ on: value: ${{ jobs.detect.outputs.json }} e2e: value: ${{ jobs.detect.outputs.e2e }} + shell: + value: ${{ jobs.detect.outputs.shell }} permissions: contents: read @@ -45,6 +47,7 @@ jobs: yaml: ${{ steps.changes.outputs.yaml }} json: ${{ steps.changes.outputs.json }} e2e: ${{ steps.changes.outputs.e2e }} + shell: ${{ steps.changes.outputs.shell }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 @@ -124,3 +127,7 @@ jobs: - "tests/e2e/**" - "tools/test-e2e.sh" - "playwright.config.ts" + + shell: + - "**/*.sh" + - ".shellcheckrc" diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index 9ccfc76315e..801966e1444 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -54,6 +54,9 @@ jobs: - if: needs.files-changed.outputs.actions == 'true' run: make lint-actions + - if: needs.files-changed.outputs.shell == 'true' + run: make lint-shell + checks-backend: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 00000000000..a012fba96e5 --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1 @@ +disable=SC1091,SC2001,SC2002,SC2016,SC2028,SC2046,SC2124,SC2128,SC2129,SC2154,SC2155,SC2164,SC2181,SC2207 diff --git a/Makefile b/Makefile index 94f07a44125..52a266fa39b 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,9 @@ SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.2 # renova XGO_PACKAGE ?= src.techknowlogick.com/xgo@v1.9.0 # renovate: datasource=go GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.3.0 # renovate: datasource=go ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 # renovate: datasource=go +SHELLCHECK_IMAGE ?= docker.io/koalaman/shellcheck:v0.11.0@sha256:61862eba1fcf09a484ebcc6feea46f1782532571a34ed51fedf90dd25f925a8d # renovate: datasource=docker + +CONTAINER_RUNTIME ?= $(shell hash docker >/dev/null 2>&1 && echo docker || echo podman) HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes) ifeq ($(HAS_GO), yes) @@ -271,7 +274,7 @@ checks-frontend: lockfile-check svg-check ## check frontend files checks-backend: tidy-check swagger-check openapi3-check fmt-check swagger-validate security-check ## check backend files .PHONY: lint -lint: lint-frontend lint-backend lint-templates lint-swagger lint-spell lint-md lint-actions lint-json lint-yaml ## lint everything +lint: lint-frontend lint-backend lint-templates lint-swagger lint-spell lint-md lint-actions lint-json lint-yaml lint-shell ## lint everything .PHONY: lint-fix lint-fix: lint-frontend-fix lint-backend-fix lint-spell-fix ## lint everything and fix issues @@ -348,6 +351,10 @@ lint-actions: .venv ## lint action workflow files @$(GO) run $(ACTIONLINT_PACKAGE) @uv run --frozen zizmor --quiet --min-confidence=medium .github +.PHONY: lint-shell +lint-shell: ## lint shell scripts + @SHELLCHECK_IMAGE=$(SHELLCHECK_IMAGE) CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) ./tools/lint-shell.sh $$(git ls-files '*.sh') + .PHONY: lint-templates lint-templates: .venv node_modules ## lint template files @node tools/lint-templates-svg.ts @@ -473,11 +480,11 @@ migrations.individual.test\#%: .PHONY: playwright playwright: deps-frontend - @./tools/test-e2e.sh install + @CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) ./tools/test-e2e.sh install .PHONY: test-e2e test-e2e: playwright frontend backend - @EXECUTABLE=$(EXECUTABLE) ./tools/test-e2e.sh run $(GITEA_TEST_E2E_FLAGS) + @CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) EXECUTABLE=$(EXECUTABLE) ./tools/test-e2e.sh run $(GITEA_TEST_E2E_FLAGS) .PHONY: build build: frontend backend ## build everything diff --git a/contrib/upgrade.sh b/contrib/upgrade.sh index 2593d245093..71d0435d146 100755 --- a/contrib/upgrade.sh +++ b/contrib/upgrade.sh @@ -126,6 +126,7 @@ giteacmd manager flush-queues echo "Stopping gitea at $(date)" $service_stop echo "Creating backup in $giteahome" +# shellcheck disable=SC2086 # flag string giteacmd dump $backupopts echo "Updating binary at $giteabin" cp -f "$giteabin" "$giteabin.bak" && mv -f "$binname" "$giteabin" diff --git a/docker/rootless/usr/local/bin/docker-entrypoint.sh b/docker/rootless/usr/local/bin/docker-entrypoint.sh index ca509214bff..4ffc4ff3a11 100755 --- a/docker/rootless/usr/local/bin/docker-entrypoint.sh +++ b/docker/rootless/usr/local/bin/docker-entrypoint.sh @@ -13,5 +13,5 @@ fi if [ $# -gt 0 ]; then exec "$@" else - exec /usr/local/bin/gitea -c ${GITEA_APP_INI} web + exec /usr/local/bin/gitea -c "${GITEA_APP_INI}" web fi diff --git a/docker/rootless/usr/local/bin/docker-setup.sh b/docker/rootless/usr/local/bin/docker-setup.sh index feab02a3793..3320d69dde8 100755 --- a/docker/rootless/usr/local/bin/docker-setup.sh +++ b/docker/rootless/usr/local/bin/docker-setup.sh @@ -1,23 +1,23 @@ #!/bin/bash # Prepare git folder -mkdir -p ${HOME} && chmod 0700 ${HOME} -if [ ! -w ${HOME} ]; then echo "${HOME} is not writable"; exit 1; fi +mkdir -p "${HOME}" && chmod 0700 "${HOME}" +if [ ! -w "${HOME}" ]; then echo "${HOME} is not writable"; exit 1; fi # Prepare custom folder -mkdir -p ${GITEA_CUSTOM} && chmod 0700 ${GITEA_CUSTOM} +mkdir -p "${GITEA_CUSTOM}" && chmod 0700 "${GITEA_CUSTOM}" # Prepare temp folder -mkdir -p ${GITEA_TEMP} && chmod 0700 ${GITEA_TEMP} -if [ ! -w ${GITEA_TEMP} ]; then echo "${GITEA_TEMP} is not writable"; exit 1; fi +mkdir -p "${GITEA_TEMP}" && chmod 0700 "${GITEA_TEMP}" +if [ ! -w "${GITEA_TEMP}" ]; then echo "${GITEA_TEMP} is not writable"; exit 1; fi #Prepare config file -if [ ! -f ${GITEA_APP_INI} ]; then +if [ ! -f "${GITEA_APP_INI}" ]; then #Prepare config file folder - GITEA_APP_INI_DIR=$(dirname ${GITEA_APP_INI}) - mkdir -p ${GITEA_APP_INI_DIR} && chmod 0700 ${GITEA_APP_INI_DIR} - if [ ! -w ${GITEA_APP_INI_DIR} ]; then echo "${GITEA_APP_INI_DIR} is not writable"; exit 1; fi + GITEA_APP_INI_DIR=$(dirname "${GITEA_APP_INI}") + mkdir -p "${GITEA_APP_INI_DIR}" && chmod 0700 "${GITEA_APP_INI_DIR}" + if [ ! -w "${GITEA_APP_INI_DIR}" ]; then echo "${GITEA_APP_INI_DIR} is not writable"; exit 1; fi # Set INSTALL_LOCK to true only if SECRET_KEY is not empty and # INSTALL_LOCK is empty @@ -34,7 +34,7 @@ if [ ! -f ${GITEA_APP_INI} ]; then ROOT_URL=${ROOT_URL:-""} \ DISABLE_SSH=${DISABLE_SSH:-"false"} \ SSH_PORT=${SSH_PORT:-"2222"} \ - SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-$SSH_PORT} \ + SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-} \ DB_TYPE=${DB_TYPE:-"sqlite3"} \ DB_HOST=${DB_HOST:-"localhost:3306"} \ DB_NAME=${DB_NAME:-"gitea"} \ @@ -44,8 +44,8 @@ if [ ! -f ${GITEA_APP_INI} ]; then DISABLE_REGISTRATION=${DISABLE_REGISTRATION:-"false"} \ REQUIRE_SIGNIN_VIEW=${REQUIRE_SIGNIN_VIEW:-"false"} \ SECRET_KEY=${SECRET_KEY:-""} \ - envsubst < /etc/templates/app.ini > ${GITEA_APP_INI} + envsubst < /etc/templates/app.ini > "${GITEA_APP_INI}" fi # Replace app.ini settings with env variables in the form GITEA__SECTION_NAME__KEY_NAME -environment-to-ini --config ${GITEA_APP_INI} +environment-to-ini --config "${GITEA_APP_INI}" diff --git a/renovate.json5 b/renovate.json5 index c5d712dc0ab..bf913011fbe 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -18,6 +18,7 @@ "managerFilePatterns": ["/(^|/)Makefile$/"], "matchStrings": [ "[A-Z_]+_PACKAGE\\s*\\?=\\s*(?[^@\\s]+?)(?:/cmd/[^@/\\s]+)?@(?\\S+)\\s+# renovate: datasource=(?\\S+)", + "[A-Z_]+_IMAGE\\s*\\?=\\s*(?[^:\\s]+):(?[^@\\s]+)@(?sha256:[a-f0-9]+)\\s+# renovate: datasource=(?\\S+)", ], }, ], diff --git a/snap/part-gitea-build.sh b/snap/part-gitea-build.sh index 8388c16e06e..005162488fa 100755 --- a/snap/part-gitea-build.sh +++ b/snap/part-gitea-build.sh @@ -1,7 +1,7 @@ #!/bin/sh set -e -if [ ! -f go.mod -o ! -d snap ]; then +if [ ! -f go.mod ] || [ ! -d snap ]; then echo "This script should be run from the root of the gitea repository" exit 1 fi diff --git a/snap/part-gitea-pull.sh b/snap/part-gitea-pull.sh index 0d117bb4d1d..83fde11a129 100755 --- a/snap/part-gitea-pull.sh +++ b/snap/part-gitea-pull.sh @@ -1,7 +1,7 @@ #!/bin/sh set -e -if [ ! -f go.mod -o ! -d snap ]; then +if [ ! -f go.mod ] || [ ! -d snap ]; then echo "This script should be run from the root of the gitea repository" exit 1 fi diff --git a/tools/lint-shell.sh b/tools/lint-shell.sh new file mode 100755 index 00000000000..dac838e8338 --- /dev/null +++ b/tools/lint-shell.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -euo pipefail + +CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-docker}" +VERSION=$(echo "$SHELLCHECK_IMAGE" | sed -E 's/.*:v([0-9.]+)@.*/\1/') + +if hash shellcheck 2>/dev/null && shellcheck --version | grep -qx "version: $VERSION"; then + exec shellcheck --color=always "$@" +else + exec "$CONTAINER_RUNTIME" run --rm -v "$PWD":/mnt -w /mnt "$SHELLCHECK_IMAGE" --color=always "$@" +fi diff --git a/tools/test-e2e.sh b/tools/test-e2e.sh index d6c053ff1f5..be3e848ee1d 100755 --- a/tools/test-e2e.sh +++ b/tools/test-e2e.sh @@ -71,8 +71,10 @@ if [ "$CMD" = "install" ]; then if [ "$PLAYWRIGHT_MODE" = "local" ]; then # on GitHub Actions VMs, playwright's system deps are pre-installed if [ -z "${GITHUB_ACTIONS:-}" ]; then + # shellcheck disable=SC2086 # flag string pnpm exec playwright install --with-deps chromium firefox ${PLAYWRIGHT_FLAGS:-} else + # shellcheck disable=SC2086 # flag string pnpm exec playwright install chromium firefox ${PLAYWRIGHT_FLAGS:-} fi else From a290d81eabe60998ab84ac74a45489f8880b68d0 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 22 May 2026 22:39:10 +0200 Subject: [PATCH 10/10] ci: cap Elasticsearch heap in db-tests (#37816) Elasticsearch JVM heap defaults to ~50% of detected host RAM, typically way too much for our little tests and it starved the other runner processes from memory. Co-authored-by: Claude (Opus 4.7) --- .github/workflows/pull-db-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml index e4de01e711a..1a3ce400352 100644 --- a/.github/workflows/pull-db-tests.yml +++ b/.github/workflows/pull-db-tests.yml @@ -119,6 +119,7 @@ jobs: env: discovery.type: single-node xpack.security.enabled: false + ES_JAVA_OPTS: "-Xms512m -Xmx512m" # reduce from ES default of 50% ports: - "9200:9200" meilisearch: @@ -192,6 +193,7 @@ jobs: env: discovery.type: single-node xpack.security.enabled: false + ES_JAVA_OPTS: "-Xms512m -Xmx512m" # reduce from ES default of 50% ports: - "9200:9200" smtpimap: