## Summary
This PR adds **scoped workflows** to Gitea Actions. Workflows defined
centrally in a "source" repository that automatically run on every
repository in scope: an organization's repositories, or (for instance
admins) every repository on the instance. Each scoped run executes in
the consuming repository's own context (its runners, secrets, and
branch) while its content is read from the source repository, so an org
or instance can mandate shared CI across many repositories without
copying workflow files into each one.
An owner or instance admin registers source repositories on a settings
page and can mark individual workflows as **required**. A required
scoped workflow cannot be opted out by a consuming repository and gates
its pull-request merges; an optional one can be disabled per repository.
Scoped workflows live under a dedicated `SCOPED_WORKFLOW_DIRS` (default
`.gitea/scoped_workflows`), kept separate from regular `WORKFLOW_DIRS`.
## Main changes
### Configuration
New `SCOPED_WORKFLOW_DIRS` setting, validated to not overlap with
`WORKFLOW_DIRS`. Default: `.gitea/scoped_workflows`
### Data model & migration
- New `action_scoped_workflow_source` table mapping a registering owner
(`owner_id`, where `0` = instance-level) to a source repository, with a
per-workflow `WorkflowConfigs` map.
- `ActionRun` gains `WorkflowRepoID` / `WorkflowCommitSHA` (the pinned
content source) and an `IsScopedRun` flag.
### Detection & run creation
On consumer events, scoped workflows from the effective sources (the
owner's own sources plus instance-level ones) are matched and turned
into runs that execute in the consumer's context, with content pinned to
the source repo's default-branch commit.
`on: workflow_run` and `on: schedule` are currently not supported.
### Opt-out
A consuming repository can disable an optional scoped workflow (tracked
separately from regular `DisabledWorkflows`); required scoped workflows
can never be disabled, opted out, or bypassed.
### Commit status
A scoped run's status context format is `"<source repo full name>:
<workflow display name> / <job> (<event>)"`
(for example: `my-org/scoped-workflows: db-tests / test-sqlite
(pull_request)`),
keeping it distinct from a same-named repo-level workflow and from other
sources.
### Required status checks
Admins mark workflows required and supply status-check patterns.
`EffectiveRequiredContexts` appends those patterns to the branch
protection's required contexts and they are matched
must-present-and-pass. If the status checks from scoped workflows fail,
the PR cannot be merged.
NOTE: scoped workflows' required status checks patterns can protect any
target branch that has a protection rule, even though the rule's "Status
Check" is disabled. A target branch with no protection rule cannot be
protected.
<details>
<summary>Screenshots</summary>
<img width="1400" alt="image"
src="https://github.com/user-attachments/assets/a5d1db33-15ec-487e-93be-2bc04b4e6643"
/>
</details>
### Reusable workflows (`uses:`)
A scoped workflow's local `uses: ./...` resolves against the source
repository. `uses:` directory validation honors the
instance-configurable `WORKFLOW_DIRS` and `SCOPED_WORKFLOW_DIRS`
(previously hardcoded to `.gitea`/`.github/workflows`).
### Manual dispatch
`workflow_dispatch` is supported for scoped workflows (web and API),
resolving inputs/content from the source repo.
### Performance
A process-local LRU cache keyed by source repo ID for the per-source
workflow parse, so instance-level and owner-level sources don't open the
source repo and parse workflow files on every event.
### UI
Org / user / admin pages to register and remove sources, search
repositories, and mark workflows required with their status-check
patterns. The repository Actions sidebar groups scoped workflows by
source with owner/instance labels and required/disabled badges.
<details>
<summary>Screenshots</summary>
Scoped workflows setting page:
<img width="1600" alt="image"
src="https://github.com/user-attachments/assets/9d19f667-97a5-4935-92b2-e53f105e3642"
/>
Consumer repo's Actions runs list:
<img width="1600" alt="image"
src="https://github.com/user-attachments/assets/a77241f9-0aa9-41aa-ba73-12a9a688cb64"
/>
- `Owner`: this is a owner-level scoped workflows source repo
- `Global`: this is a global scoped workflows source repo
- `Required`: this scoped workflow is required, repo admin cannot
disable it
</details>
---
Docs: https://gitea.com/gitea/docs/pulls/447
---------
Co-authored-by: bircni <bircni@icloud.com>
## 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.
This PR replaces a set of struct-based `Get` lookups with explicit
`db.Get` / `db.Exist` conditions in places where zero-value fields can
lead to ambiguous matches or incorrect records being returned.
The main goal is to make read paths deterministic and avoid accidentally
matching the wrong row when only part of a struct is populated.
### What changed
- replace many `db.GetEngine(ctx).Get(bean)` calls with explicit
`builder.Eq` conditions across models such as actions, admin tasks,
issues, pull requests, repositories, users, packages, redirects,
watches, stars, and follows
- use quoted column names where needed for reserved fields like `index`,
`type`, and `name`
- add dedicated user lookup helpers for:
- primary email
- OAuth login source / login name
- update sign-in and OAuth-related flows to use explicit individual-user
lookups instead of partially populated `User` structs
- tighten package property and Terraform lock lookups to avoid ambiguous
reads and updates
- keep existing fallback behavior where needed, while removing reliance
on zero-value struct matching
### User-facing impact
These changes primarily affect authentication and account lookup paths:
- email/username sign-in now re-fetches users through explicit keys
- OAuth2 auto-linking now resolves users by name or primary email
explicitly
- OAuth2 login/sync now looks up users by login source, login type, and
login name explicitly
- non-individual accounts are no longer implicitly matched through
partial user lookups in these flows
This should reduce the risk of incorrect account matches and make query
behavior more predictable across the codebase.
---------
Co-authored-by: bircni <bircni@icloud.com>
## 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>
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>
Removes the legacy `delete-button` handler (`initGlobalDeleteButton`)
and migrates all remaining usages to `link-action` and `show-modal` /
`form-fetch-action`.
Two handlers are adjusted for the new request shape: webauthn key delete
reads `id` from the query, and account deletion returns `JSONError` on
validation failure.
A E2E test ist added to cover one of the use cases.
Suggested in
https://github.com/go-gitea/gitea/pull/38046#discussion_r3414936737.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
- 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>
- 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>
* fix incorrect delayWriter call (there is already a defer call)
* split HookPostReceive into small functions
* fix incorrect HookPostReceiveResult response for errors
* fix incorrect AddRepoToLicenseUpdaterQueue call
* make sure repo home and branches page can work without default branch
* make sure default branch is always synchronized between database and
git repo, and fix FIXME
## 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>
Follow
https://docs.github.com/en/enterprise-server@3.20/rest/issues/assignees?apiVersion=2022-11-28Fix#33576
And it also fixed some possible dead-lock problem.
---------
Signed-off-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Nicolas <bircni@icloud.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
`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>
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>
Follow up #37478
## Changes
1. #37478 doesn't support absolute URL in `uses`. This PR provides
partial support for URL-style reusable workflow references. A reusable
workflow can now be referenced by an absolute URL, as long as it points
to the local Gitea instance:
```yaml
jobs:
call:
uses: https://your-gitea.example.com/OWNER/REPO/.gitea/workflows/ci.yaml@v1
```
2. Show an error message in the UI for invalid `uses`.
<img width="1600" alt="image"
src="https://github.com/user-attachments/assets/21b34e61-bf10-4af1-b9fd-4ee4e9fde049"
/>
3. Fix reusable caller cancellation issue. A reusable caller's status is
aggregated from its children, so cancellation should processes a
caller's descendants deepest-first.
---------
Signed-off-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: bircni <bircni@icloud.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>
Fixes https://github.com/go-gitea/gitea/issues/37286
Automatic release notes for the first release in a repository were empty
when there was no previous tag.
Before this change, the release notes generator used the tag name to
build the changelog link, but reused that state for pull request
collection. When `PreviousTag` was empty, the PR collection logic did
not scan a useful commit range, so merged pull requests were omitted
from the generated notes.
This pull request fixes that by decoupling the internal PR collection
range from the rendered changelog link:
- when a previous tag exists, behavior stays unchanged
- when no previous tag exists, release notes collect merged pull
requests from the full reachable history up to the target tag
- the displayed full changelog link for the first release still uses the
existing `/commits/tag/{tag}` format
Tests were updated to cover:
- generating notes for a repository with no previous tags
- including merged pull requests before the first tag
- preserving existing behavior when a previous tag exists
Two test-only changes that cut the `-race` backend unit job's critical
path, with no behavior change.
- **`modules/auth/password/hash`** — `TestHashing`/`TestVectors`
exercised the CPU-bound KDFs (scrypt `N=65536`, pbkdf2, bcrypt, argon2)
serially on one core. Marking the subtests `t.Parallel()` fans them
across cores. The hasher registry they read is only mutated by the
non-parallel `Test_registerHasher`, so this is race-free.
- **`services/release`** — `TestRelease_Update`/`TestRelease_createTag`
slept `6x time.Sleep(2s)` only to cross the 1-second `CreatedUnix`
boundary. Replaced with an advancing mocked clock (`timeutil.MockSet`),
making the timestamp assertions deterministic and removing the real
waits.
---
This PR was written with the help of Claude Opus 4.8
Co-authored-by: Claude (Opus 4.8) <noreply@anthropic.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
## Summary
This fixes an OIDC sign-in edge case where a stale `external_login_user`
record can still point to an organization or a deleted user.
In that situation, Gitea may keep resolving the external login to the
wrong account during sign-in. For affected instances, this matches the
behavior reported in #36439 and #37812, where a user signing in with
OIDC/Entra ID could appear as an organization, or hit a 404 after that
organization was removed.
## What changed
- validate the user resolved from `external_login_user` during
OAuth2/OIDC login
- ignore stale links when the linked user no longer exists
- ignore stale links when the linked user is not an individual user
- remove the stale external login row so the sign-in flow can relink the
external account to the correct user
## Related
- Fixes#37812
- Related to #36439
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.8) <noreply@anthropic.com>
## Summary
This PR improves reusable workflow support for Gitea Actions. The
parsing of the called workflow now happens on Gitea side, not on the
runner. When the caller becomes ready, Gitea fetches the called workflow
source, parses it, and inserts each child job into the database as a
`ActionRunJob` linked to the caller via `ParentCallJobID`. As a result,
every callee job is dispatched as its own task and its logs surface as
an independent job entry in the UI, rather than being inlined into the
caller's "Set up job" step.
This PR supports two kinds of `uses` :
- same-repo call: `uses: ./.gitea/workflows/foo.yaml`
- cross-repo call: `uses: OWNER/REPO/.gitea/workflows/foo.yaml@REF`
## **⚠️ BREAKING ⚠️**
External reusable workflows (`uses:
https://other-gitea-instance/OWNER/REPO/.gitea/workflows/test.yaml@REF`)
are no longer supported. To keep using them, clone the repositories to
the local instance.
## Main changes
### Execution model
- Each caller job carries `IsReusableCaller=true` and won't be fetched
by runners.
- `ParentCallJobID` can link a called job to its caller.
- Caller status is derived from its direct children.
### Workflow syntax
- `jobparser` now supports parsing `on: workflow_call` trigger with
`inputs:`, `outputs:`, and `secrets:` declarations.
- **Max nesting depth**: capped at `MaxReusableCallLevels = 9`, which
means a top-level caller may have at most 9 nested callers below it.
- **Cycle prevention**: at expansion time, `checkCallerChain` walks the
caller's ancestor chain via `ParentCallJobID` and rejects if the same
`uses:` string appears anywhere upstream (`reusable workflow call cycle
detected`). This catches both direct (`A -> A`) and indirect (`A -> B ->
A`) cycles.
### Cross-repo access
- To share reusable workflows from private repos, use `Collaborative
Owners` introduced by #32562
### Rerun semantics
- `expandRerunJobIDs` partitions the latest attempt's jobs into:
- a **rerun set**: jobs being rerun + downstream siblings within the
same scope.
- an **ancestor set**: reusable callers whose only *some* descendants
are being rerun (the caller itself is not).
- Cloning behavior for callers in `execRerunPlan`:
- **Caller is fully rerun** (caller's `AttemptJobID` in `rerunSet`):
none of its descendants are cloned. The caller is cloned with
`IsCallerExpanded=false`, and re-expansion (which reinserts the children
fresh) happens later when the resolver brings the caller to `Waiting`
again.
- **Caller is in ancestor set** (only some descendants rerun): the
caller is pass-through (`Status` will be updated by its fresh children).
Its non-rerun descendants are also pass-through clones (point
`SourceTaskID` at the original task). Their `ParentCallJobID` is
remapped to the new attempt's caller row.
### UI
- Job list in `RepoActionView.vue` is now tree-shaped: callers indent
their children. Callers default to collapsed.
- New caller detail page using `WorkflowGraph` to show direct children
only; the run summary's `WorkflowGraph` shows top-level callers and
their immediate descendants.
### Known trade-offs
- **Caller expansion runs inside the enclosing write transaction.**
`expandReusableWorkflowCaller` performs a git read of the called
workflow while holding the row locks that update the caller and insert
its children. This is intentional: the caller-row update and child-row
inserts must commit atomically. None of the call sites is hot (each
caller is expanded once per attempt), so the trade-off is acceptable.
- **A malformed `if:` expression on a job leaves it `Blocked`
silently.** `evaluateJobIf` now runs server-side as part of resolver
passes; deterministic expression errors (typos, undefined context
fields) are logged but do not surface in the UI. This is the same
behavior the resolver already had for concurrency-expression errors.
Distinguishing transient DB errors from user-authored expression errors
and writing the latter back as `StatusFailure` is a follow-up.
#### Screenshots
<img width="1600" alt="image"
src="https://github.com/user-attachments/assets/bfaa9b7a-07e9-4127-8de9-a81f86e82828"
/>
<img width="1600" alt="image"
src="https://github.com/user-attachments/assets/8af109b3-ef28-4b53-aaad-d4632b923224"
/>
## References
-
https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows
-
https://docs.github.com/en/actions/reference/workflows-and-actions/reusing-workflow-configurations
---
Replace #36388
---------
Signed-off-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
### Description
Replaces all remaining direct `gopkg.in/yaml.v3` imports with
`go.yaml.in/yaml/v4` across models, modules, routers, services, and
integration tests. `gopkg.in/yaml.v3` moves from a direct to an indirect
dependency in `go.mod`.
#### API compatibility
The yaml.Node type, node.Kind/node.Content traversal style
(modules/markup/markdown/convertyaml.go), and the
UnmarshalYAML(*yaml.Node) interface signature
(modules/optional/serialization.go) are all preserved in v4 — no
call-site changes were required beyond the import path.
**Related:**
- https://github.com/go-gitea/gitea/pull/36564#issuecomment-4526536805
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.8) <noreply@anthropic.com>
## Summary
Fixes#37528
This PR makes the workflow dispatch API reject workflows that do not
declare `workflow_dispatch`. Previously, `POST
/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches` could
create an `ActionRun` for a workflow that only declared another event
such as `push`.
The service now validates that the target workflow has a
`workflow_dispatch` trigger before inserting the run. The API maps that
validation failure to `422 Unprocessable Entity`, matching existing
validation failures in this handler.
The regression test creates a push-only workflow, dispatches it through
the public API, asserts the `workflow_dispatch` validation message, and
verifies that no run was inserted.
## Disclosure
Developed with assistance from OpenAI Codex.
---------
Co-authored-by: Nicolas <bircni@icloud.com>
## Background
`MAX_CREATION_LIMIT` applies to whoever owns a new repository, with no
distinction between individual users and organizations. Admins who want
different limits for the two - most commonly "block personal repos but
let orgs create freely" - currently have to set per-user / per-org
overrides on every entity.
## Changes
Adds two new `[repository]` settings:
- `USER_MAX_CREATION_LIMIT`: global limit for individual users
- `ORG_MAX_CREATION_LIMIT`: global limit for organizations
`MAX_CREATION_LIMIT` is kept as a shortcut: when set, it becomes the
default value for both new keys. When the new keys are explicitly
configured, they take precedence. Deployments that only set
`MAX_CREATION_LIMIT` see behavior identical to now.
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
In dev mode `/api/swagger` returned HTTP 500 (`Failed to locate local
path for managed asset URI: css/swagger.css`): the backend synthesised
asset keys from the Vite entry name instead of reading the manifest,
which only worked by coincidence and broke once a source file name
diverged from its entry name.
This keys the manifest by its source path (e.g. `web_src/js/index.ts`)
and resolves entries directly — hashed `file` in prod, dev-server source
in dev. A new `AssetCSSLinks` helper renders a JS entry's stylesheet
`<link>` tags from the manifest (the entry's CSS plus the CSS of its
statically-imported chunks).
Fixes: https://github.com/go-gitea/gitea/issues/37830
Fixes: https://github.com/go-gitea/gitea/pull/37832
Fixes: https://github.com/go-gitea/gitea/pull/37876
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: prakhar0x01 <prakharporwal2004@gmail.com>
Co-authored-by: Nicolas <bircni@icloud.com>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
Co-authored-by: Giteabot <teabot@gitea.io>
- Update `github.com/alecthomas/chroma/v2` to `v2.25.0`.
- Migrate `github.com/dlclark/regexp2` to `/v2` (incorporates
https://github.com/go-gitea/gitea/pull/37664); drop the renovate pin.
- Replace the unmaintained `github.com/dimiro1/reply` (the last consumer
of `regexp2` v1 in our own code) with a small built-in reply parser for
incoming mail.
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
Co-authored-by: Nicolas <bircni@icloud.com>
Currently unmaintained package `github.com/jaytaylor/html2text` is
replaced using `replace` directive. Instead, the correct package
`github.com/Necoro/html2text` should be referenced directly in code.
---------
Co-authored-by: Giteabot <teabot@gitea.io>
## 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>