Commit status list orders only by `created_unix`/`updated_unix`, which
have 1-second resolution while CI often posts many statuses per second.
With LIMIT/OFFSET paging, databases (e.g. PostgreSQL using a Sort plan)
may order tied rows differently per page, so `GET
/repos/{owner}/{repo}/commits/{ref}/statuses` returns some statuses
twice and never returns others.
This became visible after https://github.com/go-gitea/gitea/pull/36521
made requests without `page` paginated. Clients like Renovate that page
until `X-Total-Count` can miss a context's newest status and see a stale
`pending`, blocking automerge.
Fix: add `index` (unique per commit) as a tiebreaker to the
timestamp-based orders.
Co-authored-by: silverwind <me@silverwind.io>
Aligns the npm registry with what npm, pnpm and yarn expect:
1. Raise the publish body cap from
https://github.com/go-gitea/gitea/pull/37890 to 256 MiB like npmjs,
larger bodies get 413
2. Pick the tarball attachment by name, `npm publish --provenance`
failed at random
3. Store and serve `libc`, so mismatched glibc/musl optional binaries
are skipped
4. Treat root `*.gyp` files as an install script, like npm does
5. Always serve a `latest` dist-tag, yarn and pnpm fail without it
6. Take top-level metadata from `latest` and drop the per-version readme
7. Serve tarballs at the npmjs path `/<name>/-/<file>`, former URLs keep
working
8. Add ETag revalidation for metadata, `npm ping` and `npm whoami`
Tested with npm 12.1, pnpm 12.4, yarn 1.22 and yarn 4.18.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The example shows `REPOSITORY_AVATAR_FALLBACK_IMAGE =
/img/repo_default.png`, but public files moved under `/assets` in
https://github.com/go-gitea/gitea/pull/15219 and nothing serves `/img/`
anymore. The value is also used as-is without the sub-path, so even
`/assets/img/repo_default.png` 404s when `ROOT_URL` has one. Leave the
key empty and document the real default
`{AppSubURL}/assets/img/repo_default.png` from
`modules/setting/picture.go`.
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Lets users approve an OAuth2 scope change on an existing grant instead
of failing with `a grant exists with different scope`.
- Approving a different scope updates the existing grant. Issued tokens
follow immediately, since their scope is read from the grant.
- Confidential and trusted apps show the consent page when the scope set
changes, instead of silently reusing the old grant.
- An omitted `scope` reuses the existing grant's scope, like GitHub.
- The consent page lists newly added scopes.
Fixes: https://github.com/go-gitea/gitea/issues/38940
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: Giteabot <teabot@gitea.io>
Co-authored-by: silverwind <me@silverwind.io>
Align job and `runs-on` validation with github.com, as implemented by
the parser in https://github.com/actions/runner. A job without `runs-on`
could be claimed by any runner, so a job meant for a container could run
on the host.
- Jobs without `runs-on` fail with `Required property is missing:
runs-on`, called workflows included
- Unknown job keys and callers (`uses:`) mixed with steps-only keys like
`runs-on` are rejected
- Empty, null and nested `runs-on` values are rejected
- A `runs-on` evaluating to such a value fails only that job
- Called workflows are validated at run creation, an invalid one fails
the run as an invalid workflow file
- Zero labels (`runs-on: []` or `{}`) never match a runner, including
jobs queued before upgrading
<img width="960" alt="image"
src="https://github.com/user-attachments/assets/e746ce5a-b711-4e8b-aab8-81336ff53d86"
/>
**Behavior Change:** workflows that omit `runs-on`, use unknown job keys
or mix `uses` with `runs-on` stop running until fixed.
---------
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
Three commented defaults in `custom/conf/app.example.ini` differ from
what Gitea actually uses, so the file says they default to something
else:
- `MINIMUM_KEY_SIZE_CHECK`: example `false`, code `true`
(`modules/setting/ssh.go:55`, read at `:152`).
- `SSH_SERVER_HOST_KEYS`: example lists `ssh/gitea.rsa, ssh/gogs.rsa`,
code also loads `ssh/gitea.ed25519` and `ssh/gitea.ecdsa`
(`modules/setting/ssh.go:57`).
- `[queue] DATADIR`: example `queues/`, code `queues/common`
(`modules/setting/queue.go:33`), which the comment above it already
states.
Co-authored-by: silverwind <me@silverwind.io>
Several handlers skipped checks that their sibling routes or settings already enforce. This brings them in line.
- Push mirror API honors `DISABLE_NEW_PUSH` and checks the caller's permission
- Media API serves small files with the usual content headers
- Issue attachment API ignores comment attachments
- Push-to-create respects `FORCE_PRIVATE`
- Profile feeds and follow actions respect `ENABLE_FEED` and owner visibility
- Tag delete route refuses release tags
- Refresh token grant only accepts refresh tokens
- Gitea migrations bound the source's page size
Co-authored-by: bircni <bircni@icloud.com>
On a repository's Actions tab, runs are sorted newest first on initial
page load. After the first auto refresh (added in #38329, every 3
seconds while runs are active and every 12 seconds otherwise), the same
runs may appear in a different order and move again as their status
changes.
Example with four runs (Gitea 28.0.0, SQLite):
```
page load: #10 success, #9 failure, #8 success, #7 running
after refresh: #8 success, #10 success, #9 failure, #7 running
```
To reproduce, open the Actions tab of a repository with runs in
different statuses and wait for an auto refresh. On SQLite, the runs may
be regrouped by status, with each group ordered oldest first.
`preparePartialRefreshRuns` reloads the runs currently shown on the page
using `GetRunsByRepoAndID`. That query has no `ORDER BY`, while the
initial page load uses `FindRunOptions.ToOrders` and sorts by index
descending.
With SQLite, the query planner used the `(repo_id, status)` index, so
the returned row order differed from the original page order. Since the
query has no explicit ordering, this behavior is database-dependent. I
have not tested MySQL or PostgreSQL.
This change orders `GetRunsByRepoAndID` by index descending, the same
order `FindRunOptions.ToOrders` uses for the initial page load. The
refresh only reloads the runs already on the page, so they come back in
the original order, with or without filters and on any page.
The other caller of `GetRunsByRepoAndID`, run approval, does not depend
on result ordering.
Testing:
- Added `TestPreparePartialRefreshRunsKeepsRequestedOrder`. Without the
fix, runs 794, 793, 792, 791 are returned as 791, 792, 794, 793; with
the fix, the test passes.
- `go test` passes for `./routers/web/repo/actions/`,
`./models/actions/` and `./services/actions/`.
- `go vet` and `golangci-lint v2.13.2` pass for the changed packages.
- Manually tested by building Gitea 28.0.0 with this patch and running
it on our SQLite instance. The runs list keeps its newest-first order
across auto refreshes. The official 28.0.0 binary reproduces the
reordering.
AI-assisted: drafted with Claude Code (claude-opus-5-5), reviewed by me.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Set receive.fsckObjects=true in Gitea's internal global git config so
the receiving git process rejects bad, malicious or duplicate objects at
push time, before Gitea ever stores them.
Assisted-by: Codet:claude-opus-4-8
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Introduces gitproxy module which spawns a small forward proxy as scanner
for git calls
Replaces hostmatcher with matchlist which supports port rules
Deprecates ALLOWED_DOMAINS/BLOCKED_DOMAINS and ALLOW_LOCALNETWORKS
settings in migration in favor of full names we have in security
configs.
Removes `external` preset in favor of lax/strict modes, strict mode
requiring explicit ports if they aren't standard http/s ones.
Breaking changes:
- `external` preset no longer works as deny rule. To enforce that, use
`strict` mode and allow ranges to connect to
- Wildcards are no longer accepted in IP addresses
- `*` is no longer allowed as entry in lists
- domain rules now use curl like syntax `*.example.com` matching
subdomains but not `example.com`, `example.com` matching itself and all
subdomains. `example.*` is not a valid rule
- In the default `lax` mode, `[security] ALLOWED_HOST_LIST` no longer
restricts public hosts, set `EGRESS_MODE = strict` to keep an exclusive
list. A startup warning flags this
- Invalid list entries are logged at startup, invalid
`BLOCKED_HOST_LIST`/`BLOCKED_DOMAINS` entries stop it
Docs: https://gitea.com/gitea/docs/pulls/557
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Fixes https://github.com/go-gitea/gitea/issues/38705
Fixes https://gitea.com/gitea/runner/issues/1232
Jobs of a called workflow saw `gitea.event_name` as `workflow_call` and
`gitea.event.inputs` replaced by the caller's `with:`, so a condition
like `gitea.event_name == 'push'` never held in them, and a dispatched
run's own inputs were lost there.
They now keep the caller's trigger event and their `inputs` are the
run's `workflow_dispatch` inputs overlaid with the caller's `with:`, as
on GitHub. For example, with `workflow_dispatch` inputs `{target: prod,
debug: true}` and caller's `with: {target: dev}`, the called workflow's
`inputs` are `{target: dev, debug: true}`.
A runner cannot resolve these inputs itself, so they are sent in a new
`gitea_workflow_call` context entry, with the original event name and
inputs for the runner to restore. For more details, see the runner PR:
https://gitea.com/gitea/runner/pulls/1250
Co-authored-by: silverwind <me@silverwind.io>
Fixes several gaps in the approval of fork pull request runs:
1. Approving a run that was cancelled while awaiting approval revived
its cancelled jobs. Such a run is no longer treated as awaiting approval
by the merge box, run page, approve actions and API, and rerunning it
approves it.
2. Approval no longer revives jobs cancelled while the run was pending,
no longer lets two jobs sharing a concurrency group cancel each other,
and re-emits the run so jobs needing a cancelled job get resolved.
3. An unapproved run applies its workflow-level concurrency only once
approved.
4. For workflows from the pull request, both the event actor and the
pull request author must be trusted to skip approval. Workflows from the
default branch, like `issue_comment`, still only check the actor.
---------
Co-authored-by: silverwind <me@silverwind.io>
Refs #39450.
The `ReviewPayload.Type` comment said the value is approved, rejected or
comment. The webhook and Actions notifiers set it to the event type,
`pull_request_review_approved`, `pull_request_review_rejected` or
`pull_request_review_comment`, and the comment now lists those. The same
wording on the docs site (`docs/usage/repository/webhooks.md` in
gitea/docs) needs the matching change, which is why this refs the issue
rather than closing it.
* Revert the behavior introduced by #30805
* Now the PR status is still managed in Gitea's code where the operation
is triggerred but not in post-receive hook
* Fix#39254 and many more related bugs.
* Fix#39124
```
// MarkAsMerged sets a pull request to merged and closes the corresponding issue
// To make sure the pull request is marked as merged correctly, the caller uses multiple-stage operations:
// 1. Create a temp repo from base, merge the head into the temp repo, and get the merged commit ID and timestamp,
// 2. The merged commit ID and related information are stored into pull request
// 3. Push the merged commit to the base repo
// 4. Call MarkAsMerged to mark the pull request as merged and do post-processing (notification, close issues, etc)
//
// If failure occurs in step 1/2/3: the pull request is still open, the base repo is not changed, the doer can start a new merge.
// If failure occurs in step 4: the pull request can be marked as merged by the merged commit ID stored in it later.
```
Prior to this change, the API rejected reviews without a summary
comment, even if it had pending inline comments. This differs from the
web UI, which accepts such reviews. The affected endpoints are:
1. Submitting via POST /repos/{owner}/{repo}/pulls/{index}/reviews/{id}.
2. Creating via POST /repos/{owner}/{repo}/pulls/{index}/reviews, both
when finishing an existing pending review and when creating a new
pending review.
The endpoints ran their own emptiness check, which ignored comments
already in a pending review. The fix drops it for comment and pending
reviews and relies on the model's check, which counts them, as the web
UI does.
A review with neither a body nor inline comments remains invalid.
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: bircni <bircni@icloud.com>
`GET /repos/{owner}/{repo}/compare/{basehead}` reads `verification` and
`files` (both default true), the same as the commit endpoints do:
```go
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
files := ctx.FormString("files") == "" || ctx.FormBool("files")
```
The spec only lists `output`, so generated clients cannot turn either
off. This documents both with the wording the commit endpoints already
use and regenerates the spec.
Co-authored-by: bircni <bircni@icloud.com>
Removing and re-adding a mention, or changing `closes #1` to a plain
`#1`, added duplicate references to the issue's timeline.
The timeline now renders a single entry per referencing issue or pull
request, positioned at the first mention, like GitHub does.
Adds a read-only Actions job queue: running jobs first, then waiting
jobs in the order a runner picks them up. It is shown instance-wide in
the admin Actions section with owner, repository and status filters, and
per repository in the Actions tab. Both lists refresh in place.
Pending work is currently only visible per repository and newest-first,
so nothing shows what is queued, in which order, or what occupies the
runners. Reordering the queue will be proposed separately.
A migration adds indexes for the runner pickup query and
repository-scoped status lookups.
* Fix#34198
<img width="1345" height="451" alt="image"
src="https://github.com/user-attachments/assets/7d52ff76-81b4-44e8-b583-d7d89c9dffcd"
/>
<img width="1809" height="1134" alt="image"
src="https://github.com/user-attachments/assets/4d56c0cb-bae7-4ce2-8f3c-75163b2bc7f4"
/>
---------
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Closes https://github.com/go-gitea/gitea/issues/33579.
Adds browser previews for Actions artifacts. Selecting an artifact opens
its file browser; selecting a file renders it in the same tab. The ZIP
download remains available separately.
Previews require sign-in and read access to the run. Text, image and PDF
files are supported; rendered HTML and JavaScript run in a sandboxed
frame and are labeled as automatically generated. The frame loads files
from a signed link that expires after an hour, because its requests
carry no session cookie. `[actions] ARTIFACT_PREVIEW_MAX_SIZE` limits
total previewable artifact size (`0` disables previews; `-1` removes the
limit); individual files also follow `[ui] MAX_DISPLAY_FILE_SIZE`.
<img width="1803" height="913" alt="image"
src="https://github.com/user-attachments/assets/a38fd704-2244-44fa-9181-c695ecbe0276"
/>
Docs: https://gitea.com/gitea/docs/pulls/533
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Update eslint and its plugins, enable more rules and fix their findings:
1. `unicorn/no-unsafe-string-replacement` found that uploading a file
whose name contains `$&` inserted a broken markdown link, because
`String#replace` expands such patterns in the replacement string
2. `@typescript-eslint/require-await` removes `async` from functions
that never await
3. Plugin rules not covered by a preset are now listed explicitly
---------
Co-authored-by: bircni <bircni@icloud.com>
Gitea doesn't evaluate a job's `if:` before checking the job's
concurrency group, which causes a job that should have been skipped to
incorrectly cancel other jobs in the same concurrency group.
This PR makes Gitea decide `if:` for every job before it becomes
waiting, including jobs without `needs` at insertion, on approval and on
rerun. A skipped job therefore no longer takes part in job concurrency
or holds a max-parallel slot, and a reusable caller whose `if:` is false
is no longer expanded on approval or rerun. An invalid `if:` skips the
job with an error summary.
After this PR, Gitea decides all jobs' `if:` expressions and sends `if:
always()` to the runner, so the runner no longer needs to evaluate a
job's `if:` again ([gitea/runner
`run_context.go`](https://gitea.com/gitea/runner/src/commit/81add274599355ec1838b6ebe45804890d40bab9/act/runner/run_context.go#L1195)).
---------
Co-authored-by: silverwind <me@silverwind.io>
`GET /admin/unadopted` documents a `pattern` query parameter, but the
handler reads `query`:
```go
repoNames, count, err := repo_service.ListUnadoptedRepositories(ctx, ctx.FormString("query"), &listOptions)
```
Nothing in the tree reads `pattern`. A client generated from the
published spec sends it, the server ignores it, and the caller gets the
full unadopted list with no error — the failure is silent.
Both spellings date from the commit that added the endpoint,
https://github.com/go-gitea/gitea/pull/12920, so the documentation has
been wrong since 2020 rather than drifting. The handler side is the one
clients already depend on, so this renames the documented parameter and
regenerates the spec.
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
`COOKIE_USERNAME` is documented in `app.example.ini` but nothing in the
tree reads it (unlike `COOKIE_REMEMBER_NAME` / session `COOKIE_NAME`).
An admin who sets it gets silence and no effect.
Removed the dead example lines.
Fixes#39353
AI disclosure: assisted with drafting; I verified the setting is unused
and reviewed the diff.
Signed-off-by: TastyHeadphones <tastyheadphones@icloud.com>
`test-backend#%` and `test-integration#%` passed the test name to the
shell unquoted, so regex characters broke the command. For example `make
'test-backend#^(TestA|TestB)$'` failed with `syntax error near
unexpected token '('`. The pattern is now single-quoted like the
neighbouring `-tags` argument.
https://github.com/go-gitea/gitea/pull/38406 stopped setting
`ServerName` on the internal API client, which
https://github.com/go-gitea/gitea/pull/5820 had added for ACME. Internal
requests to a local `LOCAL_ROOT_URL` now send SNI `localhost` (or none
for IPs). The ACME listener selects its certificate by SNI, finds none
and aborts the handshake with `tls: internal error`, breaking SSH access
and git hooks.
Send the `ROOT_URL` host as SNI again for local targets, and treat
unspecified addresses (`0.0.0.0`, `::`) as local since dialing them
reaches the local host. Remote targets are still verified against their
own hostname.
Fixes: https://github.com/go-gitea/gitea/issues/38903
---------
Co-authored-by: silverwind <me@silverwind.io>
On GitHub, one can close PRs via `Fixes: #123` references which was not
possible on Gitea before, but now is. Verified fully that behaviour
matches GH and ensured no regressions for external trackers.
Bump the TypeScript and Vite targets from ES2020 to ES2022. The frontend
already calls ES2022 APIs like `Object.hasOwn` and `Array.prototype.at`
without polyfills, so downleveling class syntax only added helpers. JS
output shrinks by 36 KB.
The only new syntax-level requirement is Safari 16.4 for mermaid, which
ships class static blocks. Pale Moon supports all emitted syntax since
32.2.
The ES2022 target implies `useDefineForClassFields`, so
`noImplicitOverride` is enabled to flag class fields that shadow
inherited DOM properties.
Comment excerpts in activity feeds previously used either the first 200
display characters of a comment or, for review comments, its first
physical line. That excerpt is rendered as Markdown in the feed, so if
the excerpt began with a leading blank lines or structural markdown
syntax, the excerpt would render as empty or produce broken output. For
example, a review comment beginning with a code fence stored only the
opening fence, which rendered as an empty code block.
This commit instead renders feed excerpts as prose only, dropping code,
math, tables, images and HTML, which also fixes already stored excerpts.
New excerpts start at the first prose line, and review comments get the
same excerpt as issue comments.
This produces meaningful excerpts in more cases while preserving their
original Markdown.
---
For a comment that contains the following:
````
```
some code
```
hello
````
This previously rendered as:
<img width="816" height="118" alt="Screenshot 2026-09-08 at 5 06 47 PM"
src="https://github.com/user-attachments/assets/9d125363-72de-46bf-b47a-961245a79c5f"
/>
And now renders as:
<img width="807" height="89" alt="Screenshot 2026-09-08 at 5 08 30 PM"
src="https://github.com/user-attachments/assets/d2dcdc6d-d7a9-437f-8e9c-b845fe99fa5e"
/>
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: bircni <bircni@icloud.com>