Backport #37459 by cyphercodes
This fixes the scheduled action panic when an event payload is JSON
`null` by initializing the payload map before adding `schedule`. It also
adds regression coverage for the null-payload case.
Fixes#37447.
Co-authored-by: Rayan Salhab <r.salhab@aiyexpertsolutions.com>
Co-authored-by: cyphercodes <cyphercodes@users.noreply.github.com>
Co-authored-by: Hermes Agent (GPT-5.5) <hermes-agent@users.noreply.github.com>
Co-authored-by: Nicolas <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
Backport of #37403 to `release/v1.26`.
The `events › logout propagation` e2e test was racing the SSE connection
setup: if page2's SharedWorker had not finished registering its
messenger by the time page1 triggered logout, the event was silently
dropped and page2 stayed on the authenticated page.
Wait 500ms after verifying page2 is signed in, before triggering the
logout from page1, so the SharedWorker has time to register. Comment
points at a cleaner future fix (expose a ready attribute on the page)
that will also work for the planned WebSocket SharedWorker.
---
This PR was written with the help of Claude Opus 4.7
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
Backport #37388 by @wxiaoguang
Fix#27120
By the way, refactor ReserveLineBreakForTextarea to NormalizeStringEOL
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Backport #37365 by @pisarz77
Fix team members missing from assignee list when `team_unit.access_mode`
is 0 but the doer is owner.
Fix #34871
1. Use `GetTeamUserIDsWithAccessToAnyRepoUnit` for repo assignee list
2. Load assignee list for project issues directly
3. Use `GetTeamUserIDsWithAccessToAnyRepoUnit` for repo reviewer list
Signed-off-by: Jakub Pisarczyk <pisarz77@gmail.com>
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: pisarz77 <pisarz77@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
Backport #37372 by @bircni
Fixes the issue that status report always shows waiting to run, when
already running
https://github.com/go-gitea/gitea/issues/36906#issuecomment-4294545813
Co-authored-by: Nicolas <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
Backport #37288 by @KalashThakare
## Summary
Fixes#37252
The `/api/v1/repos/{owner}/{repo}/actions/runs` endpoint was returning
`event: "push"` for workflow runs triggered by `schedule:` (cron),
instead
of `event: "schedule"`.
## Root Cause
`ActionRun` has two separate fields:
- `Event` — the workflow registration event (e.g. `push`, set when the
workflow file was first pushed)
- `TriggerEvent` — the actual event that triggered the run (e.g.
`schedule`)
`ToActionWorkflowRun` in `services/convert/action.go` was serializing
`run.Event` into the API response instead of `run.TriggerEvent`, causing
scheduled runs to be indistinguishable from push events via the API.
This was already asymmetric — the tasks/jobs API correctly used
`TriggerEvent`.
## Fix
Changed `ToActionWorkflowRun` to use `run.TriggerEvent` for the `event`
field in the API response, consistent with how the jobs API works.
## Before
`event: "push"` returned for all scheduled runs:
<img width="1112" height="191" alt="Screenshot 2026-04-19 115642"
src="https://github.com/user-attachments/assets/c0a169f5-bbd9-4f5d-9474-e4c3795110e4"
/>
## After
`event: "schedule"` correctly returned for scheduled runs:
<img width="890" height="166" alt="Screenshot 2026-04-19 121723"
src="https://github.com/user-attachments/assets/860e99ac-0935-4a43-86a1-7b60f8113480"
/>
## Testing
- Added unit test `TestToActionWorkflowRun_UsesTriggerEvent` in
`services/convert/action_test.go` that explicitly verifies the API
returns `TriggerEvent` and not `Event` for a scheduled run.
- Manually verified via the API against a live Gitea instance with a
`cron: "* * * * *"` workflow.
Co-authored-by: Kalash Thakare ☯︎ <kalashthakare898@gmail.com>
Co-authored-by: Nicolas <bircni@icloud.com>
Backport #37324 by @lunny
A quick fix#37317
---
The current behavior for forks when an organization or repository is
changed to private differs from GitHub.
On GitHub, when a parent repository becomes private, the fork
relationship is removed, which keeps the behavior simple and avoids
visibility conflicts.
I think we need a similar solution to handle cases where the parent
repository becomes private while a fork remains public and the fork
relationship is still preserved.
Signed-off-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Backport #37327 by @prettysunflower
Nyallo~
In pull request #36901, a change is made so that the link to
authentication sources is now escaped with the QueryEscape filter.
https://github.com/go-gitea/gitea/pull/36901/changes#diff-34c39c9736a8b62e293c0c0b24c4b5b8c1c792790018c5809f9ff2cbc12b16b1R4
The problem is that [QueryEscape replace spaces with the `+`
character](https://cs.opensource.google/go/go/+/refs/tags/go1.26.2:src/net/url/url.go;l=234;drc=917949cc1d16c652cb09ba369718f45e5d814d8f),
and this is not unescaped when a user tries to log in with an
authentication source that contains a space, which throws an error.
This commit fixes that by unescaping the provider name in the URL.
---
Example of the error, on my instance, when I try to log in with
`prettysunflower's auth`
```
2026/04/21 00:11:41 routers/web/auth/oauth.go:42:SignInOAuth() [E] SignIn: oauth2 source not found, name: "prettysunflower's+auth"
/go/src/code.gitea.io/gitea/routers/web/auth/oauth.go:42 (0x2cfa5c5)
/usr/local/go/src/reflect/value.go:586 (0x51e245)
/usr/local/go/src/reflect/value.go:369 (0x51d0f8)
/go/src/code.gitea.io/gitea/modules/web/handler.go:181 (0x1a6aaf6)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/modules/web/handler.go:188 (0x1a6ab65)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/modules/web/handler.go:188 (0x1a6ab65)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/modules/web/handler.go:188 (0x1a6ab65)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/services/context/context.go:217 (0x2df1b23)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/modules/web/handler.go:145 (0x1a6afb5)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/pkg/mod/gitea.com/go-chi/session@v0.0.0-20251124165456-68e0254e989e/session.go:258 (0x197eb82)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/modules/web/handler.go:145 (0x1a6afb5)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/pkg/mod/github.com/go-chi/chi/v5@v5.2.5/chain.go:31 (0x1a61d05)
/go/pkg/mod/github.com/go-chi/chi/v5@v5.2.5/mux.go:479 (0x1a64fae)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/pkg/mod/github.com/go-chi/chi/v5@v5.2.5/mux.go:73 (0x1a628c2)
/go/pkg/mod/github.com/go-chi/chi/v5@v5.2.5/mux.go:321 (0x1a6421a)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/pkg/mod/github.com/go-chi/chi/v5@v5.2.5/chain.go:31 (0x1a61d05)
/go/pkg/mod/github.com/go-chi/chi/v5@v5.2.5/mux.go:479 (0x1a64fae)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/pkg/mod/github.com/go-chi/chi/v5@v5.2.5/middleware/get_head.go:37 (0x2c33a67)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/modules/web/handler.go:145 (0x1a6afb5)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/pkg/mod/github.com/go-chi/chi/v5@v5.2.5/mux.go:73 (0x1a628c2)
/go/pkg/mod/github.com/go-chi/chi/v5@v5.2.5/mux.go:321 (0x1a6421a)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/routers/common/maintenancemode.go:50 (0x2b752da)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/modules/web/handler.go:145 (0x1a6afb5)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/pkg/mod/github.com/go-chi/chi/v5@v5.2.5/chain.go:31 (0x1a61d05)
/go/pkg/mod/github.com/go-chi/chi/v5@v5.2.5/mux.go:479 (0x1a64fae)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/modules/web/routing/logger_manager.go:124 (0x127d1ec)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/modules/web/handler.go:145 (0x1a6afb5)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/pkg/mod/github.com/chi-middleware/proxy@v1.1.1/middleware.go:37 (0x2b76acf)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/modules/web/handler.go:145 (0x1a6afb5)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/routers/common/middleware.go:89 (0x2b78cd6)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/modules/web/handler.go:145 (0x1a6afb5)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/routers/common/middleware.go:104 (0x2b7890f)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/src/code.gitea.io/gitea/modules/web/handler.go:145 (0x1a6afb5)
/usr/local/go/src/net/http/server.go:2286 (0x94dc88)
/go/pkg/mod/github.com/go-chi/chi/v5@v5.2.5/mux.go:90 (0x1a62881)
/go/src/code.gitea.io/gitea/modules/web/router.go:286 (0x1a6d2a2)
/go/src/code.gitea.io/gitea/modules/web/router.go:221 (0x1a6cbc6)
/usr/local/go/src/net/http/server.go:3311 (0x96e36d)
/usr/local/go/src/net/http/server.go:2073 (0x94bd6f)
/usr/local/go/src/runtime/asm_amd64.s:1771 (0x49af20)
```
Signed-off-by: prettysunflower <me@prettysunflower.moe>
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: prettysunflower <me@prettysunflower.moe>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Backport #37311 by @silverwind
## Problem
Workflow-level concurrency groups were evaluated — and jobs were parsed
— before the run was persisted, so `run.ID` was `0` and `github.run_id`
in the expression context resolved to an empty string. Expressions like:
```yaml
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
```
collapsed to `<workflow>-` on every push event (`head_ref` is empty on
push), so `cancel-in-progress` cancelled in-progress runs across
**unrelated branches**, not just the current one.
Reproduced on a 1.26 instance:
- push to `master` → `ci` run starts
- push to `feature-branch` → the `master` run gets cancelled
GitHub Actions' documented semantic: on push events `github.run_id` is
unique per run, so the group is unique → no cancellation; on PR events
`github.head_ref` is the source branch → cancellation is per-PR.
## Fix
Insert the run **before** parsing jobs or evaluating workflow-level
concurrency, so `run.ID` is populated in time for every expression that
reads `github.run_id` — not just the concurrency group, but also
`run-name`, job names, and `runs-on`.
`jobparser.Parse` now runs inside the `InsertRun` transaction, after
`db.Insert(ctx, run)`. Workflow-level concurrency evaluation runs next
and only mutates `run` in memory. All concurrency-derived fields
(`raw_concurrency`, `concurrency_group`, `concurrency_cancel`) plus
`status` and `title` are persisted in a single final `UpdateRun` at
end-of-transaction — one `INSERT` + one `UPDATE` per run in both the
concurrency and non-concurrency paths (matches pre-branch parity, one
fewer `UpdateRepoRunsNumbers` `COUNT` than the interim state).
`GenerateGiteaContext` now sets `run_id` from `run.ID` unconditionally;
every caller passes a persisted run.
**Verification**: tested end-to-end on a 1.26 deployment. Before the
patch, two successive `ci` pushes (one to master, one to a feature
branch) cross-cancelled each other. After the patch, the same pushes —
in both orders (master→branch, branch→master) — run to completion
simultaneously across 15+ runs with zero cancellations.
**Regression tests** in `services/actions/context_test.go`:
- `TestEvaluateRunConcurrency_RunIDFallback` — unit check that
`EvaluateRunConcurrencyFillModel` resolves `github.run_id` from
`run.ID`.
- `TestPrepareRunAndInsert_ExpressionsSeeRunID` — full-flow check: calls
`PrepareRunAndInsert` with `${{ github.run_id }}` in both `run-name` and
the concurrency group, then asserts the persisted `Title`,
`ConcurrencyGroup`, and `RawConcurrency` contain / survive the run's ID.
Re-ordering `db.Insert` relative to either parse or concurrency eval
fails this test.
## Relation to #37119
[#37119](https://github.com/go-gitea/gitea/pull/37119) also moves
concurrency evaluation into `InsertRun` but keeps it **before**
`db.Insert`, then tries to populate `run_id` only when `run.ID > 0` —
which is still `0` at that call site, so the cross-branch leak would
survive that PR as written. This PR fixes the ordering so that `run.ID`
is actually populated at eval time, and broadens it to cover parse-time
expression interpolation too.
---
This PR was written with the help of Claude Opus 4.7
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
Backport #37325 by @lunny
Fix a bug the checkout command line hint become `git fetch -u
https://gitea.combircni/tea`
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Backport #37279 by @silverwind
Moves the manifest patching from `closeBundle` to `writeBundle`. Thrown
errors in `writeBundle` work correctly and exit the build.
Signed-off-by: silverwind <me@silverwind.io>
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Backport #37290 by wxiaoguang
Fix#37289
Don't tell container client that the instance needs basic auth if the
public access is available.
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Backport #37189 by @bircni
If a workflow is not in default branch the hooks could not be detected
Fixes#37169
Co-authored-by: Nicolas <bircni@icloud.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Backport of go-git upgrade to v5.18.0 for the v1.26 release branch.
Fixes GHSA-3xc5-wrhm-f963 (credential exposure on HTTP redirects).
---
This PR was written with the help of Claude Opus 4.6
Co-authored-by: Claude (Opus 4.6) <noreply@anthropic.com>
Backport #37244 by @JoeGruffins
Patterns starting with "/" (e.g. /docs/.*\.md) never matched because git
returns relative paths without a leading slash. Strip the leading "/"
before compiling the regex since the ^...$ anchoring already provides
root-relative semantics.
closes#28107
Co-authored-by: JoeGruffins <34998433+JoeGruffins@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Backport #37256 by wxiaoguang
1. Make sure OmitEmail won't panic
2. SSH principal keys are not for signing or authentication
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Backport #37193 by @Mohit25022005
fix(api): handle missing base branch in PR commits API
Closes#36366
Previously, the PR commits API returned a 500 Internal Server Error
when the base branch was missing due to an unhandled git "bad revision"
error.
This change:
- Checks for base branch existence before performing git operations
- Returns 404 when the base branch does not exist
- Prevents git errors from surfacing as 500 responses
This improves API robustness and aligns behavior with expected error
handling.
Tested locally by:
- Creating a pull request
- Deleting the base branch
- Verifying that the API returns 404 instead of 500
Co-authored-by: Mohit Swarnkar <mohitswarnkar13@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Backport #37190 by @bircni
`url.PathEscape` unnecessarily encodes ! to %21, causing Matrix
homeservers to reject the request with 401. Replace %21 back to ! after
escaping.
Fixes#36012
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Nicolas <bircni@icloud.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Backport #37172 by @xingxing21
Fixes: https://github.com/go-gitea/gitea/issues/36677
The fix is a template-only change in
[templates/repo/diff/compare.tmpl:16-25](vscode-webview://1ca9j6f1e3qtaf59o0cr4ind65ulf8mevvbbbq88int1gg2lncar/templates/repo/diff/compare.tmpl#L16-L25).
Before, the display names were built with conditional logic:
All four variables defaulted to just the owner name (Examples)
$HeadCompareName was only upgraded to owner/repo format in two narrow
cases:
Same org, different repos
A root repo exists with the same owner as the head repo
All other cases (same-repo PRs, cross-org PRs) got owner-only labels
After, all four variables are unconditionally set to owner/repo format
using printf "%s/%s":
```
- {{$BaseCompareName := printf "%s/%s" $.BaseName $.Repository.Name -}}
- {{- $HeadCompareName := printf "%s/%s" $.HeadRepo.OwnerName $.HeadRepo.Name -}}
- {{- $OwnForkCompareName := "" -}}
- {{- if .OwnForkRepo -}}
- {{- $OwnForkCompareName = printf "%s/%s" .OwnForkRepo.OwnerName .OwnForkRepo.Name -}}
- {{- end -}}
- {{- $RootRepoCompareName := "" -}}
- {{- if .RootRepo -}}
- {{- $RootRepoCompareName = printf "%s/%s" .RootRepo.OwnerName .RootRepo.Name -}}
- {{- end -}}
+ {{$BaseCompareName := $.Repository.FullName -}}
+ {{$HeadCompareName := $.HeadRepo.FullName -}}
+ {{$OwnForkCompareName := "" -}}
+ {{if $.OwnForkRepo -}}
+ {{$OwnForkCompareName = $.OwnForkRepo.FullName -}}
+ {{end -}}
+ {{$RootRepoCompareName := "" -}}
+ {{if $.RootRepo -}}
+ {{$RootRepoCompareName = $.RootRepo.FullName -}}
+ {{end -}}
```
These variables drive the labels in the base and head branch selector
buttons and their dropdown items. No backend changes were needed —
$.BaseName, $.Repository.Name, $.HeadRepo.OwnerName, and $.HeadRepo.Name
were already available in the template context.
Co-authored-by: Xing Hong <39619359+xingxing21@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Backport #37185 by @Mohit25022005
Fix 500 error when comparing branches across fork repositories
## Problem
The compare API returns a 500 Internal Server Error when comparing
branches where the head commit exists only in the fork repository.
## Cause
The API was using the base repository's GitRepo and repository context
when converting commits. This fails when the commit does not exist in
the base repository, resulting in a "fatal: bad object" error.
## Solution
Use the head repository and HeadGitRepo when available to ensure commits
are resolved in the correct repository context.
## Result
* Fixes "fatal: bad object" error
* Enables proper comparison between base and fork repositories
* Prevents 500 Internal Server Error
Fixes#37168
Co-authored-by: Mohit Swarnkar <mohitswarnkar13@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>