Fixes#38278
## Problem
When branch protection matches the branch an Actions workflow pushes to,
the runner's `git push` is rejected — even though the workflow token has
`contents: write` and the same push performed with a PAT (write access)
succeeds. Disabling protection or changing the pattern so it no longer
matches makes the push work.
## Root cause
In `preReceiveBranch` (`routers/private/hook_pre_receive.go`), the "can
the doer push to this protected branch" check resolves the pusher with
`user_model.GetUserByID(ctx, ctx.opts.UserID)`. For an Actions push the
user ID is `-2` (the virtual `ActionsUserID`), which has no database
row, so the lookup fails. Even past that, `CanUserPush` →
`HasAccessUnit`/whitelist membership cannot evaluate a virtual user and
returns `false`. As a result the Actions bot was rejected on every
matching protected branch, despite the earlier `assertCanWriteRef`
already confirming the token's code-write via
`GetActionsUserRepoPermission`.
This was inconsistent: a PAT with identical write access passed the
exact same check.
## Fix
Evaluate the Actions bot against its already-computed token permission
instead of a user lookup, mirroring the existing
`IsUserMergeWhitelisted` pattern:
- Add `CanActionsUserPush` / `CanActionsUserForcePush` on
`ProtectedBranch`, which take the precomputed `access_model.Permission`.
- Allow the push when push is enabled, **no** push whitelist is
enforced, and the token has code-write.
- Keep the bot blocked when a whitelist is enforced — it cannot be added
to one, so it must use a pull request. This preserves the whitelist as a
real security boundary.
Force-push, signed-commit and protected-file-path checks are untouched.
---------
Signed-off-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The **Status** filter dropdown on the repository Actions run list does
not let you filter for **Blocked** runs (nor **Cancelled** or
**Skipped**). These statuses are missing from the dropdown even though a
run can legitimately end up in any of them.
A run's status is computed by `aggregateJobStatus`, which can return
`Blocked`, `Cancelled` and `Skipped`. Because the filter dropdown only
offered Success, Failure, Waiting, Running and Cancelling, runs in those
other states existed but were impossible to filter for.
Fixes#38226
## Summary
Add `chi_middleware.GetHead` as the first `BeforeRouting` middleware on
the API router. This makes every API `GET` endpoint automatically handle
`HEAD` requests, as required by RFC 9110 §9.3.2.
Previously, `HEAD` requests to endpoints like `GET
/repos/{owner}/{repo}/git/commits/{sha}` returned `405 Method Not
Allowed`.
The web router already used this same middleware (see
`routers/web/web.go:261`), so this aligns API behaviour with the web
router.
## Changes
- `routers/api/v1/api.go`: add `chi_middleware.GetHead` middleware to
the API router
- `tests/integration/api_repo_git_commits_test.go`: add
`TestAPIReposGitCommitsHEAD` verifying HEAD returns 200 on a valid ref
and 404 (not 405) on a missing ref
In the Actions log viewer, a double-quoted URL renders with a stray
extra `;` after it.
Reported in `gitea/runner#1046`
Remove the buggy AI slop `linkifyURLs` and use new approach to process
URLs in text
---------
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
### What
`GetActionsUserRepoPermission` (`models/perm/access/repo_permission.go`)
decides whether an Actions task token may access a target repo. Its
cross-repo branches each enforce a fork-PR discriminator — except the
collaborative-owner branch, which was missing the
`!task.IsForkPullRequest` guard that its sibling
`checkSameOwnerCrossRepoAccess` has.
As a result, when a private repo **B** lists owner **A** as a
collaborative owner, an attacker-controlled fork pull-request workflow
whose base repo is owned by A was granted code-read on B — i.e. the
fork's workflow could clone a third private repository it has no rights
to (read-only confidentiality breach).
### Fix
Add the same fork-PR guard the sibling path already enforces:
```go
if taskRepo.IsPrivate && !task.IsForkPullRequest {
actionsUnit := repo.MustGetUnit(ctx, unit.TypeActions)
if actionsUnit.ActionsConfig().IsCollaborativeOwner(taskRepo.OwnerID) {
return maxPerm, nil
}
}
```
## Summary
Fixes two related bugs that cause jobs in large workflows (50+ parallel
jobs) to never get a runner assigned even though runners are free.
### Bug 1 — Concurrent runner race
When N runners all poll `FetchTask` with a stale `tasksVersion`
simultaneously, they all query the same waiting job list sorted by
`(updated, id)` and all pick **job #1**. Only one wins the `UPDATE WHERE
task_id=0` optimistic lock; the rest return empty-handed but still
receive `latestVersion` in the response. They then consider themselves
"up to date" and skip `PickTask` on every subsequent poll, leaving jobs
#2–50 permanently unassigned.
**Fix:** `CreateTaskForRunner` now iterates through all matching waiting
jobs. When the optimistic lock fails on job #1, it immediately tries job
#2, then #3, etc., each in its own independent transaction so a failed
attempt rolls back cleanly before the next candidate is tried.
`PickTask` no longer wraps this call in an outer `db.WithTx` (which
caused `halfCommitter` entanglement that prevented per-attempt
rollbacks).
### Bug 2 — Idle runner doesn't re-check after finishing a task
`tasks_version` only bumps when a job transitions **to** waiting (new
workflow triggered, blocked→unblocked). After a runner finishes its
current task it polls `FetchTask` with `tasksVersion == latestVersion`,
so the server skips `PickTask` entirely — the remaining 45 waiting jobs
are invisible to the now-idle runner.
**Fix:** Also call `IncreaseTaskVersion` in `UpdateRunJob` when a
(non-reusable-caller) job transitions to a **done** state. Idle runners
then see a version mismatch on their next poll and attempt `PickTask`,
picking up the remaining jobs.
## Problem
The repository restorer (`services/migrations/restore.go`) builds
`file://` URLs for release attachments and PR patches by joining
user-supplied paths from `release.yml` and `pull_request.yml` onto the
dump directory:
```go
*asset.DownloadURL = "file://" + filepath.Join(r.baseDir, *asset.DownloadURL)
pr.PatchURL = "file://" + filepath.Join(r.baseDir, pr.PatchURL)
```
`filepath.Join` cleans the path, so a crafted relative value such as
`../../../../etc/passwd` resolves to an absolute path **outside** the
dump directory. `uri.Open` then reads it via `os.Open` and stores the
content as a release attachment, which is retrievable through the API —
an arbitrary file read (Local File Inclusion) from a dump archive
supplied to `restore-repo`.
## Fix
Add a `localFileURL` helper that resolves the relative path against
`baseDir` and rejects anything that escapes it. Malicious entries are
skipped with a warning so a legitimate restore still completes; in-dump
files keep working unchanged.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
## Problem
On MSSQL databases created by old Gitea versions, the real datetime
columns `external_login_user.expires_at` and `lfs_lock.created` were
created as `DATETIME`. `DATETIME` parses datetime literals in a
locale-dependent way, so the ISO string `'YYYY-MM-DD HH:MM:SS'` that
xorm sends fails to convert when the session language is not English
(e.g. German defaults to `dmy`):
```
mssql: Bei der Konvertierung eines nvarchar-Datentyps in einen datetime-Datentyp liegt der Wert außerhalb des gültigen Bereichs.
```
This breaks linking an external (OAuth/Keycloak) account to an existing
user, and LFS lock creation, with a 500 error.
## Fix
Current xorm already maps `time.Time` to the locale-independent
`DATETIME2` for new installs, so only legacy databases are affected.
This adds migration `341` that converts these columns to `DATETIME2` on
legacy MSSQL databases (no-op on other databases and on columns already
using `DATETIME2`).
A full audit of persisted `time.Time` columns in `models/` confirmed
these two are the only real datetime columns affected — every other time
value is stored as a unix-timestamp integer.
A regression test (MSSQL-only, mirroring the existing v338 pattern)
downgrades the columns to legacy `DATETIME`, runs the migration, asserts
the type becomes `DATETIME2`, and verifies an ISO datetime insert
succeeds under `SET LANGUAGE German`.
Fixes#38211
- Display the per-repository run number as `#N` next to the run title in
the run view, matching the runs list and GitHub
- Add the run `Index` to the run view API response (and the devtest
mock) to support that
- Restore the summary panel's `flex: 1` so the workflow graph fills the
right-column height even when a run has no job summaries
- Keep the job-summary section content-sized so it doesn't compete with
the graph for height
- Gate the devtest mock job summaries to a subset of runs so the devtest
page also exercises the no-summary layout
<img width="521" height="232" alt="image"
src="https://github.com/user-attachments/assets/a1f2f20b-65bd-4d98-ba6a-b8135580a6de"
/>
Support `continue-on-error` for workflow jobs when aggregating an
Actions workflow run status.
Previously, `continue-on-error` was parsed from workflow YAML but was
not persisted or used when calculating the overall run result. As a
result, a failed job could incorrectly fail the entire workflow even
when the workflow explicitly allowed that job to fail.
This PR stores the parsed `continue-on-error` value on each action run
job and treats failed jobs with `continue-on-error: true` as successful
when computing the workflow run status, matching GitHub Actions
behavior.
## Changes
- Add `ContinueOnError` to `jobparser.Job`.
- Add `continue_on_error` to `ActionRunJob` with a `NOT NULL DEFAULT
FALSE` migration.
- Populate `ActionRunJob.ContinueOnError` when creating workflow run
jobs.
- Update workflow status aggregation so failed `continue-on-error` jobs
do not fail the overall run.
- Leave `resolveCheckNeeds` unchanged so dependent jobs still see the
job result as `failure` and are skipped by default.
## Compatibility
This is backward compatible.
If only the runner or only the server is updated, `continue-on-error`
continues to degrade to the previous behavior and is effectively ignored
until both sides support it.
Related runner PR: https://gitea.com/gitea/runner/pulls/1032
---------
Signed-off-by: bircni <bircni@icloud.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Fixes five N+1 / O(n) query patterns found across common user paths.
Each uses a bulk query that already existed elsewhere in the codebase.
| Location | Problem | Introduced in |
| -------------------------------- |
-------------------------------------------------------------------------------------------------------------------------------
| ------------- |
| `IssueList.LoadIsRead` | `.In("issue_id")` missing its arg — xorm
generates `WHERE 0=1`, so `IsRead` is **never** set; every issue always
appears unread | #29515 |
| `ParseCommitsWithStatus` | `GetLatestCommitStatus` called once per
commit (O(n) queries on commit list / PR commits tab) | #33605 |
| `getReleaseInfos` (release list) | `GetLatestCommitStatus` called once
per release for CI badges | #29149 |
| User milestone dashboard | O(n×m) nested loop matching milestones to
repos | #26300 |
| `findCodeComments` (PR diff) | `LoadResolveDoer` + `LoadReactions`
called per inline comment — up to ~150 queries on a PR with 50 comments
| #20821 |
---------
Co-authored-by: Lauris B <lauris@nix.lv>
Adds `created_unix` as the third column of the `c_u` composite index on
the `action` table, changing it from `(user_id, is_deleted)` to
`(user_id, is_deleted, created_unix)`.
Migration 337 drops and recreates the index. No data is touched.
## Root causes
#32333 introduced the `c_u` index to speed up dashboard queries, but
defined it as `(user_id, is_deleted)` — without `created_unix`.
#3368 The simple query is now efficient enough for the database to
actually use `c_u`, but because `created_unix` is absent from the index,
the database must load and sort **every** matching row before returning
the first page of 20.
The existing `c_u_d` index `(created_unix, user_id, is_deleted)` does
not help because its leading column is `created_unix`, which can't be
used for an equality seek on `user_id`.
Those two caused this issue:
https://github.com/go-gitea/gitea/issues/38075
With the fix, the database seeks directly to `(user_id=X,
is_deleted=false)` and walks `created_unix` in descending order,
stopping after 20 rows.
Fixes https://github.com/go-gitea/gitea/issues/38075
- Enforce org visibility on organization label read endpoints (private
org labels no longer leak to non-members).
- Block fork sync (`merge-upstream`) when the base repo is no longer
readable (stops pulling commits after a parent goes private).
- Remove `REVERSE_PROXY_LIMIT` / `REVERSE_PROXY_TRUSTED_PROXIES` from
the Docker `app.ini` templates (the `= *` default allowed
`X-WEBAUTH-USER` impersonation; reverse-proxy auth is now opt-in and
admin-configured).
- Enforce single-use TOTP passcodes across web login, password-reset,
and Basic-Auth `X-Gitea-OTP` (fixes a TOCTOU race and a stateless
replay).
- Re-check branch write permission for every ref in a push (the
pre-receive hook cached the first ref's result, letting a per-branch
maintainer-edit grant escalate to full repo write).
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Moves the "Hacking on Gitea" page out of the documentation website and into the repository as `docs/development.md`, so contributors find build and test instructions next to the code. The content has been cleaned up and corrected for in-repo use.
---------
Signed-off-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
- Enforce repository token scope on RSS/Atom feed endpoints so a PAT
without repo scope can no longer read private repo commit data.
- Block HTTP redirects during repository migration clones to prevent
SSRF reaching internal addresses via an attacker-controlled redirect.
- Redact the notification subject after repo access is revoked so
private issue/PR metadata is no longer leaked through the notification
API.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Closes#37670.
Today, org members in Gitea only see teams they're a member of. In
larger orgs that hurts onboarding and discoverability — there's no way
to look up which team owns what without asking around. GitHub solves
this with a per-team visibility setting; this PR brings the same model
to Gitea.
## What changes
- Every team gets a `visibility` setting:
- `private` *(default)* — only team members and org owners can see the
team. Same as today's behavior.
- `limited` — listable by any member of the organization. Members and
the repos the team has access to are visible too. Non-org-members still
see nothing.
- `public` — listable by any signed-in user.
- The Owners team visibility is fixed and cannot be changed via
settings.
- Existing teams default to `private`, so this is a no-op for anyone who
doesn't change anything.
## API
- `Team`, `CreateTeamOption`, `EditTeamOption` all gain a `visibility`
field (string enum: `private` | `limited` | `public`).
- `GET /orgs/{org}/teams` and `/orgs/{org}/teams/search` now apply the
same visibility rules as the web UI:
- site admins and org owners still see every team
- other org members see their own teams plus any `limited` or `public`
team
- `private` teams are no longer leaked through these endpoints
- Swagger/OpenAPI specs regenerated.
## UI
View from admin2 (not an owner):
<img width="1669" height="726"
src="https://github.com/user-attachments/assets/daf4bccb-644b-4426-b178-71963aeaf73b"
/>
View from admin (owner):
<img width="2559" height="863"
src="https://github.com/user-attachments/assets/4f22cebc-e9df-4fd2-8ed4-724d31fadb7a"
/>
---------
Signed-off-by: bircni <bircni@icloud.com>
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Fixes#38079
## Regression path
The layout previously had `.commit-status-item .status-context { flex: 1
}`,
which let the context fill remaining space and ellipsize. That rule was
dropped in #37517 ("Refactor pull request view (5)") when the row markup
moved to nested `.flex-text-block` wrappers, so nothing constrained the
left block anymore.
After:
<img width="832" height="242" alt="image"
src="https://github.com/user-attachments/assets/a20019f8-6016-40f7-8901-2808280dc093"
/>
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Follow-up to #37987, addressing the unresolved review comments on the
org members search form.
And fix more trivial problems together (see the commit titles)
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
[actions-proto-go v0.6.0](https://gitea.com/gitea/actions-proto-go) adds
a
`capabilities` field to `RegisterRequest` and `DeclareRequest`. This
lets a
runner advertise the transitional `cancelling` capability directly in
the proto
message instead of through the out-of-band mechanism we used while the
proto
bump was pending.
This PR:
- Bumps `gitea.dev/actions-proto-go` to `v0.6.0`.
- Drops the forward-compat `capabilityGetter` type-assertion shim and
the
`runnerRequestHasCancellingCapability` helper, reading
`GetCapabilities()`
directly (now part of the `declareRequest` interface).
- Removes the "capability state unknown → preserve existing value"
branch.
## Why the behaviour change is correct
The shim and the `(hasSupport, known)` two-value return only existed
because the
old proto had no `capabilities` field, so we couldn't tell "runner
doesn't
support it" from "we can't see the field." With v0.6.0 the field is
always
present. Since proto3 repeated fields have no presence, "no capabilities
sent"
now unambiguously means the runner does not advertise the capability, so
a
runner that omits `cancelling` is correctly recorded as
`HasCancellingSupport =
false`.
There is no regression: prior to this bump Gitea was on `v0.5.0`, where
the
type assertion always failed and `HasCancellingSupport` was therefore
never set
from requests — so no runner relied on the preserved-unknown path.
## Compatibility
The change is wire-compatible in both directions of version skew,
because the
new field uses a previously unused field number (8 on `RegisterRequest`,
3 on
`DeclareRequest`) and the transport uses the binary protobuf codec:
- **Old runner → new Gitea:** the runner omits the field; it decodes to
an empty
capability list. Registration/declaration succeed; the runner simply
doesn't
get the cancelling feature.
- **New runner → old Gitea:** the runner sends the field; the old
server's
generated code doesn't know the field number and silently ignores it.
Registration/declaration succeed.
The feature only activates once both server and runner are on `v0.6.0`.
## Summary
Two Gitea Actions workflow files that share the same `name:` and same
job name produced identical commit-status `Context` strings. Because
`GetLatestCommitStatus` groups by `context_hash` (derived from
`Context`), only one row was shown on the PR page — see #35699.
GitHub displays both rows even though they look identical. This change
does the same: the displayed `Context` is unchanged, but `ContextHash`
now mixes in the workflow file path so the two statuses remain distinct
in the dedupe query.
## Notes
- Workflows that omit `name:` now use the workflow file name in the
`Context` (e.g. `ci.yaml / build (push)`) instead of an empty `/ build
(push)`. This changes the `Context` string for unnamed workflows, so any
required-status-check rule that referenced the old string must be
updated after upgrade.
- For statuses created before this change (hashed from `Context` alone),
`createCommitStatus` reuses that legacy hash when a matching row is
still present, so in-flight pending statuses are superseded rather than
orphaned on upgrade.
Fixes#35699
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: silverwind <me@silverwind.io>
`ifNeedApproval` in `services/actions/notifier_helper.go` decided
whether a
fork PR's workflow run had to wait for maintainer approval. The bypass
clause
counted any prior `approved_by > 0` run for `(repo_id,
trigger_user_id)`, so
the very first Approve-and-run click on a contributor's fork PR
permanently
trusted that user for every future fork PR in the same repository —
including
PRs whose only change is the workflow YAML itself.
Approving a workflow *run* is not the same as merging *code*. This
change
aligns the gate with GitHub Actions' first-time-contributor model: trust
is
granted only after the user has had a pull request merged in the repo.
## Behavior change
- **Before**: one approval = permanent trust for that user in that repo.
- **After**: every fork PR is gated until the contributor has at least
one
merged PR in the repo.
Existing already-approved runs and merged PRs continue to work; only the
trust criterion for *future* fork PRs changes. Maintainers who rely on
the
implicit "approve once" trust will see the approval banner reappear
until
they merge a PR from that contributor.
- Add GitHub-style Actions **job summaries** support
(`GITHUB_STEP_SUMMARY` / `workflow/SUMMARY.md`) and render them on the
run Summary view.
- Store uploaded summaries internally in the DB (not as downloadable
artifacts).
- Add runtime-token endpoint for runners to upload summaries:
- `PUT
/api/actions_pipeline/_apis/pipelines/workflows/{run_id}/jobs/{job_id}/summary`
- Advertise support to runners via `RunnerService.Declare` response
header:
- `X-Gitea-Actions-Capabilities: job-summary`
- Devtest: extend `/devtest/repo-action-view/...` to include mock
`jobSummaries` for previewing UI rendering.
## Compatibility
- New Gitea + old runner: no summary upload → UI shows nothing (no
behavior change)
- New runner + old Gitea: capability not advertised → runner skips
upload (no behavior change)
## Screenshot:
<img width="2017" height="729"
src="https://github.com/user-attachments/assets/31f8b945-50c4-40e1-9f40-382901a53013"
/>
Fixes#23721
PR on gitea-runner https://gitea.com/gitea/runner/pulls/917
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
## Summary
- Redesign the Actions run summary header to follow GitHub Actions
layout: trigger info on the left, Status / Total duration / Artifacts
columns inline on the right
- Expose trigger user avatar, pull request link, and PR head branch info
from the run view API
- Update the workflow graph header to show the workflow filename (linked
to the run workflow file) and `on: <event>`, while keeping the
jobs/dependencies/success stats line
- Remove the redundant commit/workflow metadata row below the run title;
that information now lives in the summary bar
New:
<img width="1564" height="639"
src="https://github.com/user-attachments/assets/e6bc1623-c5fc-4e97-abc9-fde7f3c6aef9"
/>
Old:
<img width="2038" height="1038"
src="https://github.com/user-attachments/assets/0857f19a-8d3a-4da2-82fd-e9ebeb200062"
/>
Replaces https://github.com/go-gitea/gitea/pull/36721
---------
Co-authored-by: Giteabot <teabot@gitea.io>
Parse `Co-authored-by:` trailers from commit messages and surface
contributors as an avatar stack across the commit page, commits list, PR
commits tab, latest-commit row, blame, graph, and dashboard feed.
- Up to 10 visible 20px avatars, GitHub-style overlap (6px first stride,
4px between subsequent), `+N` chip for the rest.
- Label: 1 → name; 2 → `<a> and <b>`; 3+ → `<N> people` opens a Tippy
popup with all participants.
- Names and avatars link to the repo's commits-by-author search; fall
back to profile or `mailto:`.
- Trailer parsing uses `net/mail.ParseAddress`, scans only the trailing
paragraph, filters out the commit's own author/committer.
- Drops the non-standard `Co-committed-by:` emission on squash merge and
web edits.
Devtest: `/devtest/coauthor-avatars`.
Fixes#25521
----
<img width="353" height="277" alt="image"
src="https://github.com/user-attachments/assets/72092ceb-97ca-4b09-9557-0b72d3c5458e"
/>
<img width="533" height="328"
src="https://github.com/user-attachments/assets/11d0c8f8-8b3f-4f2e-9993-879f1c06bcc5"
/>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
User-supplied CODEOWNERS patterns were compiled without a match timeout,
so a crafted pattern (e.g. (a+)+) against a crafted file path could
backtrack for tens of seconds inside the PR creation transaction and
exhaust the database connection pool. Set MatchTimeout on each compiled
rule; the caller already treats match errors as non-matches.
---------
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The OAuth2 sign-in callback unconditionally set IsActive=true on the
local user row whenever the IdP authenticated them, silently undoing an
administrator's "Disable Account" action and granting the user a fresh
session in the same response. Treat the local IsActive flag as an
authoritative admin override: inactive users get a session and are
routed through the existing activate / prohibit-login pages by
verifyAuthWithOptions, matching the local-credentials sign-in path.
Adds an integration regression test that disables a linked local user
and asserts the row stays IsActive=false after a full OIDC callback.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
An authenticated SSH user could pass a malformed sub-verb (e.g.
`git-lfs-authenticate <repo> badverb`) so getAccessMode falls through to
AccessModeNone (0). The permission check in routers/private/serv.go then
evaluates `userMode < 0` which is always false, granting a valid LFS JWT
for any private repository. The HTTP LFS handler only validates the Op
claim on writes, so the token works for downloads.
Validate the sub-verb in runServ before calling getAccessMode and fail
fast for anything other than upload/download.
This PR adds an automated AgentScan workflow to help detect and handle
pull requests that appear to be created or authored primarily by
automated agents.
- If a PR is classified as `automation` or community-flagged, the
workflow:
- Adds the `possible bot` label,
- Posts a policy comment linking to the repository AI Contribution
Policy (`CONTRIBUTING.md#ai-contribution-policy`) and listing required
disclosures and checks,
- Optionally closes the PR if classification indicates an
automated/unwelcome submission.
- When the `action_task` row exists but the underlying dbfs/storage blob
is gone, `OpenLogs` returns a wrapped `os.ErrNotExist` which surfaces as
a 500 on the job logs endpoints.
- Translate it to the same `util.NewNotExistErrorf` shape already used
for unknown job ids / expired logs, so both the API
(`/api/v1/repos/.../actions/jobs/<id>/logs`) and the web download
handler return a clean 404 instead.
Fixes#37990.
The Workflow Dependencies graph in the Actions run details view had
hard-coded English strings.
Also in projects view and contributors view I found some hard-coded
strings.
The other items in the issue #37787 (Summary / All jobs / Run Details /
Workflow file / Triggered via / Total duration) were already wired
through ctx.Locale.Tr; their translations just need to land in the
non-English locale_*.json files via the translation pipeline.
Fixes#37787
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.8) <noreply@anthropic.com>
Adds bulk actions on the site-admin runner list
(`/-/admin/actions/runners`). Site admins can now select multiple
runners and **Delete**, **Disable**, or **Enable** them in one go
instead of clicking through each runner's edit page.
Scope is intentionally limited to the admin page. The user, org, and
repo runner pages keep their existing per-row UX — the shared list
template gates the bulk UI behind an `AllowBulkActions` flag set only by
the admin handler.
## Screenshots
<img width="1582" height="353"
src="https://github.com/user-attachments/assets/2125661f-aac0-4168-990a-97995a26abd2"
/>
---------
Signed-off-by: Nicolas <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
- When a commit subject is a bare URL, `linkProcessor` wrapped it in its
own `<a>` to that URL. Because HTML cannot nest anchors, the wrapping
default link (the action run / commit link) was lost and the action
title became unclickable — clicking it sent the user to the URL from the
commit message instead of the action log.
- Drop `linkProcessor` from `PostProcessCommitMessageSubject` so the
whole subject stays wrapped in the default link. URLs in subjects now
render as text inside that link; URLs in commit bodies are unaffected.
Fixes#37865
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
## Summary
- Add a Branch filter dropdown to the repo Actions run list web UI
- Wire `?branch=` query param through the web handler, matching the
existing REST API filter behavior
- Source the Branch dropdown from the indexed `branch` table (filtering
out deleted branches) instead of scanning `action_run.ref`, addressing
review feedback about unindexed columns
The Event filter was dropped after review: a static list of supported
events was noisy as UX, and querying distinct values from
`action_run.trigger_event` is slow because the column is not indexed.
`FindRunOptions.TriggerEvent` is kept for the REST API.
Closes#25042
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
If this also doesnt work we need to revert it ig
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
## Summary
- Add `before` and `after` fields to `PullRequestPayload` for
`synchronize` events
- Thread push old/new commit SHAs through the PR synchronize notifier
path (regular and Agit flows)
- Populate the fields in webhook and Actions event payloads so workflows
can access them via `github.event.before` and `github.event.after`
Fixes#33395
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Syncs `type/*` and `pr/breaking` labels from the PR title (Conventional
Commits) and folds the existing title lint into the same workflow so
labeling only runs once the title is valid.
- `tools/pr-title.ts`: shared title parser and label mapping.
- `tools/set-pr-labels.ts`: adds/removes labels via the GitHub API.
`type/*` and `pr/breaking` are fully synced (added and removed);
`skip-changelog` (chore/ci) and `topic/build` (build) are only added,
never auto-removed, so manual labeling is preserved.
- `pull-labeler.yml` now hosts `lint-pr-title` and `set-pr-labels`
(`needs: lint-pr-title`) under `pull_request_target`, required so fork
PRs get a writable token. Base-branch checkout only; no PR-head code
runs in the elevated context.
- Removes the superseded `pull-pr-title.yml` and the CI-only
`lint-pr-title` Makefile target.
---------
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Fixes an issue where users could not commit changes on a file which is
unprotected.
Fixes: #37655
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Giteabot <teabot@gitea.io>
- Introduce a “Bypass Protection Allowlist” on branch rules
(users/teams) alongside admins, with BlockAdminMergeOverride
still respected.
- Surface the allowlist in API (create/edit options, structs) and
settings UI; merge box now shows the red button +
message for bypass-capable users.
- Apply bypass logic to merge checks and pre-receive so allowlisted
users can override unmet approvals/status checks/
protected files when force-merging.
- Add migration for new columns, locale strings, and unit tests (bypass
helper; queue test tweak).
<img width="1069" height="218" alt="image"
src="https://github.com/user-attachments/assets/0b61bc2a-a27f-47f3-a923-613688008e65"
/>
Fixes#36476
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Codex GPT-5.3 <codex@openai.com>
Co-authored-by: GPT-5.2 <noreply@openai.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
## Summary
When comparing branches with **no common merge base** (e.g. unrelated
histories or orphan branches), `PageIsComparePull` is false and
`CommitCount` is zero. The compare template still showed
`repo.commits.nothing_to_compare`, which in German reads like the
branches are identical—even though the flash already explains there is
no merge base.
## Changes
- **`templates/repo/diff/compare.tmpl`**: Only render the grey “nothing
to compare” segment when `CompareInfo.CompareBase` is set.
<img width="1962" height="564"
src="https://github.com/user-attachments/assets/adc3b4a0-6f03-45da-b297-e15e5ad0aa79"
/>
Fixes#37642
---------
Signed-off-by: Nicolas <bircni@icloud.com>
When a workflow job failed, the API response reported all steps as
failed — even steps that had completed successfully before the failing
step. `ToActionWorkflowJob` was calling `ToActionsStatus(job.Status)`
for every step instead of `ToActionsStatus(step.Status)`, so the job's
overall conclusion was propagated to each step.
Each `ActionTaskStep` has its own `Status` field that tracks the actual
outcome of that step independently of the job result.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
fixes adding collaborative owners in Actions settings when the user or
organization name contains capital letters.
Fixes#37548
---------
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
## Summary
- Enforce **Conventional Commits** on PR titles (PRs are squash-merged,
so the PR title becomes the final commit message).
- Add a local `make lint-pr-title` target so contributors can validate
titles before pushing.
## Why
We squash-merge PRs, which means the final repository history is largely
shaped by **PR titles**. Enforcing a consistent Conventional Commits
format makes:
- **Release notes & changelogs easier to generate** (types like `feat` /
`fix` can be grouped automatically).
- **History easier to scan** (uniform structure, optional scopes,
explicit breaking changes via `!`).
- **Automation more reliable** (future tooling can infer category and
scope from the title).
## PR title format
```text
type(scope)!: subject
type: one of build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test
scope: optional (e.g. web, api, actions, repo, …)
!: optional, indicates a breaking change
subject: short, imperative, no trailing period
```
## Examples
```text
feat(web): add dark mode toggle
fix(api): avoid panic when repo is missing
chore(ci): lint PR titles with commitlint
refactor(templates): reduce duplication in repo list rendering
feat!: remove legacy OAuth endpoint
```
## Local testing
```text
make deps-frontend
make lint-pr-title PR_TITLE="feat(web): add dark mode toggle"
```
---------
Signed-off-by: Nicolas <bircni@icloud.com>
Co-authored-by: nb <nb@users.noreply.local>
Co-authored-by: GPT-5.2 <gpt-5.2@openai.com>
If a workflow is not in default branch the hooks could not be detected
Fixes#37169
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Giteabot <teabot@gitea.io>
`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: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This PR updates `CONTRIBUTING.md` for clarity (code review, maintainers,
PR workflow)
## Suggestion
- majors about every **three months**, with a more predictable cadence
from **v1.26** onward.
- target dates such as **v1.26.0** (April 2026), **v1.27.0** (June
2026), **v1.28.0** (September 2026), **v1.29.0** (December 2026).
- announce feature freeze **two weeks** before each release.
## Other doc changes
- Reviewing PRs: separate guidance for reviewers vs authors; small edits
to maintaining PRs, merge queue, commit messages, co-authors.
- Maintainers: clearer subsections; links to GitHub Docs for 2FA / GPG.
- Split the Contributing.md into more useful markdown files
---------
Signed-off-by: Nicolas <bircni@icloud.com>
`model.ReadWorkflow` succeeds for YAML that is syntactically valid but
fails deeper parsing in `jobparser.Parse` (e.g. blank lines inside `run:
|` blocks cause a SetJob round-trip error). Add
`ValidateWorkflowContent` which runs the full `jobparser.Parse` to catch
these cases, and use it in the file view, the actions workflow list, and
the workflow detection loop so users see the error instead of silently
getting a 500 or a dropped workflow.
Fixes#37115
Signed-off-by: Nicolas <bircni@icloud.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
`LoadSettingsForInstall` only ran `loadMailerFrom`, not
_loadRegisterMailFrom_ or _loadNotifyMailFrom_, so
Service.RegisterEmailConfirm and Service.EnableNotifyMail were never
read from app.ini on the install page.
Full startup runs those through loadMailsFrom; the install path was a
narrower subset and never included that step—an oversight from when
install-specific loading was added
Fixes#37112
Workflow run, job, task, and step durations could show **negative**
values (e.g. `-50s`) when `Stopped` was missing, zero (epoch), or
**before** `Started` (clock skew, races, reruns). The UI used
`calculateDuration` with no validation.
This change:
- Uses each row`s **Updated** timestamp as a **fallback end time** when
`Stopped` is invalid but the status is terminal, so duration
approximates elapsed time instead of `0s` or a negative.
- Keeps **`ActionRun.Duration()`** clamped to **≥ 0** when
`PreviousDuration` plus the current segment would still be negative
(legacy bad data).
Fixes#34582.
Co-authored-by: Composer <composer@cursor.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Add an optional Name field to webhooks so users can give them
human-readable labels instead of relying only on URLs. The webhook
overview page now displays names when available, or falls back to the
URL for unnamed webhooks.
Fixes#37025
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Treat Commit Status Warnings as errors
> The root problem is that the definition of "warning" are different
across systems.
>
> * Sometimes, "warning" is treated as "acceptable" (Gitea 1.25)
> * Sometimes, "warning" is mapped from "Result.UNSTABLE", which means
"there are test failures" and it is "failure" in Gitea
>
> **To avoid breaking existing users, the best choice is to revert the
behavior on Gitea side: treat "warning" as "error".**
https://github.com/go-gitea/gitea/issues/37042#issuecomment-4158231611
fixes https://github.com/go-gitea/gitea/issues/37042
---------
Signed-off-by: Nicolas <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
With Gitea 1.25.4 the workflow event for in_progress was not triggered
for Gitea Actions.
Fixes#36906
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
I tried to tighten the AI contribution policy and make the expectations
around AI-assisted submissions clearer.
---------
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
Co-authored-by: silverwind <me@silverwind.io>
Use shared repo permission resolution for Actions task users in issue
label remove and clear paths, and add a regression test for deleting
issue labels with a Gitea Actions token.
This fixes issue label deletion when the request is authenticated with a
Gitea Actions token.
Fixes#37011
The bug was that the delete path re-resolved repository permissions
using the normal user permission helper, which does not handle Actions
task users. As a result, `DELETE
/api/v1/repos/{owner}/{repo}/issues/{index}/labels/{id}` could return
`500` for Actions tokens even though label listing and label addition
worked.
---------
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
When the email field was submitted as empty in org settings (web and
API), the previous guard `if form.Email != ""` silently skipped the
update, making it impossible to remove a contact email after it was set.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Move UserDisabledFeatures context data into a shared SettingsCtxData
middleware for the /user/settings route group, so it is set consistently
on all pages (including Notifications, Actions, etc.) instead of only on
the handlers that remembered to set it individually.
Fixes#36954
Hey, I bumped Go to 1.26.1 and fixed a couple of things I ran into while
poking around.
### Changes
- Bump go.mod from 1.26.0 to 1.26.1 (security patch)
- Bump golangci-lint from v2.10.1 to v2.11.2
- Run make tidy, fmt, lint-go
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.6) <noreply@anthropic.com>
This PR adds official REST API endpoints to rerun Gitea Actions workflow
runs and individual jobs:
* POST /api/v1/repos/{owner}/{repo}/actions/runs/{run}/rerun
* POST /api/v1/repos/{owner}/{repo}/actions/runs/{run}/jobs/{job_id}/rerun
It reuses the existing rerun behavior from the web UI and exposes it
through stable API routes.
---------
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
I was testing typos-cli and fixed some misspelled wording here.
All changes are internal — no public API fields, database columns,
locale keys, or migration names are affected.
The banner allows site operators to communicate important announcements
(e.g., maintenance windows, policy updates, service notices) directly
within the UI.
The maintenance mode only allows admin to access the web UI.
* Fix#2345
* Fix#9618
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>