Fix minor issue: `cache-prune` logs `jq: error: writing output failed:
Broken pipe` when it has nothing to delete, because the loop stops
reading as soon as it is under the limit while `jq` still has output
pending. Reading from a here-string removes the pipe.
---------
Signed-off-by: silverwind <me@silverwind.io>
On the PR view page, the "merge box" is refreshed periodically.
If the PR changes (e.g.: merged and the head branch has been deleted),
then the merge box doesn't exist in the refresh response. For such case,
the merge box should also be removed from the UI.
Implements the missing REST API endpoints for Actions workflow run
management:
1. `POST /actions/runs/{run}/cancel` cancels a run and its jobs, `409`
when it already finished
1. `POST /actions/runs/{run}/approve` approves a run awaiting approval,
idempotent, `409` when it never awaited one
1. `GET /actions/runs/{run}/logs` downloads the latest attempt's job
logs as a zip archive
`ActionWorkflowRun` gains `created_at`, `updated_at` and the `jobs_url`,
`logs_url`, `artifacts_url`, `cancel_url` and `rerun_url` fields, and
now always emits `conclusion` and `head_branch`.
Cancellation is shared with the web handler in `services/actions`.
Fixes https://github.com/go-gitea/gitea/issues/35176
Fixes https://github.com/go-gitea/gitea/issues/36554
---------
Co-authored-by: Claude Sonnet 4.6 <claude-sonnet-4-6@anthropic.com>
Co-authored-by: OpenCode Agent <opencode@rossgolder.com>
Co-authored-by: Nicolas <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Cache saves have been rejected since June because the repo sat above its
10 GB allowance, so every PR run fell back to a cache built with an
older toolchain and rebuilt the backend from scratch.
- go version in the `gobuild` and `golint` keys and `restore-keys`
- seeder triggers on `go.mod` and gained `workflow_dispatch`
- single writer for the `gomod` cache, the two were racing
- new daily `cache-prune` workflow holding the total under a limit
- `cache: false` for `setup-go` in release and cron workflows, it held
815 MB
- add `workflow_dispatch` so the cache workflows can be triggered
on-demand
Assisted-by: Claude Code:claude-opus-5
The api specs were Go templates whose committed form was not a valid
swagger document, so `swagger-validate`, `generate-openapi.go` and
`.spectral.yaml` each worked around it. They are now plain json,
substituted at serve time.
Renaming them off `.tmpl` also stops `make fmt` rewriting them, which
used to bump their mtime and silently skip the next `make
generate-swagger`.
Also enables stricter spectral linting: extends `lint-swagger` to the
OpenAPI 3 spec, turns on `openapi-tags`, `operation-singular-tag` and
`operation-tag-defined`, adds a top-level `tags` array with descriptions
to the swagger input, and drops the redundant `repository` tag from
`POST /user/repos`.
---------
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
Fixes a `runtime error: index out of range [0] with length 0` panic when
executing template `repo/diff/section_unified` because
`GetLineTypeMarker()` indexes into empty `DiffLine` content.
The regression was introduced in PR #38706 (`94c61137e4`) where
`Content: " "` was removed from the initialization of `tailDiffLine`.
Guarding `GetLineTypeMarker()` directly makes Gitea defensive and robust
against empty diff line values regardless of the source path.
Fixes https://github.com/go-gitea/gitea/issues/38724
### Tests
- Added `TestDiffLine_GetLineTypeMarker` covering empty content,
prefixes, and normal text formats.
Signed-off-by: Sudhanshu Singh <sudhanshuwriterblc@gmail.com>
Further refinements to AGENTS.md:
- consolidate some points
- add point about locales
- rewrite some points for clarity and size
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: delvh <dev.lh@web.de>
`.markup del code { text-decoration: inherit }` makes inline code inside
`<del>` paint its own line-through in addition to the one already
propagating from the parent. Since `code` is `font-size: 85%`, the two
land at different heights and render as a doubled strikethrough.
The rule was inherited from primer-markdown, where it was added in
https://github.com/primer/css/commit/762b8b8264aa4c4beed0a7f842f90c142eb2b310
("so that `<del>` or `<a>` has the same effects on `<code>` tags") at a
time when inline `code` was `display: inline-block`, a box that text
decorations do not propagate into. That `display: inline-block` was
removed 11 days later in
https://github.com/primer/css/commit/f1131d5ab618ac41d4097823b511ca0b373b2ee2,
which made the rule redundant, but it survived the squashed import into
primer/css and every copy downstream of it.
No other popular markdown stylesheet carries an equivalent rule, GitHub
still ships it and shows the same doubled line.
Fixes: https://github.com/go-gitea/gitea/issues/34786
Repository create and delete already fire `repository` webhooks, rename
did not. This adds the `renamed` action with `changes.name.from`
carrying the previous name, and renders it in the chat converters.
Actions workflows are unaffected, they still do not trigger on rename.
AI assistance was used for the implementation and tests.
Fixes https://github.com/go-gitea/gitea/issues/34891.
Co-authored-by: roman s <roman.sukach@dust-labs.com>
Co-authored-by: silverwind <me@silverwind.io>
UploadHandler creates the LFS meta object only as the last step of
uploadOrVerify, after the content is already in the store, so a request
that errors has never created a row of its own. The removal on the error
path was a real compensating action when it was added in #14726, where
the meta object was created before contentStore.Put, but #16865 moved
creation after the Put and left the removal behind.
Since then it can only ever delete a row created by a different request:
a stalled git-lfs PUT that fails after its own retry has already
succeeded wipes the winner's meta object, leaving the content in the
store unreachable and eligible for orphan cleanup.
Drop the removal and add a regression test asserting that a failing
upload keeps a pre-existing meta object.
Fixes: #38424
Assisted-by: Claude Code:claude-opus-5
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Adds a `tag_filter` query parameter to `GET /repos/{owner}/{repo}/releases` that filters releases by tag name, with `*` as a wildcard (e.g. `v1*`, `*beta`, `*rc*`). Matching is
case-insensitive and done in the database query. Literal `%`, `_` and `\` in the filter are escaped.
Fixes: https://github.com/go-gitea/gitea/issues/38513
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
This pull request optimizes code diff highlighting in the PR Files
Changed view, which is a major CPU bottleneck and hot path in
production.
Previously, Gitea executed the expensive `DiffMatchPatch` algorithm
twice for every matching deleted/added line pair (once for the deleted
line, and once for the added line). We now run the diff algorithm once
and cache the resulting `DiffInline` on the `DiffLine` struct itself,
allowing the second line to render via a 0-cost cache lookup.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Follow-up to https://github.com/go-gitea/gitea/pull/36564 (dynamic
matrix) and https://github.com/go-gitea/gitea/pull/36357 (max-parallel),
fixing issues found reviewing the two features together.
- **A placeholder could stall its run forever.** Its payload keeps the
raw matrix but loses its `needs`, so `ParseJob` re-expanded it instead
of reading it back — fatal for `include: ${{ fromJson(needs.*.outputs.*)
}}`.
- **An `if:` reading `matrix.*` skipped the whole job**, with or without
the `${{ }}`. It now reduces to the needs gate, except under
`always()`/`failure()`/`cancelled()`, and each combination is decided on
its own values once the matrix expands.
- **Dependents could be skipped before the combinations ran**, since
inserted siblings are absent from the resolver's job set. The pass now
stops after an insert and defers to the re-emit it schedules.
- **Expansion failures stranded the placeholder.** A retryable one is
returned so the queue retries it; a malformed payload fails the job
instead of requeueing forever.
- **Rerun could rewind a pass-through row** into a raw placeholder
keeping its old terminal status, which nothing expands. Now gated on the
anchor itself.
Plus: `max-parallel` distinguishes an unevaluated `${{ }}` (debug) from
a non-numeric literal (warn — it silently drops the cap).
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Show only author and co-authors without committer, and deduplicate the
same user with multiple email addresses.
The commit list "Author" column should not show the committer. This is
somewhat misleading, and arguably showing it on the individual commit
page is sufficient and consistent with other forges.
The same user with multiple email addresses often happens when
DEFAULT_KEEP_EMAIL_PRIVATE is enabled and Gitea does not use an actual
email address by default for edits. Showing the same avatar and name
twice is not helpful then.
Ref #37594Fix#38488
---
Before
<img width="4088" height="2138" alt="before"
src="https://github.com/user-attachments/assets/a9e919f4-72d0-495f-a0b5-ec094f148f5a"
/>
After
<img width="4124" height="2142" alt="after"
src="https://github.com/user-attachments/assets/7212e527-ab83-440a-a8a4-95b5a79b3580"
/>
---------
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Unfortunately, we can't completely remove the ctx from git.Repository,
because the CatFileBatch still heavily depends on a parent context.
If we remove the Repository ctx, the CatFileBatch will become a mess and
create a lot of unnecessary git processes.
http://localhost:3000/-/admin/monitor/perftrace
* Before: open a repo home, dozens of git processes (duplicate cat-file)
* After: only a few (no duplicate cat-file)
Adds dynamic matrix evaluation to Gitea Actions: a job's
`strategy.matrix` can be built from the outputs of the jobs it needs.
```yaml
jobs:
generate:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set.outputs.result }}
steps:
- id: set
run: echo "result=[1,2,3]" >> $GITHUB_OUTPUT
build:
needs: [generate]
runs-on: ubuntu-latest
strategy:
matrix:
version: ${{ fromJson(needs.generate.outputs.matrix) }}
steps:
- run: echo "building ${{ matrix.version }}"
```
Such a matrix cannot be expanded at planning time, so the job is planned
as a single placeholder and expanded by the job emitter once its needs
finish. Each combination is then gated by `if:` and concurrency as
usual.
- A matrix that resolves to no combination fails the job, as on GitHub.
- Expansion is capped at `MaxJobNumPerRun`.
- Workflows without a needs-dependent matrix are unaffected.
Fixes https://github.com/go-gitea/gitea/issues/25179
---------
Signed-off-by: Pascal Zimmermann <pascal.zimmermann@theiotstudio.com>
Signed-off-by: ZPascal <pascal.zimmermann@theiotstudio.com>
Co-authored-by: Claude <claude-sonnet-4-5@anthropic.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.8) <noreply@anthropic.com>
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
### Description
Fixes#38676
The Alpine package registry registers for only, so a request returns .
Clients that probe the index with before fetching it (like and other
-based tools) fail outright.
**Fix:** Change to for the APKINDEX.tar.gz route, matching the pattern
already used by the i386 registry a few lines below.
### Related issue
Closes#38676
Co-authored-by: waterWang <waterWang@users.noreply.github.com>
Regression of the ctx removal from "git.Repository" struct (the old code
was already wrong and can still cause 500, the "ctx removal" just makes
the problem easier to reproduce).
Merge duplicate code.
Reviewd by codex: no actionable findings.
`GetBranch` silently returned soft-deleted branches, contradicting
`IsBranchExist` and forcing callers to manually check `branch.IsDeleted`
everywhere.
Refactored it to `GetBranchExisting` to have a clear behavior: it only
returns the existing branch.
---------
Signed-off-by: Sudhanshu Singh <sudhanshuwriterblc@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Add support for `strategy.max-parallel` on Gitea Actions matrix jobs.
**How it works**
Jobs over the limit are inserted as `Blocked` instead of `Waiting`, so
runners never see them. When a job finishes, the job-status resolver
promotes one `Blocked` job per freed slot, in job order. Slots are
counted per `JobID` and scoped by reusable-workflow caller. A
`Cancelling` job still owns its runner, so it keeps its slot.
The cap is applied wherever a job can become `Waiting`: initial insert,
rerun, approval, and resolver promotion.
Best effort, not a hard invariant: two concurrent emitter passes can
each promote into the last slot, overshooting by one. It does not
compound, since every later pass recounts.
**Parsing**
Any YAML number, cast to an int as GitHub does (`1.5` → 1). `0` or
negative means unlimited. Expressions (`${{ ... }}`) are not evaluated
yet and fall back to unlimited.
**Migration**
Adds the `max_parallel` column on `action_run_job`. No index or
constraint changes.
**Compatibility**
Existing rows default to `0`, so behaviour is unchanged. No runner
changes needed: the runner protocol is untouched, and since the server
splits the matrix each runner still receives a single job.
Closes https://github.com/go-gitea/gitea/issues/35561
Signed-off-by: Pascal Zimmermann <pascal.zimmermann@theiotstudio.com>
Signed-off-by: ZPascal <pascal.zimmermann@theiotstudio.com>
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Fixes#38209
OAuth2-linked accounts that sign in via the password form were still
redirected to the provider end_session_endpoint on logout because the
redirect was keyed off account LoginType.
Store the session sign-in method (password vs oauth2) and only use
RP-initiated OIDC logout when this session was authenticated via OAuth2.
Sessions without the new key keep the previous LoginType behavior.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The `configure-aws-credentials` step earlier in the same job exports
**both** `AWS_DEFAULT_REGION` and `AWS_REGION` into `$GITHUB_ENV`
(verified in `exportRegion()` at the pinned SHA `517a711`), so
`secrets.AWS_REGION` (the real AWS region) stays set for every later
step in that job.
The AWS CLI v2 region resolution order is `--region` > `AWS_REGION` >
`AWS_DEFAULT_REGION`. The R2 step only set `AWS_DEFAULT_REGION: auto`,
so the leaked `AWS_REGION` won and R2 rejected it, since R2 only accepts
`wnam`, `enam`, `weur`, `eeur`, `apac`, `oc` or `auto`.
The failure log shows both `AWS_DEFAULT_REGION: auto` and `AWS_REGION:
***` in the step env, which confirms the leak.
### Fix
Set `AWS_REGION: auto` explicitly in the R2 upload step env of all three
release workflows. Step-level `env:` is applied after
`GITHUB_ENV`-derived variables, so this reliably overrides the leaked
value.
No other variable leaks from `configure-aws-credentials` matter here:
`AWS_SESSION_TOKEN` is only exported when a session token exists, and
these workflows use static access keys without `role-to-assume`;
`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are already overridden
in the R2 step.
Consolidating the three duplicated upload steps into a composite action
remains a follow-up, as noted in #38635.
fix#38652
UI part (`.log-msg`) uses "white-space: break-spaces;" so the new line
can be correctly rendered.
---------
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: silverwind <me@silverwind.io>
`RedisBroker.Subscribe` returns before the server acks `SUBSCRIBE`, so a
publish right after it can be dropped, making
`TestRedisBroker/CrossBroker` fail intermittently on loaded CI runners
([example](https://github.com/go-gitea/gitea/actions/runs/30257188479/job/89948425118)).
Each scenario now uses its own topic and waits for `PUBSUB NUMSUB`
before publishing. `MemoryBroker` registers synchronously and skips the
wait.
* Closes#36942
* Fixes#19265
Replaces the SSE-based push channel (`/user/events`) with a WebSocket
endpoint (`/-/ws`).
### What changes
- **New `/-/ws` endpoint** (authenticated). One WebSocket per origin,
shared across tabs via a single `SharedWorker`.
- **Pubsub broker** (`services/pubsub`) for fan-out by topic, behind a
`Broker` interface. `MemoryBroker` is the default (single process); a
Redis backend is available for multi-process setups, configured via
`[websocket].PUBSUB_TYPE` / `PUBSUB_CONN_STR`. The internal Gitea queue
was not usable here because it has FIFO/single-consumer semantics.
- **Push-only event production.** Events are emitted by write-triggered
notifiers — `NotificationCountChange`, `PublishStopwatchesForUser`, and
the logout publisher — wired into the existing `notify.Notifier`
interface. No server-side pollers.
- **Typed pub/sub on the client.** `web_src/js/modules/worker.ts` is a
singleton transport; features subscribe per event type via
`onUserEvent('notification-count', cb)` instead of branching on
`event.data.type`.
- **Wire contract** (`UserEventType` union) is shared between the worker
and consumers via `web_src/js/types.ts`, kept in sync with
`services/websocket/events.go`.
- **Client-side periodic polling fallback** kicks in only when the
WebSocket cannot be established (e.g. proxy blocks WS, browser lacks
module-SharedWorker support).
### What's removed
- `modules/eventsource` (SSE manager, run loop, messenger).
- `/user/events` route and `tests/integration/eventsource_test.go`.
- All server-side polling for stopwatches and notification counts.
### Stopwatch multi-tab fix
The navbar stopwatch icon was previously rendered conditionally on `{{if
$activeStopwatch}}`, so tabs loaded before the timer started had no DOM
element to update. The icon and popup are now always rendered (toggled
with `tw-hidden`), and the start/stop/cancel handlers POST silently so
all open tabs reflect the change in real time.
### Deployment note
WebSocket needs the upgrade headers to pass through a reverse proxy,
e.g. for nginx:
```nginx
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
```
Without them the WebSocket cannot be established, and after 3
consecutive failed opens the shared worker signals `push-unavailable`:
the notification count and stopwatch fall back to periodic polling on
the existing `[ui.notification]` timeouts. Real-time push is lost, the
features keep working. The reverse-proxy docs need the same note (see
the `docs-update-needed` label).
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Epid <rexmrj@gmail.com>
Fixes a bug in Actions context generation for `pull_request_target`
workflows where `github.ref` / `gitea.ref` was incorrectly populated
with `refs/heads/owner:branch` instead of `refs/heads/branch`.
### Problem Statement
In `services/actions/context.go`, when constructing the `ref` string for
`pull_request_target` events:
```go
ref = git.BranchPrefix + pullPayload.PullRequest.Base.Name
Signed-off-by: Sudhanshu Singh <sudhanshuwriterblc@gmail.com>
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The handler of `/actions/runs/{run}/approve` doesn't check if the run is
already approved. If a run is re-approved, its jobs' status will be
reset to `StatusWaiting`, causing incorrect job status.
`GET/POST` repository contents endpoints resolve the `ref` query
parameter through `ResolveRefCommit`, which previously only accepted
short branch/tag names or commit IDs. Clients that pass fully-qualified
Git refs (GitHub-compatible), e.g. `ref=refs%2Fheads%2Fmain` or
`ref=refs%2Ftags%2Fv1.0`, received 404 "object does not exist".
This change accepts fully-qualified **`refs/heads/*`** and
**`refs/tags/*`** only, then falls back to the existing short-name and
SHA logic. Other `refs/*` prefixes (e.g. `refs/pull/`, `refs/for/`) are
rejected.
Fixes#38197
---------
Co-authored-by: roman s <roman.sukach@dust-labs.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The download site’s bandwidth costs are growing rapidly, while
Cloudflare R2 does not charge egress fees. Therefore, we would like to
migrate the download site from S3 to R2.
To ensure a smooth transition, we will keep the existing S3 upload
process temporarily and remove it after the migration is complete.
The upload steps are currently duplicated in three places. They could be
consolidated into a composite action in a separate PR.
The preset we extended only matches a `# renovate:` comment on the line
above the `_VERSION` assignment. Ours trails the value, like the ones in
the `Makefile`, so it never matched and the pin has sat at `43.141.5`
since https://github.com/go-gitea/gitea/pull/37050.
Co-authored-by: Claude (Opus 5) <noreply@anthropic.com>