Pushing a commit whose message mentions `owner/repo#1` adds a
"referenced this issue from a commit" event to that issue, with the
commit subject and a link into the pushed repository. The event never
recorded that repository, so a commit in a private repository
referencing a public issue showed its subject and repository path to
anonymous visitors of the issue page and the timeline API.
- The event now records the pushed repository in `RefRepoID`, and is
only shown to viewers who can read that repository's code, including the
issue's own. Older events have no recorded source and stay as they are.
- As on GitHub, the event stays attributed to the repository the commit
was first pushed to, even after the commit reaches another repository,
and is removed when that repository is deleted.
---------
Co-authored-by: silverwind <me@silverwind.io>
Since Playwright 1.56, a WebKit click on a native submit button can
return before the form navigation starts, so `toHaveValue` passed on the
old page with the value `fill()` just set, and the next step raced the
reload.
Assert the textarea's text instead, its server-rendered default, which
only the reloaded page has.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Allows an LDAP auth source to list multiple comma-separated servers in
the Host field so logins continue working when one server is
unreachable. Entries can be plain hosts using the configured Port and
Security Protocol fields or `ldap://`, `ldaps://`, or `ldap+starttls://`
URLs with their own port and protocol (as proposed in
https://github.com/go-gitea/gitea/pull/31649#issuecomment-2505555133),
with TLS protocol consistency enforced so an entry cannot silently
downgrade encryption.
Hosts are tried in order with a 250 ms head start each so an
unresponsive server does not block logins for the full 60 s connect
timeout, and only connection failures fail over to the next host so
failed binds are not retried across servers. Existing single-host
configurations continue to work unchanged without a migration.
Docs: https://gitea.com/gitea/docs/pulls/635
Fixes https://github.com/go-gitea/gitea/issues/6898
/claim #6898
---------
Co-authored-by: silverwind <me@silverwind.io>
The board reported every card of the target column on each drag, and the
server wrote all of them into that column. A stale board in another
browser therefore pulled cards moved by someone else back to their old
column.
The client now sends only the dragged issue and its predecessor, and the
server computes the position in one transaction:
- Only the dragged issue changes column, the others are just renumbered.
- A predecessor that is no longer in the column puts the card at the
end.
Added an e2e test.
Fixes https://github.com/go-gitea/gitea/issues/21243
Fixes https://github.com/go-gitea/gitea/issues/32070
Fixes https://github.com/go-gitea/gitea/issues/13498
---
Assisted-by: Codet:claude
---------
Co-authored-by: silverwind <me@silverwind.io>
On the dashboard issue and pull request pages, author and assignee
filters only accept direct username input since candidate lists across
all accessible repositories cannot be pre-fetched. However, the dropdown
inherited several confusing artifacts from the repository-level user
search:
- Displayed a misleading "Shows a maximum of 30 users" tooltip even
though no remote user list was fetched.
- Used "Search users" as placeholder rather than indicating direct
username input.
- Pressing Enter in the input had no effect, requiring mouse selection.
- Any active username filter was not reflected in the dropdown menu on
open.
This change:
- Only renders the 30-user tooltip when a remote search URL is provided.
- Uses "Filter by username…" as placeholder when no remote search URL
exists.
- Applies the filter on Enter keypress.
- Ensures the active username is displayed and selected when opening the
menu.
Fixes: https://github.com/go-gitea/gitea/issues/40275
Fixes: https://github.com/go-gitea/gitea/issues/33426
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
`PUT/DELETE /repos/{owner}/{repo}/issues/{index}/lock` passed
`ctx.ContextUser` as the doer of `LockIssue` / `UnlockIssue`. In the
repo routes that is the owner of the repository, so on a repository of
an organization the lock and unlock timeline comments were created by
the organization instead of the user who did it. The web handler already
uses the signed-in user; this uses `ctx.Doer` as well.
Adds a subtest `DoerOnOrganizationRepo` to the existing
`TestAPILockIssue`: a member of an organization locks and unlocks an
issue of an organization repository, and the comments must be posted by
that member.
Found while testing audit logging of the issue lock events downstream.
_Authored by Codet (Claude)._
If the stderr output is not handled, then it needs to use RunWithStderr
to make sure the git error message is included in the returned error.
Otherwise, when the git command fails, the error log won't show useful
information.
Ref:
* #39795 : no useful information in log messages
*
https://github.com/go-gitea/gitea/pull/40160#pullrequestreview-5752720866
`pull_request` workflow runs checked out the PR head, so a PR could pass
CI and still break the base branch once merged.
Gitea now maintains `refs/pull/<index>/merge`, a test merge of the head
into the base branch, for open pull requests. It is updated on creation
and mergeability checks and removed on close or merge.
- `pull_request` runs read their workflows from the test merge and
expose it as `github.ref`, `github.sha` and
`github.event.pull_request.merge_commit_sha`. Commit statuses, the API's
`head_sha`, `workflow_run` payloads and `paths` filters stay on the
head.
- Runs fall back to the head when the merge conflicts, the pull request
is closed or git is older than 2.40.
- Hidden refs keep each run's test merge (`refs/gitea/run/<id>`) and
merged commits (`refs/gitea/pull/<index>/merged`) from `git gc`, for
reruns and after the base branch is deleted or force-pushed.
Fixes: https://github.com/go-gitea/gitea/issues/27039
Fixes: https://github.com/go-gitea/gitea/issues/13134
Fixes: https://github.com/go-gitea/gitea/issues/30972
Docs: https://gitea.com/gitea/docs/pulls/617
---------
Co-authored-by: silverwind <me@silverwind.io>
Updates JS, Go, Python and Actions dependencies, including the stylelint
18 and stylelint-config-recommended 19 majors.
- Update clippie to 4.3.1, which fixes copy buttons in browsers without
`ClipboardItem` such as Pale Moon
- Exempt the markup `tt` selectors from the new `selector-no-deprecated`
rule, the sanitizer allows `<tt>`
- Replace the `stylelint-value-no-unknown-custom-properties` plugin with
stylelint's `referenceFiles` and the core `no-unknown-*` rules
- Remove dead dropdown loading icon styles that referenced deleted
keyframes
Fixes: https://github.com/go-gitea/gitea/issues/40287
Assisted-by: Claude Code:claude-opus-5-5
---------
Co-authored-by: silverwind <me@silverwind.io>
Every line of file, blame, diff and code preview views goes through
`EscapeControlHTML`, which streamed it through a 32 KiB read buffer and
copied every tag and text part. Views render the `EscapeStatus` before
the content, so every caller holds the complete HTML and streaming can't
help. It is now escaped in one pass over the string and returned
unchanged when nothing needs escaping.
- Skip text of printable ASCII, tabs and newlines, such lines return
immediately
- Render markup and wiki pages into a buffer instead of a pipe and
goroutine, and remove `EscapeControlReader`
- Analyze long text parts in windows overlapping by the detection
context, which bounds memory without changing results
- Escape invalid bytes before a tag, they were moved into the tag part
unescaped and could hide a `<math>` tag
| | before | after |
|---|---|---|
| ASCII line | 5.1 µs, 59 allocs | 0.08 µs, 1 alloc |
| line with one `é` | 3.9 µs, 30 allocs | 0.29 µs, 3 allocs |
| 150 KB document | 2.5 ms, 5.6 MB | 0.27 ms, 0.6 KB |
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Removing a milestone deadline with `"due_on": null` was untested, only
`""` was. On release/v28 the null case returned HTTP 500
(https://github.com/go-gitea/gitea/pull/40171). main already handles it,
this adds the case to the existing test so it stays that way.
Remove the "adopt unadopted repositories" feature. Adopting assumes a
fixed repository storage path on disk, which makes the storage path hard
to change. It is mostly used once for migrations, and the [Gitea
importer](https://gitea.com/gitea/importer) covers that after the
repositories are moved out of the repository root. The docs are updated
in https://gitea.com/gitea/docs/pulls/639.
- Remove the admin and user adopt pages, the `/admin/unadopted` API
endpoints and the adopt notifier hook
- Remove the `ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES` and
`ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES` settings
- The user "Repositories" settings page only lists the user's
repositories
- Repository files without a database record must now be removed from
disk by the administrator
Assisted-by: Codet:claude
---------
Co-authored-by: silverwind <me@silverwind.io>
Several code paths skipped checks that their siblings already perform.
- Code comment replies are rejected when they target a review of another
pull request or another user's pending review
- OAuth2 refuses tokens for users who can't sign in, and introspection
reports their tokens and already used refresh tokens as inactive
- AGit proc-receive runs the same pull request permission and wiki
checks as pre-receive
- Repository transfer drops the repository's owner-level scoped workflow
source registrations, and registrations whose source the owner no longer
owns are ignored
- The web editor's base branch copy into a fork and the fork's upstream
divergence info require read access to the base repository
- LFS downloads require sign-in under strict `REQUIRE_SIGNIN_VIEW`, like
git smart HTTP
- Unknown AGit target branches are reported as an unexpected ref again
Copying lines from a diff, file view, blame, code search or code preview
and pasting them into a comment on Gitea or GitHub produced a markdown
table with escaped pipes and stripped indentation. Both use
`@github/paste-markdown`, which converts copied HTML tables unless they
carry `data-paste-markdown-skip`, as GitHub's own code tables do.
Gitea's code tables now set it too.
The test is a bit more complicated because of a upstream bug which
causes `paste-markdown` to insert pasted content twice, once
https://github.com/github/paste-markdown/pull/128 is merged and
released, the negative assertion could be removed.
---------
Signed-off-by: silverwind <me@silverwind.io>
Derive theme colors from a few base colors with `color-mix(in srgb, …)`
and make the colorful palette coherent, so custom themes that import a
Gitea theme can override a base color and get matching shades.
- Every `-light`, `-dark-1` and `-dark-2` shade follows one rule for all
hues, fixing dark orange shades copied from the light theme, black
shades that barely moved and neon tints
- Base colors move at most 4 OKLab units toward a common lightness, the
median in dark and red in light, yellow stays unchanged
- Text, secondary, console, message, surface and alpha colors derive
from their own base, alpha variants now match their names
- Added and removed diff colors share one lightness per level in all
themes, including colorblind ones
- Chart.js and the external render iframe resolve `color-mix()` values
to hex through `getComputedStyle`
- Remove unused `secondary-dark-12`, `-13`, `shadow-opaque` and
`workflow-edge-hover`
Fixes: https://github.com/go-gitea/gitea/issues/30160
<img width="880" alt="Components with the largest color changes, dark
and light, before and after"
src="https://github.com/user-attachments/assets/a9d27636-e3bb-46fa-a3ac-3fec33fd9806"
/>
<img width="880" alt="All theme colors per family, before and after,
unchanged ones dimmed"
src="https://github.com/user-attachments/assets/9977dde3-0749-4926-b110-18c0dc31d632"
/>
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Gitea accepted unlimited wrong TOTP passcodes, so anyone holding an
account's password could enumerate the 6-digit code in minutes. This
allows 5 failed attempts per account, then refuses TOTP until 15 minutes
after the last one.
- The count lives in the cache, and the global lock spans the check,
validation and counting, so new sessions and concurrent requests cannot
widen it. With Redis, both are shared across instances
- Applies to every TOTP check: the web 2FA page, account recovery and
Basic auth with `X-Gitea-OTP` (API, git, packages)
- Only wrong or replayed passcodes count, server errors do not
- Scratch codes and WebAuthn stay usable as the recovery path while
locked
- Failures log `Failed authentication attempt for <user> from <ip>`,
which the fail2ban filter already matches
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Commit, compare and SHA links render the SHA in code with the rest of
the URL after it, and links to other repositories get an `owner/repo@`
prefix like references do.
- Abbreviated SHAs, branch compares and `<url>` autolinks now render as
commit links
- Pull request commit URLs no longer break into an issue reference plus
stray text
- Only routes that take a SHA are shortened, so file and API URLs stay
plain
Replaces https://github.com/go-gitea/gitea/pull/36725
<img width="620" alt="Commit links before and after"
src="https://github.com/user-attachments/assets/5919c8a0-94e3-4148-ad11-6944bf790572"
/>
Some useful rules from my past experiences, feel free to suggest more.
Dark theme screenshots is subjective of course, but I prefer them.
---------
Signed-off-by: silverwind <me@silverwind.io>
Pending reviews created through the API went through `SubmitReview`,
which added a timeline entry and sent notifications before the review
was submitted, so submitting it later showed the review twice.
- Pending reviews skip the timeline entry and notifications until
submitted, like on GitHub
- `CreatePullReview` rejects comments without a `path` or a positive
line with 422, like GitHub
- Already duplicated reviews stay as they are
Fixes https://github.com/go-gitea/gitea/issues/40259
Assisted-by: Claude Code:claude-opus-5-5
---------
Co-authored-by: silverwind <me@silverwind.io>
Runs triggered by `workflow_dispatch` never created a commit status, so
the commit showed no sign that a workflow ran on it.
- Post the status on the run's commit, e.g. `ci / e2e
(workflow_dispatch)`. `schedule` runs stay unchanged.
- These statuses don't count for pull requests. They don't show in a
PR's checks and can't satisfy or fail its required checks, even through
broad globs like `ci / e2e*`.
Closes https://github.com/go-gitea/gitea/issues/39639
---------
Co-authored-by: silverwind <me@silverwind.io>
Changing a pull mirror address with an empty password field carried the
stored credentials over to the new address. The password is write-only,
so a repo admin who did not set up the mirror could send the owner's
token to a host they control, or pull another repository on the same
host that the token can read.
Stored credentials are now reused only when the address is unchanged,
like a changed username already drops the stored password. The API
cannot change the address and push mirrors never reuse credentials, so
both are unaffected.
---------
Co-authored-by: silverwind <me@silverwind.io>
Concurrent requests could create teams with the same name in one
organization, because only the service checked the name. Editing such a
team, or renaming a team to a taken name, then failed with a 500.
- Add a unique index on `team (org_id, lower_name)`. Migration 368
renames every duplicate except the oldest to a free `<name>-<n>` and
logs each rename. IDs, members, repositories and permissions are kept,
but lookups by the old name (CODEOWNERS, group-to-team sync, mentions,
URLs) now reach only the kept team.
- Team writes report a name rejected by the index as
`ErrTeamAlreadyExist`, so duplicates, concurrent ones included, return
409 from `POST /orgs/{org}/teams` (previously 422) and `PATCH
/teams/{id}`, and a form error in the web UI.
Fixes https://github.com/go-gitea/gitea/issues/39632
---------
Co-authored-by: silverwind <me@silverwind.io>
Byte slicing like `t.Description[:255]` can split a multi-byte character
and store invalid UTF-8. Team descriptions were also cut to 255 bytes
although their `MaxSize(255)` validation allows 255 characters.
Team descriptions now truncate by runes. Signup user agents, Actions log
lines and access log request IDs use `util.TruncateStringBytes`, which
now returns short strings untouched.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Adds a Terraform module registry package type implementing the [module
registry
protocol](https://developer.hashicorp.com/terraform/internals/module-registry-protocol),
so `terraform init` and Terragrunt `tfr://` can resolve modules from
Gitea:
```hcl
module "vpc" {
source = "gitea.example.com/owner/vpc/aws"
version = "~> 1.0"
}
```
- Modules are uploaded with `PUT
/api/packages/-/terraform/modules/{owner}/{name}/{provider}/{version}`
as a `.tar.gz` with the module at its root, stored verbatim and
immutable.
- `X-Terraform-Get` is a short-lived signed URL because Terraform sends
no credentials when downloading the archive.
- The package page shows install snippets, README, the root module's
inputs, outputs and providers, and `modules/*` submodules.
- Metadata is read by a small in-tree HCL scanner instead of
`hashicorp/hcl/v2`, which would add 5 modules and ~1.4 MB to the binary.
Docs: https://gitea.com/gitea/docs/pulls/433
---------
Signed-off-by: Michael Lopez <mickael.lopez@gmail.com>
Co-authored-by: TheFox0x7 <thefox0x7@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
Issue references next to non-ASCII characters, common in Chinese and
Japanese text, are ignored: `修复#1`, `(#2)` or `Closes #3,` create no
link or cross reference.
Like on GitHub, any non-ASCII character may now precede `#123`, `!123`
or `owner/repo#123`, and anything but a word character may follow it.
Dead trailing-space handling in the reference and mention search is
removed.
I used Claude Code to write this change.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Several code paths skipped checks their siblings perform, or cleaned up
more than they validated.
- Official team review requests now count, and removing one no longer
marks an unrelated review official
- Release edits only delete files of validated attachments
- Rejected container uploads no longer delete an existing blob
- Attachment removal only removes the uploader's own pending uploads
- OpenID sign-in checks the allow and block lists against the normalized
identifier
- HTTP git access is rejected while a repository is being created, like
SSH
---------
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The branch protection API handled push and force push options
inconsistently. Create dropped force push unless push was enabled, and
edit ignored allowlist fields unless their parent was set to true in the
same request.
- Create treats force push independently of push, like edit and the web
UI. Enforcement is unchanged.
- Edit applies `enable_push_whitelist`, `enable_force_push_allowlist`
and both deploy key flags against the rule's resulting state, like the
deletion fields. `{"enable_push_whitelist": false}` now disables the
whitelist instead of being ignored.
- Documents that `enable_force_push_allowlist` requires
`enable_force_push` on create.
Fixes https://github.com/go-gitea/gitea/issues/35121
---------
Co-authored-by: silverwind <me@silverwind.io>
The post-receive hook syncs pushed branches to the database, but
`is_empty` and the default branch of an empty repository were only
updated later by the `push_update` queue. Until then the API treated the
repository as empty (branch list `null`, `GET /branches/{branch}` 500),
and the queue overwrote the default branch chosen by the hook or set via
the API.
`SyncBranchesToDB` now initializes an empty repository and repairs a
default branch missing in git in its locked transaction, so the state is
final when `git push` returns. The push queue and web file edits retry
it while the repository is still empty.
- The default branch is the configured one if it exists, else `main`,
else `master`, else the first branch by name, like GitLab. HEAD follows
it.
- A first push of several branches no longer depends on push order. The
push that initializes a repository still prints no pull request hints,
like GitHub.
---------
Co-authored-by: silverwind <me@silverwind.io>
The Firefox mermaid e2e test fails intermittently with `Clipboard read
operation is not allowed.`, for example in
https://github.com/go-gitea/gitea/actions/runs/41281998298/job/124776072700.
Firefox rejects `readText()` when it overlaps the page's still pending
clipboard write, and `expect.poll` does not retry errors thrown by its
callback, so one overlapping read fails the test.
https://github.com/go-gitea/gitea/pull/39838 only removed the paste
prompt, not this rejection.
- A new `assertClipboardText` helper treats a rejected read as a
mismatch so it is polled again
- The bot token copy test had the same race and uses it too
_Written by Claude (Opus 5.5)._
Pure refactors with no behavior change, mostly reusing existing helpers
and dropping duplicated or dead code. Net −222 lines.
- Reuse existing helpers instead of inlined copies: `getIssueIndex`,
`NewLabel`, `ToCommitUser`, `GetPackageFileDescriptors`,
`util.SplitTrimSpace`
- Merge copy-pasted blocks in review comment deletion, project deletion
and the pre/post-receive hook templates
- Drop dead code: an error `toStringKeys` never returned, impossible
`bytes.Buffer` write errors, redundant nil-map branches, a `base64`
wrapper
- Use stdlib where it fits: `strings.Cut`, `bytes.IndexAny`,
`cmp.Compare`, `min`/`max`, `slices.Concat`, map lookups instead of key
scans
Gitea picks the latest release by commit date, while GitHub keeps an
explicit pointer, so repositories migrated from GitHub could show a
different latest release than their source. Fixes
https://github.com/go-gitea/gitea/issues/35273.
- Add a "Set as Latest Release" checkbox and GitHub's `make_latest` API
option. Without them, the newest stable release by commit date stays the
latest.
- Migrations from GitHub and Gitea carry over the source's latest
release.
- Migrated releases are dated by their commit like native ones, and
equally dated ones keep the source's order.
<img width="355" height="122" alt="Set as Latest Release checkbox"
src="https://github.com/user-attachments/assets/d35f5324-7ae0-4e9f-b73c-d3c90496fb00"
/>
---------
Co-authored-by: silverwind <me@silverwind.io>
Saving a branch protection rule with a "Required approvals" value that
can't be parsed as an integer (like `99999999999999999999` or `abc`)
showed the success message and stored 0, because the binding error was
never checked. The form now uses `form-fetch-action` with
`GetFetchActionForm`, so invalid input is rejected and nothing is saved.
Dev mode still answers unparsable numbers with a 500 until
`TypeCastError` gets a message, see
https://github.com/go-gitea/gitea/pull/40212.
- Validation errors show on the page and highlight the offending field,
keeping the form input
- Negative approvals are rejected before any rule lookup, and the input
gets `min="0"`
Fixes https://github.com/go-gitea/gitea/issues/40211
Assisted-by: Claude Code:claude-opus-5-5
---------
Co-authored-by: silverwind <me@silverwind.io>
Bump `giteabot` to
[`v1.5.1`](https://github.com/go-gitea/giteabot/releases/tag/v1.5.1) to
label PRs with a `backport/<version>` label that would cherry-pick
cleanly onto the latest release branch with `backport/conflict-free`.
The backport workflow's concurrency group is now per ref, so release
branch pushes can't cancel a pending backport run from `main`.
_Written by Claude (Opus 5.5)._
Code review never checked user blocks, so a user blocked by the poster
or repository owner could still post reviews, code comments and replies
through the web, API and email.
- `CreateCodeComment` and `SubmitReview` now check blocks, with the repo
admin exemption issue comments have.
- Official reviewers blocked only by the author can still review, so an
author can't keep a maintainer from requesting changes.
- Review requests are handled in
https://github.com/go-gitea/gitea/pull/39296.
---------
Co-authored-by: silverwind <me@silverwind.io>
Document: https://gitea.com/gitea/docs/pulls/634 ([doc PR
preview](955ae7d75c/docs/usage/repository/code-owners.md))
1. If the pattern starts with `*` or contains `/*`, use our glob package
to parse it as glob to regexp
* Make it easier to use. Otherwise it's really ugly to keep writing a
lot of backslash escapes
2. Add GitLab-style "section" support
* Fix#28926
```codeowners
[Section] @user
*.js
```
3. Add our annotation support, at the moment only need to support
review-request=true/false
* Fix#40241
```codeowners
## review-request=false # this is global
*.js $user ## review-request=true # this is line-level
[Section] @user ## review-request=true # this is section-level
*.md
```
4. Add GitHub/GitLab-style "show codeowner when viewing a file"
* Fix#28137
The `alpine:3.24` base image lags behind fixes published to the Alpine
repository.
Both images now run `apk upgrade` before `apk add`. Packages from `apk
add` are already current, so this only affects the preinstalled base
packages.
_Written by Claude (Opus 5.5)._
Adding a restricted user as a read collaborator to a public repo gave
them no access when the owner isn't fully public, like a limited org.
Restricted users get no default read there, and `RecalculateUserAccess`
dropped their read access row.
- `RecalculateUserAccess` now keeps the same read rows as
`refreshAccesses`, on org-owned repos and for restricted users.
- Re-saving an unchanged collaborator permission recalculates access,
repairing collaborators affected before this fix.
Fixes https://github.com/go-gitea/gitea/issues/40233
Assisted-by: Claude Code:claude-opus-5-5
---------
Co-authored-by: silverwind <me@silverwind.io>
The `test-backend#` and `test-integration#` targets rewrote every `.`
into `/`, because GNU make pattern rules never match goals containing
`/`. That broke regexes like `make 'test-backend#TestA.*'`, which
silently selected subtests instead.
Static pattern rules built from the goals accept `/` directly, so the
pattern reaches `go test -run` unchanged. Subtests are now selected like
`make 'test-backend#TestA/sub'`, on GNU make 3.81 and 4.x.
Removes redundant work from the pull request conversation tab.
- Base and head refs resolve through the `cat-file` batch of the git
repos the callers already hold, instead of two `git rev-parse`
processes, reducing the number of git processes from 9 to 7 per page,
about 17 ms faster locally.
- Sidebar reviewers are batch-loaded and code-review comments reuse
their loaded poster, reducing the number of SQL queries from 387 to 358
on a PR with 169 timeline entries and 15 reviews.
Also fixes the commit list of merged pull requests, which resolved the
merge base as a branch name and returned 500.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
`gitea dump -f backup.tar.gz` without `--type` wrote a zip archive under
that name. Detecting the type from the file name was requested in
https://github.com/go-gitea/gitea/issues/24622 and intended in
https://github.com/go-gitea/gitea/pull/30240, but the check read the
still-empty result instead of `--file`, so every name fell back to zip.
A supported extension now selects the type, other names like `backup` or
`gitea.bak` still default to zip. An explicit `--type` still wins, and
its help text now mentions the detection.
AI disclosure: written with Claude Code (claude-opus-5-5).
---------
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: silverwind <me@silverwind.io>
Fixes#40230
Cookbooks without dependencies are served with `"dependencies": null`
from the universe and version metadata endpoints. This happens because
`omitempty` drops the empty map when the metadata is stored. Chef
clients iterate over this value and Policyfile resolution crashes.
Supermarket returns `{}` for these cookbooks, Gitea now does too. The
fix checks whether dependencies are `nil` while building response,
returns an empty map if true. This way cookbooks that are already stored
are covered without a re-upload.
Tested with the Chef integration test and `chef-cli install`.
Assisted by Claude Code (claude-opus-5-5).
Bitbucket Server can answer 500 for `/pull-requests/{id}/activities` of
some pull requests. That error aborted the whole repository migration:
`bitbucket server API .../pull-requests/128/activities returned status
500`
Now a 500 is logged as a warning and that pull request's comments,
reviews and merge commit are skipped, cached so the endpoint is not hit
again. Other errors are still returned, so the migration retries them as
before.
---------
Co-authored-by: silverwind <me@silverwind.io>
Fixes https://github.com/go-gitea/gitea/issues/40107
Static Fomantic dropdowns get `role="combobox"` or `role="menu"`, and
neither role takes its accessible name from child contents (`nameFrom:
author` only). Dropdowns like Filter and Sort on `/explore/repos`
without `data-tooltip-content`, `aria-label` or `aria-labelledby`
therefore had an empty accessible name.
If the focusable element still has no `aria-label` or `aria-labelledby`
after the tooltip check, link `aria-labelledby` to the dropdown's direct
`.text` or `span` child. Selection dropdowns are skipped because their
`.text` holds the current value, not a label.
---------
Co-authored-by: Mark <14371969+malfunctionAI@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
The issue reference popup built its `/info` request from the raw link
path, so a link ending in `/owner/repo/issues/N` queried an unrelated
location.
It now builds the request from the owner, repo, type and index parsed
from the link, with `parseIssueHref` anchored to the pathname under
`appSubUrl`.
Inline the web app manifest as a `data:` URL instead of serving it from
`/assets/site-manifest.json`. That endpoint has been linked through
`STATIC_URL_PREFIX` since https://github.com/go-gitea/gitea/pull/37405
and fails to load when:
1. a reverse proxy checks auth on every request, because browsers fetch
manifests without cookies
2. `STATIC_URL_PREFIX` serves the `public/` directory from disk, which
has no such file
3. `STATIC_URL_PREFIX` is cross-origin, because the endpoint sends no
CORS headers
The manifest is built per request so `start_url` follows
`PUBLIC_URL_DETECTION`. A reverse proxy that adds a stricter CSP must
allow `manifest-src data:`.
Fixes https://github.com/go-gitea/gitea/issues/40197
---------
Co-authored-by: silverwind <me@silverwind.io>
The job emitter resolves a dependency chain in several rounds in memory,
but a job's `if:` reads its needs' results from the database, where a
need skipped or failed earlier in the same pass was still blocked. A job
with `if: always() && needs.build.result == 'skipped'` was therefore
skipped instead of started.
Finished statuses are now persisted at the end of each round, which also
replaces the separate handling of skipped and failed reusable workflow
callers.
The repo-level runner, secret and variable API tests ran as the repo
owner, who bypasses the repo admin check. That's how the regression
fixed in https://github.com/go-gitea/gitea/pull/40174 slipped through.
As suggested in
https://github.com/go-gitea/gitea/pull/40174#issuecomment-6591991824,
they now run as an admin collaborator, which replaces the duplicate
subtest added there.
The tests are also trimmed to one case per status code, because
`services/secrets/validation_test.go` already unit tests name
validation.
When a member of a CODEOWNERS team approves or rejects a pull request,
`SubmitReview` removes the team's review request. On the next push,
`PullRequestCodeOwnersReview` finds no request for that team and adds it
again, so every team member except the author gets a new "Requested to
review" mail. This happens even when the push doesn't change the PR's
diff (for example, merging the base branch into the PR branch) and the
approval is still valid.
Individual code owners don't have this problem: they are skipped when
they already have a review. This PR applies the same rule to teams. A
team is not requested again while one of its members has an approve or
reject review on the PR that hasn't been dismissed. If that review is
dismissed (for example by "Dismiss stale approvals"), the team is
requested again as before.
Testing: `TestPullView_CodeOwnerTeamAnsweredByMember` creates an org
repo with `README.md @org3/team1`, opens a PR, and checks three things:
the team is requested; after a member approves, a code owner pass (what
a push runs) doesn't request the team again; after the approval is
dismissed, the team is requested again. The test fails without the
change. `make lint-backend` and the `TestPull*` integration tests pass
locally on SQLite.
AI disclosure: I used Claude to help trace the cause and draft the
change, test and description. I reviewed and tested it myself and can
explain every line.
Fixes#34884.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The repository issue search JSON route sits behind the issues reader
check only, and the milestone page is open to anyone who can read either
issues or pull requests, since the issue routes have to tolerate pull
request permissions. Neither handler narrows the issue type to what the
viewer may read: the search route takes `type=pulls` straight from the
query or lists both types when it is absent, and the milestone page
always lists both. A team member granted only the Issues unit on a
private repository can therefore fetch every pull request's title and
body as JSON and see pull requests on milestone pages, while a
pull-requests-only reader sees issues there. I noticed it while
comparing these routes with the API `ListIssues` handler and the web
issue suggestions endpoint, which both already narrow the type. The
narrowing now lives in one small helper used by all three handlers, with
the search route answering 404 for a requested type the viewer cannot
read, like the API does. A unit test covers both handlers with an
issues-only permission.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Transferring a repository fired no `repository` webhook. It now sends a
`transferred` action shaped like GitHub's, with the previous owner in
`changes.owner.from.user` or `changes.owner.from.organization`. Like
GitHub, it goes to the new owner's webhooks, plus the repository's own
and system webhooks.
- Chat webhooks render "Repository transferred from <owner>".
- Actions workflows are unaffected, no trigger matches `repository`
events.
Fixes https://github.com/go-gitea/gitea/issues/33584
---------
Co-authored-by: silverwind <me@silverwind.io>
Reuse the request-scoped base git repository in `ToAPIPullRequest`,
`ToPayloadPullRequest` and `ToAPIPullRequests` instead of reopening it
for each conversion, so conversions share the request's `cat-file`
process.
- Same-repo PRs reuse the base repository for the head instead of
opening it a second time.
- Drop a redundant second open of the base repository.
- Fork head repositories stay opened per call. A request-cached fork
keeps its `cat-file` running until the request ends, which blocks
deleting the fork on Windows and keeps one process per fork alive in
batch status changes.
---------
Co-authored-by: silverwind <me@silverwind.io>
Fixes https://github.com/go-gitea/gitea/issues/34317
Removing a label from a pull request still started workflows with
`types: [labeled]` and never matched `types: [unlabeled]`.
https://github.com/go-gitea/gitea/pull/34316 derived
`labeled`/`unlabeled` from the added and removed labels for issues only,
while pull requests kept mapping every `label_updated` to `labeled`.
Both events now share one conversion, so they cannot drift apart again.
Two other label changes also triggered the wrong workflows:
- Clearing all labels started no `unlabeled` workflow, because the
Actions notifier ignored it
- Replacing labels reported every old label as removed, so a replace
that kept a label also matched `unlabeled`
AI disclosure: written with Claude Code (claude-opus-5-5), session
https://claude.ai/code/session_01Kw9ySGNd4ynmyX9GcVdUfw
---------
Co-authored-by: silverwind <me@silverwind.io>
Heading anchors now keep the same characters as GitHub: letters, marks,
decimal and letter numbers, connector punctuation and alphabetic
symbols, with ASCII spaces turned into `-`. The rule follows
[github-slugger](https://github.com/Flet/github-slugger), whose fixtures
are generated from real GitHub renders.
- Other whitespace, including newlines in multi-line headings, is
dropped instead of becoming `-`, so `1<em space>ab` gets `#1ab`
- Combining marks are kept, so `हिन्दी` no longer becomes `हनद`
- Other numbers like `²` and `①` are dropped
Fixes https://github.com/go-gitea/gitea/issues/27754
This PR was written with Claude Code.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: silverwind <me@silverwind.io>
Remove the `UnmarshalHandleDoubleEncode` runtime workaround for an old
xorm bug, and normalize legacy double-encoded JSON in `repo_unit.config`
and `login_source.cfg` with a migration instead.
- Config `FromDB` no longer fails on corrupted values. A broken auth
source is deactivated, so admins can still fix it in the admin panel
- Remove `ProjectsConfig.GetProjectsMode`, `FromDB` already defaults
`ProjectsMode`
Actually it doesn't need so many changes, just because AI thinks it
should handle "corrupted database value" for auth sources.
---------
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Clearing "Full Name" in the profile settings shows success but keeps the
old value: since #31959 it is only updated when non-empty, because the
input is disabled (not submitted) when `change_full_name` is disabled.
Use `*string` for the field, so a missing field keeps the value and an
empty one clears it. With `change_full_name` disabled, any submitted
value (including empty) is now rejected. Not `optional.Option[string]`,
because `MaxSize` would count slice elements instead of characters.
Fixes#40185
---
AI disclosure: written with Claude Code (claude-opus-5-5) and tested
locally.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Concurrent `notification-service` workers can deadlock in the
transaction for-loop.
The transaction is useless, so it is removed to avoid deadlock.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Building the backend under QEMU takes 46 to 54 minutes per foreign
platform in the nightly image build, against 7 minutes natively. The
backend stage now runs on the build platform and cross-compiles with
`GOOS`/`GOARCH`/`GOARM`, so every platform builds at native speed.
- `go generate` runs once instead of once per platform.
- The PR docker dry run builds all three platforms in a single job, on
any docker-related change.
---------
Signed-off-by: silverwind <me@silverwind.io>
The `repo_stats_update` queue only deduplicates waiting items, so
several workers can index the same repository at once, then
`UpdateLanguageStats` can lead to transaction deadlock.
Use a global lock to avoid concurrent updates, and also helps
https://github.com/go-gitea/gitea/pull/39342 to avoid duplicate updates.
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Render fenced code blocks with Gitea's own code block renderer instead
of `goldmark-highlighting`, using the managed lexer lookup from
`modules/highlight`. Indented and fenced blocks now share one rendering
path.
Rendered output is unchanged. The trailing newline is stripped on copy
in the frontend since https://github.com/go-gitea/gitea/pull/39755.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The "load more lines" button in wiki diffs returned 404. `ExcerptBlob`
loads the commit from the wiki repo but read the blob from
`ctx.Repo.GitRepo`, a regression from
https://github.com/go-gitea/gitea/pull/38464.
Read the blob from the repo the commit was loaded from.
---------
Co-authored-by: Abdel <hktitof@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
WebKit was removed from the e2e tests in
https://github.com/go-gitea/gitea/pull/37315 because Linux WebKit kept
crashing. The crashes came from a use-after-free in the libsoup bundled
with WebKit, which killed the network process and surfaced as `WebKit
encountered an internal error`. macOS WebKit never had them because it
does not use libsoup. Playwright 1.64 bundles libsoup 3.6.6 with the fix
(https://github.com/microsoft/playwright/issues/42803), so WebKit now
runs on all platforms. Builds on
https://github.com/go-gitea/gitea/pull/40140.
The e2e job ran 50 times on `ubuntu-latest` with WebKit enabled, and all
runs passed without WebKit errors. The third browser adds about 1.5 to 2
minutes per run.
1. Playwright installs its system dependencies on GitHub runners too,
which only come with the Chromium and Firefox libraries
1. The passkey test skips Linux WebKit, which has no WebAuthn,
Playwright only stubs `PublicKeyCredential` there
---------
Signed-off-by: silverwind <me@silverwind.io>
tea has been replaced by [gitea-cli](https://gitea.com/gitea/cli)
(`gt`), which reuses tea's OAuth2 client ID.
- The repository clone panel now offers `gt repo clone owner/repo` under
a "Gitea CLI" tab.
- The built-in OAuth2 application is renamed from `tea` to `gitea-cli`.
`tea` in `[oauth2] DEFAULT_APPLICATIONS` is still accepted as an alias,
so existing configs keep working.
- Instances that already registered the app keep its stored display name
"tea".
Follow-up to https://github.com/go-gitea/gitea/pull/30205, per
https://github.com/go-gitea/gitea/pull/30205#discussion_r4435046605.
Moving a card on a project board is a project operation, so project
write is enough again. Repository boards already show every card to
project writers, who then got a 404 when moving one.
The per-card archived check is removed too. The frontend sends every
card of the target column, so one card from an archived repository
rejected all drops and reorders into that column, silently. Project
workflows already skip archived repositories, and an archived
repository's own board is read-only.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Requiring branches to be up to date reruns the full CI matrix on every
waiting PR after each merge, which saturates our 20 concurrent jobs. A
merge queue still tests against the latest `main`, but once per batch.
This runs the PR workflows on `merge_group` and reports `giteabot/lgtm`
on queue commits, since GiteaBot only sets it on PR heads. Mergers queue
PRs with "Merge when ready" instead of `reviewed/wait-merge`, so
giteabot no longer updates branches. Nothing changes until the
`merge-queue` ruleset is enabled and "Require branches to be up to date"
is turned off. Backport to `release/v28` before the ruleset covers it.
---------
Co-authored-by: silverwind <me@silverwind.io>
The snap nightly ran on every push to `main`, about 58 runs and 520 job
minutes a week on GitHub-hosted runners, which share the concurrent job
limit with PR CI.
It now runs as a job in `release-nightly.yml`, reusing its 12-hourly
check for new commits on `main`, like
https://github.com/go-gitea/gitea/pull/40138 did for the other
nightlies. A new release tag is still published by the first build after
`main` changes.
Pull requests between branches without a common history show no commits
and can never be merged. The web form already rejects them, but the API
and crafted `..` compare POSTs did not.
The API compared with `..` since
https://github.com/go-gitea/gitea/pull/36105, it now uses `...` again
and returns 422 when there is no merge base.
A failed contributor stats generation never released its lock, so after
the cached error expired, the contributors and code frequency pages kept
saying stats are being generated until a restart. A generation
outlasting the 5 second wait also leaked its goroutine on an unread
channel.
The lock is now claimed atomically and released together with the
completion signal when generation ends, and a cache miss after the wait
counts as still generating.
The starred and watched repository API lists converted repositories
without loading their attributes, so `language` was always empty. They
are now loaded in one batch, owners included.
Three of these handlers also continued after an internal error and wrote
a second response body, they now return.
Add `read:commitstatus` and `write:commitstatus` token scopes, so CI and
other integrations can report commit statuses without a token that
grants full repository access.
- The commit status endpoints (`/statuses/{sha}`,
`/commits/{ref}/status` and `/commits/{ref}/statuses`) accept the new
scopes, and the repository scopes keep working as they include them
- Creating a status still requires code write permission on the
repository, the scope only narrows what the token may do
- `public-only` tokens still cannot reach private repositories
- `PathGroup` middlewares now run only for a matched path, so unrelated
methods and paths that fall back to the group are not rejected by its
scope check
Fixes: https://github.com/go-gitea/gitea/issues/32625
Ref: https://github.com/go-gitea/gitea/pull/35383
Docs: https://gitea.com/gitea/docs/pulls/622
Signed-off-by: Norbert Szulc <norbert@not7cd.net>
Co-authored-by: Aiden Scandella <git@sca.ndella.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
WebKit was removed from the e2e tests in
https://github.com/go-gitea/gitea/pull/37315 because Linux WebKit kept
crashing. `make test-e2e` on macOS now also runs WebKit, which does not
show these crashes there. CI stays on Linux, because macOS runners would
need about an extra GB of cache storage.
1. The bot account test clicks the user type dropdown, Safari only moves
Tab focus between text fields
1. On macOS, Playwright runs with a single libuv thread, concurrent
`getaddrinfo` lookups of `localhost` there sporadically stall for 5s
---------
Signed-off-by: silverwind <me@silverwind.io>
Building nightlies on every push to `main` and `release/v*` costs about
1000 runner minutes a day, a third of it in runs cancelled by the next
push. Nightlies now run every 12 hours and only build branches with
commits since the last successful run.
---------
Signed-off-by: silverwind <me@silverwind.io>
Fixes https://github.com/go-gitea/gitea/issues/40037
`DELETE /repos/{owner}/{repo}/push_mirrors/{name}` only deleted the
`push_mirror` row and left the `remote_mirror_*` git remotes, which
embed the mirror credentials, in the repository and wiki config. Each
create/delete cycle through the API left more credentials behind.
- Remove the git remotes before deleting the row and record it in the
audit log, like the web settings handler.
- Return 404 for an unknown name, as documented, instead of 204.
- Ignore an already missing code remote, so the wiki remote is still
removed and such mirrors can be deleted through both the API and the web
settings.
---
AI disclosure: I used an AI coding assistant to help write this change
and the PR description. I reviewed the code and ran the tests myself.
---------
Signed-off-by: sarankumar1325 <120080287+sarankumar1325@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
With `[oauth2_client].ACCOUNT_LINKING = auto`, submitting the link
account sign-up form with a user name or email that already exists
linked the external identity to that existing account and signed in as
it, without checking that the external identity belongs to that account.
Automatic linking now only happens when the existing user matches the
external identity: its email equals the provider email, or its name
equals the user name derived from the provider (per
`[oauth2_client].USERNAME`, same as auto registration). Otherwise the
linking login page is shown, which requires the existing account's
password.
Auto registration is unchanged, since the name and email there already
come from the provider.
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
Pages showing commit statuses loaded the repository of every status
separately, so a PR with ~75 statuses ran ~75 identical repository
queries per merge box render (about 3.4s in a production trace).
`statusRepoCanReadActions` only loaded the repository inside the
permission cache loader, which never runs once `RepoAssignment` has
published the permission.
- `CommitStatus.loadRepository` resolves through the request context
cache
- `statusRepoCanReadActions` loads the repository before the permission
lookup
- `RepoAssignment` publishes its repository to the context cache next to
the permission
- `GetCommitActionsStatusMap` drops its own repository map
Queries per request with 50 statuses (45 Actions, 5 external) on the
head commit, SQLite:
| Page | Repository queries | Total queries |
|---|---|---|
| PR view | 61 → 11 | 157 → 107 |
| Commit | 6 → 5 | 24 → 23 |
| Branches | 6 → 5 | 33 → 32 |
| Commits | 6 → 5 | 23 → 22 |
| Repo home | 8 → 7 | 35 → 34 |
| `/pulls` | 15 → 15 | 37 → 37 |
---------
Co-authored-by: silverwind <me@silverwind.io>
Default-branch pushes and Actions unit saves recreated the repository's
schedules with `Next` recomputed from that moment, so a push between a
cron's due minute and the scheduler's pass skipped the occurrence, and
`@every` timers restarted.
Re-registration now updates schedule rows in place and keeps the spec
rows of unchanged crons, so their cursor survives and an in-flight
scheduler pass still finds its schedule. Re-detection also clears
schedules when no scheduled workflow remains.
Fixes https://github.com/go-gitea/gitea/issues/40062
Written with AI assistance.
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 5.5) <noreply@anthropic.com>
Updating a label through the API without `is_archived`, for example to
rename or recolor it, silently unarchived it. Creating an organization
label also ignored `is_archived`.
- Keep a label's archive state when a PATCH omits `is_archived`, for
repository and organization labels
- Honor `is_archived` when creating organization labels, as repository
labels already do
AI-assisted by Codex (GPT-6); prepared and tested by the assistant.
---------
Co-authored-by: Hello-Liar <80651389+Hello-Liar@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
Large GitHub migrations failed once the API budget ran out. A rejected
request never updated the downloader's rate state, so go-github refused
every retry locally.
- Record the rate limit from error responses too, so the next request
waits for the reset
- Retry repository-wide comment pages like the other downloader calls
- Stop `waitAndPickClient` spinning when the rate refresh fails
- Skip reaction requests when the reaction rollup reports none
- Log a failed patch download instead of failing the migration
---------
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
The post-receive hook wrote each push's new commit into the branch
table. Each push runs its own hook after the ref is updated, so with
pushes in close succession, like CI pushing back or a quick force-push,
a slower first hook can finish last. The table then keeps the older
commit, or marks a re-created branch as deleted.
- The sync reads each pushed branch's state from git under a repo-scoped
lock
- Deletions go through the same sync, so a re-created branch stays
active
- A branch that fails to sync is logged and skipped instead of aborting
the batch
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
The repository issue and pull request lists filter `access` by
`repo_id`, which no index covers, so every load scans the whole table.
Issue counts on larger repositories merge three single-column indices,
and the API pull request list loads the latest reviews with `id IN
(SELECT max(id) ... GROUP BY ...)`, which MySQL runs as a scan of every
review, about 1.1s on
https://gitea.com/api/v1/repos/gitea/gitea-mcp/pulls?limit=1.
- Index `access.repo_id`
- Index `team_repo.repo_id`
- Replace the single `repo_id`, `is_closed` and `is_pull` indices with
one `issue` index on `(repo_id, is_closed, is_pull)`
- Join the latest review ids instead of `id IN (subquery)`
Median times in ms on MySQL 8.4 with tables sized like gitea.com (1M
access rows, 500k issues, 200k reviews), for a copy of
https://gitea.com/gitea/gitea-mcp and a repository with 9.7k issues:
| Page | Before | After |
|---|---|---|
| `/{owner}/{repo}/issues` | 107 | 40 |
| `/{owner}/{repo}/pulls` | 106 | 32 |
| `/{owner}/{repo}/pulls?state=closed` | 153 | 35 |
| `/{owner}/{repo}/issues/{index}` | 141 | 60 |
| `/{owner}/{repo}/issues`, 9.7k issues | 303 | 53 |
| `/api/v1/repos/{owner}/{repo}/pulls?limit=50` | 180 | 46 |
| `/api/v1/repos/{owner}/{repo}/pulls?limit=1` | 137 | 16 |
| `/api/v1/repos/{owner}/{repo}/issues`, 9.7k issues | 248 | 43 |
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
A review's text is stored on both the review and its timeline comment,
but editing it in the web UI or through the issue comment API only
updated the comment. The reviews API kept returning the old body
afterwards.
Extracted out of https://github.com/go-gitea/gitea/pull/39865.
*Written by Claude.*
Co-authored-by: Giteabot <teabot@gitea.io>
Project board cards list the pull requests that reference their issue,
but loaded them without checking the viewer's access. A private pull
request mentioning a public issue showed its title, number and state on
that issue's board to anyone who can view the board, anonymous visitors
included.
Repository and organization boards now skip linked pull requests in
repositories where the viewer cannot read pull requests, matching how
the issue page already hides such references.
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: Giteabot <teabot@gitea.io>
With the built-in SSH server, closing the client connection cancelled
the `gitea serv` process tree. go-git based clients close it right after
the push report, so post-receive was killed and branches, pull requests,
webhooks and Actions were not updated.
The command now runs under the server's hammer context like with
OpenSSH, where it ends once its pipes close or at shutdown.
Adds migration from bitbucket.org as the Cloud variant of the Bitbucket
entry, in the web UI and via `POST /repos/migrate` with
`service=bitbucket`. It migrates the repository and pull requests with
their comments and reviews. Bitbucket Cloud has no releases, and
Atlassian removed its issues and wikis on 2026-08-20
(https://community.atlassian.com/forums/Bitbucket-articles/Announcing-sunset-of-Bitbucket-Issues-and-Wikis/ba-p/3193882).
- Private repositories need an API or access token, which also raises
the rate limit.
- Rate-limited requests back off and retry, Bitbucket sends no
`Retry-After`.
- Tests replay recorded responses, set `BITBUCKET_READ_TOKEN` to
re-record.
Co-authored-by: silverwind <me@silverwind.io>
Repository-level and instance-level runners still picked up waiting jobs
of repositories with Actions disabled.
https://github.com/go-gitea/gitea/pull/30331 only covered owner-level
runners, by filtering them out in SQL.
Pickup now checks the Actions unit of each job it is about to claim, for
every runner scope, and cancels the job instead when Actions is
disabled, also honoring `DISABLED_REPO_UNITS`. Disabling Actions
therefore cancels its queued jobs once a matching runner polls, instead
of leaving them waiting until `ABANDONED_JOB_TIMEOUT`.
The approver limit was checked before writing each `Reviewed-by:` line
but compared with `>`, so the default merge message got one more trailer
than `DEFAULT_MERGE_MESSAGE_MAX_APPROVERS` allows (2 with a limit of 1,
11 with the default 10). It now stops once the limit is reached.
Fixes https://github.com/go-gitea/gitea/issues/40116
Assisted-by: Claude Code:claude-opus-5-5
Runs triggered by pull request reviews took their ref and commit from
the review's `commit_id` instead of the pull request. They now use the
pull request head like other pull request events, and their commit
status is set on the commit that ran.
API tokens and Actions task tokens could see data beyond their scope
through user-scoped API routes and web profile pages.
- Actions task tokens authenticate as a shared system user with a
negative ID, which org, project and SSH/GPG key filters treated as "no
owner filter". These filters now check `!= 0`, so system users own
nothing. The `/api/v1/user` routes except `GET /api/v1/user` only accept
individual and bot accounts, and repo search no longer skips access
checks for the owner.
- `/{username}` only accepts tokens for RSS/Atom feeds. HTML profile and
org pages render as anonymous for token requests.
- User and org feeds require `read:user` or `read:organization` from
tokens, the same way repo feeds require `read:repository`.
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Bot accounts cannot sign in, so admins manage their access tokens, but
the regenerate button was hidden for them. This adds an admin route to
regenerate a bot's token.
Token regeneration is now also recorded in the audit log.
Fixes: https://github.com/go-gitea/gitea/issues/40074
Co-authored-by: silverwind <me@silverwind.io>
Remove the `body {margin: 0; padding: 0}` reset from
https://github.com/go-gitea/gitea/pull/38396, which left HTML mails
flush against the edge in Apple Mail and Thunderbird. The devtest
preview stops injecting its own padding, so it matches delivered mails.
These clients render the mail as a document and apply the default 8px
body margin again, as with GitHub and GitLab notifications. Webmail
clients replace `<body>` and keep their own inset.
Fixes https://github.com/go-gitea/gitea/issues/40103
Assisted-by: Codex:GPT-6.1, Claude:Opus 5.5
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: silverwind <me@silverwind.io>
A workflow whose only trigger is `workflow_call` never runs on its own,
so its sidebar entry always showed an empty run list and a "Disable
workflow" action that has no effect.
Following the discussion with @bircni and @Zettat123, these workflows
are now listed in a separate collapsed "Reusable workflows" group. Their
page shows "This workflow can only be called by other workflows."
instead of "no runs", and "Disable workflow" and "Create status badge"
are hidden. A workflow disabled before it became `workflow_call`-only
keeps its label and "Enable workflow". "Other workflows" again only
lists workflows whose file is gone.
Fixes: https://github.com/go-gitea/gitea/issues/39385
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
Opening a single commit of a PR let you look, but not review. The Review
button was disabled and there was no way to reach the next or previous
commit. This finishes https://github.com/go-gitea/gitea/pull/37475,
which only reached the head commit.
- Reviews can be submitted from a single commit, a commit range stays
disabled
- A bar above a single-commit diff shows the commit, its position in the
PR and prev/next buttons
- The commit dropdown marks the commits on screen
- Timeline commits open the PR's commit view, commits force-pushed out
of the PR redirect to the repository commit page
Assisted-by: Claude Code (Fable 5.1)
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Repo and org label endpoints accepted a label name for GET but only a
numeric ID for PATCH and DELETE, which breaks label-sync style tools.
- PATCH and DELETE on `/repos/{owner}/{repo}/labels/{id}` and
`/orgs/{org}/labels/{id}` accept a label name when the value is not
numeric, numeric values stay IDs
- A name shared by several labels returns 400 instead of acting on an
arbitrary one
- Deleting a missing label returns 404 instead of 204
- The swagger `id` parameter of these endpoints is now a string
Fixes: https://github.com/go-gitea/gitea/issues/34937
Co-authored-by: Hello-Liar <80651389+Hello-Liar@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
- Let renovate update hyphenated action inputs like `python-version`
- Python 3.14 → 3.15 in CI
- Download gh 2.103.2 directly in the release workflow instead of using
`install-gh-cli-action` with 2.39.1
- Devcontainer Go image 1.26 → 1.27
- Snap build toolchains Go 1.26 → 1.27 and Node 24 → 26
Deleting an Actions run (UI, API or `RUN_RETENTION_DAYS`) left its
commit statuses linking to the deleted run, so "Details" gave a 404.
`DeleteRun` now clears the `target_url` of the run's statuses and of the
commit's status summary on the run's commit, in the same transaction.
States are kept, so required status checks and merging are unaffected.
This differs from GitHub, which deletes the checks together with the
run.
Fixes https://github.com/go-gitea/gitea/issues/40079
Co-authored-by: silverwind <me@silverwind.io>
The dashboard counts ran queries whose cost grew with the whole instance
instead of the viewing user. On https://gitea.com/pulls the two review
counts alone take about 1.4s per page load.
- Check the owner of the already joined repository with `EXISTS` instead
of listing every public repository and public user
- Hide superseded review requests with `NOT EXISTS` instead of grouping
every review on the instance
- Skip the review counts on `/issues`, which never shows them
- Index `team_user.uid` for permission and team lookups by user
- Fix indices for table `watch`, `package_property` and `action_run`,
fix#40090
Median page times in ms, before → after, on a synthetic dataset sized
like gitea.com (175k users, 40k repos, 120k issues, 1M comments):
| Page | SQLite | PostgreSQL 17 | MySQL 8.4 | MariaDB 13 |
|---|---|---|---|---|
| `/pulls` | 901 → 56 | 330 → 156 | 189 → 124 | 192 → 218 |
| `/pulls?type=review_requested` | 1797 → 66 | 557 → 254 | 307 → 245 |
604 → 499 |
| `/pulls?type=reviewed_by` | 1646 → 118 | 537 → 316 | 515 → 265 | 528 →
682 |
| `/pulls?type=mentioned` | 1403 → 46 | 426 → 119 | 165 → 116 | 179 →
179 |
| `/issues` | 923 → 33 | 293 → 39 | 178 → 65 | 140 → 41 |
| `/api/v1/repos/issues/search` (anonymous) | 870 → 110 | 144 → 104 |
123 → 87 | 170 → 193 |
MariaDB checks the owner per row on long scans across all public
repositories, which makes a few pages slower there.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
With `[repository] ALLOW_FORK_INTO_SAME_OWNER = true` a user can fork
their own repository, but an organization still cannot fork a repository
it owns. The fork button check and the owner list on the fork page
compared the organization with the repository owner directly instead of
asking `CanUserForkBetweenOwners`, and the walk over parent repositories
dropped the owning organization whenever the setting was on.
Organizations now follow the setting the same way users already do, and
nothing changes when it is off.
Fixes https://github.com/go-gitea/gitea/issues/40021
Assisted-by: Claude Code:claude-opus-5-5
`GET /repos/{owner}/{repo}/pulls` is documented with `state` defaulting
to `open`, but without `state` it returned every pull request, closed
and merged ones included. Now it parses `state` with
`ParseIssueFilterStateIsClosed`, like the issues and milestones
endpoints, so no state means open, as documented. `closed` and `all`
work as before. GitHub's pulls endpoint also defaults to open.
Docker Hub refuses anonymous tag pages past 1000 tags, and Renovate then
drops every release date it fetched
(https://github.com/renovatebot/renovate/issues/46925). The 5-day
`minimumReleaseAge` then holds digest updates of `postgres`, `redis` and
`getmeili/meilisearch` back forever. Stopping at 10 pages keeps the
dates of the newest 1000 tags.
Package files carry type specific properties like `debian.distribution`,
`debian.component`, `rpm.group` or `alpine.branch` that decide where a
file lives in a registry, but the API never returned them. A client
listing `GET /packages/{owner}/{type}/{name}/{version}/files` could not
tell which distribution or component a file belongs to, or tell apart
same-named files in different ones. Tools copying packages between
instances, like https://gitea.com/gitea/importer, need them to re-upload
a file to the same place.
This adds a `properties` map to `PackageFile`, limited to the properties
that locate a file. Internal ones like the rpm, alpine, arch and conda
metadata or the Debian control file stay hidden, as they can be many MiB
per file.
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Fixes markdown rendering details where Gitea deviated from GitHub
unintentionally, and adds a `/devtest/markdown` page to review markdown
rendering.
- Paragraphs get the intended 1.5 line height
- Blockquotes, alerts and definition lists no longer inherit browser
default margins
- Loose task lists keep their spacing, the checkbox renders inside the
paragraph like on GitHub
- Footnote references render as `[1]` so adjacent ones stay apart
without CSS
- Code in headings, headings in summaries, nested ordered lists, tables,
`<mark>`, alert icons and `<hr>` follow GitHub
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Keep notification counts and stopwatches in sync when tabs open or
reconnect, closing the gap behind [this
flake](https://github.com/go-gitea/gitea/actions/runs/31312525323/job/93242429394).
1. Send current notification counts and stopwatch state on each
WebSocket connection, replacing the separate state fetches tabs made on
every connect.
2. Tabs joining an already open connection fetch current state once,
since its snapshot went out before they attached.
3. Send events queued during the handshake before the snapshot, so older
events can't roll it back.
4. Wait for the Redis SUBSCRIBE acknowledgement, so the snapshot read
after subscribing can't miss an event.
On SQLite, the `is_reusable_caller = false` filters (e.g. the admin job
queue page) are planned from `IDX_action_run_job_is_reusable_caller`,
which matches almost every row, so nearly the whole `action_run_job`
table is read. `ANALYZE` does not help: `sqlite_stat1` only keeps
average rows per key.
No query needs that index. `is_reusable_caller = true` is only used by
`expandedCallerCond`, always together with `repo_id` and
`concurrency_group`. Their plans use other indexes on SQLite, PostgreSQL
17 and MySQL 8.4 (PostgreSQL is slightly slower with it).
This removes the index tag. `SyncAllTables` drops the existing index on
the next start, so no migration is needed.
Job queue count on SQLite, before: a search on the `is_reusable_caller`
index; after: a `MULTI-INDEX OR` on the `status` and `pickup` indexes.
On an instance with about 7,000 jobs the admin job queue page went from
`Page: 784ms` to `39ms` (measured with an earlier revision of this PR
that gave the same plan).
---------
Signed-off-by: Myers Carpenter <myers@maski.org>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Code search now matches case-insensitive literal substrings like GitHub,
in every script. Word tokenizers can't do this: the ES pattern dropped
every non-ASCII word, and the `standard` tokenizer keeps `console.log`
as one token.
- Elasticsearch indexes positional trigrams, plus 1-2 grams for short
terms
- Bleve indexes distinct 1-3 grams as a candidate filter and verifies
candidates against the content before paging, because phrase queries
over trigrams are quadratic in repeated grams
- Snippet offsets come from the original content, so `<em>` in sources
and width-changing lowercase no longer misplace them
- `search_mode=fuzzy` requests behave like Words, the UI never offered
fuzzy for code search
- The ES code indexer test now runs in CI, it was skipped before
There are no UI changes. Both code indexes rebuild after upgrade.
Fixes: https://github.com/go-gitea/gitea/issues/40027
Fixes: https://github.com/go-gitea/gitea/issues/16158
Fixes: https://github.com/go-gitea/gitea/issues/21359
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Allowlisting gitea-actions lets any workflow of the repository with write access push matching tags, the same trade-off branches already have. Allowing gitea:deploy-key is new for tags.
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Mermaid scales diagrams wider than the markup column down to its width,
but the iframe kept the unscaled viewBox height, leaving a large blank
area below them.
The iframe now uses a CSS `aspect-ratio` capped at the viewBox height,
so it follows the rendered diagram on any width and resize without
measuring, which keeps diagrams in collapsed `<details>` working.
Diagrams that don't scale (`useMaxWidth: false`) keep the fixed height.
Fixes: https://github.com/go-gitea/gitea/issues/40045
Several handlers and parsers skipped checks that their siblings already
perform.
- Reject issue, comment, label, milestone and release mutations in
archived repositories via the API with one guard per route group,
matching the web UI
- Hide timeline references and dependencies pointing at issues the doer
cannot read, in the web UI and the API, including pull requests of the
same repository without pull request access
- Reject workflow runs with more than `MaxJobNumPerRun` jobs, including
inline reusable workflow jobs, and show rejected pushes as invalid
workflow runs
- Bound how much of a Helm chart archive is inflated while looking for
`Chart.yaml`
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Render emoji inside the default commit-title link so the whole title
remains clickable, including emoji-only titles. Explicit links and
mentions keep their own destinations.
Fixes https://github.com/go-gitea/gitea/issues/31873
PR #39354 added support for enabling both internal and external trackers
concurrently and introduced separate navigation buttons.
However, as originally pointed out during code review on #39354
([comment](https://github.com/go-gitea/gitea/pull/39354#discussion_r4047774417)):
> *"With both trackers enabled and the built-in issue unit readable,
this redirect still runs whenever the external URL is nonempty, so
`/issues` never reaches the internal issue list in the new combined
configuration. Only redirect to the external tracker when the caller
cannot read the internal issue unit."*
`MustEnableIssues` in `routers/web/repo/issue.go` was still
unconditionally redirecting whenever `unit.TypeExternalTracker` is
enabled with a non-empty `ExternalTrackerURL`.
### Fix
In `routers/web/repo/issue.go`, only redirect to the external tracker
URL when the caller cannot read the built-in issue unit
(`!ctx.Repo.Permission.CanRead(unit.TypeIssues)`).
Signed-off-by: Vavrinecz Péter <98645804+petervavrinecz@users.noreply.github.com>
- Skip race instrumentation of the transpiled SQLite engine
`modernc.org/sqlite/lib` when `GOTEST_FLAGS` contains `-race`, Gitea
code and the driver boundary stay instrumented. `models/issues` drops
from ~10s to ~5s.
- Hash OAuth2 client secrets with `bcrypt.MinCost` in tests, like user
passwords already use the `dummy` hasher. `TestOAuth2` drops from ~22s
to ~2s on the PostgreSQL job.
- Stop the Redis locker's extension loop directly on `Close` instead of
waiting for its next tick, and run the memory locker tests under
`testing/synctest`. `modules/globallock` drops from ~12s to ~4s.
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
OneDev 16.8 moved the server version endpoint from
`/~api/version/server` to `/~api/server/version`. Both are tried, the
new one first, and both errors are reported when neither works.
---------
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
The file view, blame and web editor read `.editorconfig` from the
default branch, so branch-specific settings like tab width were ignored.
The file view's middleware ran before the ref was resolved, and blame
never loaded it.
- `GetEditorconfig` reads the current commit, falling back to the
default branch only where no ref is resolved
- `Home` and `RefBlame` load the editorconfig after ref resolution
Fixes: https://github.com/go-gitea/gitea/issues/27413
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: silverwind <me@silverwind.io>
Only the plain git migration can clone from a local server path. Other
services (Gitea, GitLab, Gogs, OneDev, GitBucket, Codebase, CodeCommit,
Bitbucket Server) are accessed through their remote API, so a local path
never works, yet their migrate forms advertised "or a local server
path".
- Remove the local path hint from those templates.
- Add `IsMigrateURLAllowedForService` and use it in the web and API
migrate handlers and in `MigrateRepository`, rejecting local paths for
any service other than plain git.
- Add a unit test.
Raised in review of #39913.
_Authored with Codet (AI assistant) on behalf of @lunny._
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Agents rarely read the `docs` guides when AGENTS.md only asks for
relevant ones. AGENTS.md now names the guide for each area, and rules
agents kept missing go into those guides.
- AGENTS.md maps Go, TS, CSS, Vue, templates, tests, config options,
migrations and refactors to their guide
- The backend guide lists XORM pitfalls behind recent bugs
(https://gitea.com/xorm/xorm/issues/2615): zero-valued struct
conditions, `Cols` and `updated` columns on updates, soft-delete values,
index drops by `SyncAllTables`, MSSQL limits and pagination order
- The development guide requires a `WIP:` companion docs PR copying the
ini text verbatim for `app.example.ini` changes, and says to avoid
migrations unless the alternative is a workaround
Legacy (v1 to v3) artifact uploads stored the request's
`Content-Encoding` header in the column that v4 artifacts read as their
`Content-Type`.
- Store only the `gzip` marker on legacy uploads, the only encoding
their clients send
- Pick the PDF and audio/video serve headers only for content types that
`mime.ParseMediaType` accepts
---------
Co-authored-by: silverwind <me@silverwind.io>
When opening a pull request that renamed a CSV file:
the compare page fails with a 500.
Fix: load the left blob for every file that was not added, and the right blob
(from `Name`) for every file that was not deleted. That matches what the
templates did before #38517.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Remove the workaround for chi router, do as much as possible in
middlewares
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
Webhook delivery history (request/response, status, timestamp) was only
reachable through the repo settings UI, which shows the last ~10
deliveries with no filtering. There was no way to fetch this data
programmatically for debugging integrations or auditing webhook
activity.
This adds two read endpoints at the repository level:
- `GET /repos/{owner}/{repo}/hooks/{id}/deliveries` — paginated list of
a hook's deliveries, most recent first
- `GET /repos/{owner}/{repo}/hooks/{id}/deliveries/{uuid}` — a single
delivery, including request/response headers, body, and status
Both reuse the existing `hook_task` storage and retention policy (the
`CleanupHookTaskTable` cron job) that already back the "Recent
Deliveries" web UI, and are gated by the same `reqAdmin()` +
`reqWebhooksEnabled()` guard as the existing hook endpoints.
Org, user, and admin/system-level webhooks are intentionally out of
scope for this PR to keep it reviewable; they can follow as separate
PRs using the same pattern.
## Usage
```bash
curl -H "Authorization: token $TOKEN" \
https://gitea.example.com/api/v1/repos/{owner}/{repo}/hooks/{id}/deliveries
curl -H "Authorization: token $TOKEN" \
https://gitea.example.com/api/v1/repos/{owner}/{repo}/hooks/{id}/deliveries/{uuid}
```
## Testing
- Unit tests for the new model-layer query functions
(`ListHookTasks`, `GetHookTaskByUUID`)
- Integration tests covering: list + get, an undelivered task's response
is `null`, a hook/delivery UUID from another repository 404s instead
of leaking across repos, and pagination (`page`/`limit`, `Link` and
`X-Total-Count` headers, correct ordering/no overlap across pages)
- `make lint-go`, `make generate-swagger`
Fixes: https://github.com/go-gitea/gitea/issues/38463.
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
The project milestone filter listed every milestone of the repo, or of
all accessible org repos for org projects, including ones with no issue
in the project. That made the dropdown noisy and unhelpful.
Both repo and org projects now list only milestones that have at least
one issue linked to the project, via a new `ProjectID` option on
`FindMilestoneOptions`.
Fixes: https://github.com/go-gitea/gitea/issues/39150
Co-authored-by: silverwind <me@silverwind.io>
Add migration from Bitbucket Server (Data Center). It has no issues,
wiki, milestones or releases, so those are not offered.
Migrated: git data with tags and LFS, and pull requests with state,
draft flag, merge commit/base, comments, approvals and inline code
comments including resolved threads. Multi-line ranges become a `Lines
a-b:` text prefix, as Gitea anchors code comments to one line. Auth is
an HTTP access token (personal, project or repository) or
username/password; 429 responses are retried.
Changes outside the downloader:
- `ReviewComment.Resolved`, applied by the uploader.
- The uploader creates a fork PR's head branch from its head commit when
already in the repository, instead of fetching the fork (Bitbucket keeps
it in `refs/pull-requests/<id>/from`; private forks need credentials).
Tested with unit tests, responses recorded from Bitbucket 10.5.1, and an
end-to-end migration. Independent of
https://github.com/go-gitea/gitea/pull/38607 (Bitbucket Cloud); the
service type enum value needs adjusting for whichever lands second.
---------
Co-authored-by: bircni <bircni@icloud.com>
Each `release-create-tag` run created a deployment record, which makes
GitHub show a useless "This branch has not been deployed" box on every
PR:
<img width="819" height="113" alt="image"
src="https://github.com/user-attachments/assets/129c8348-1d70-46b9-8996-8872c84a11f2"
/>
With `deployment: false`, the `release-signing` approval and branch
policy still apply.
Replace the unmaintained `chi-middleware/proxy` with an in-tree handler
that detects client IPs correctly behind common reverse proxies.
1. Handle repeated header lines and comma-separated proxy hops.
2. Accept and normalize IPs, including `ip:port`.
3. Keep the client port from the header value or `X-Real-Port` instead
of logging port 0.
4. Prefer the last `X-Real-IP` line.
5. Keep the proxy's address when the selected value is invalid.
6. Read `Forwarded` only when `X-Forwarded-For` is absent.
Docs PR: https://gitea.com/gitea/docs/pulls/560
Fixes: https://github.com/go-gitea/gitea/issues/35682
Fixes: https://github.com/go-gitea/gitea/issues/36814
Co-authored-by: bircni <bircni@icloud.com>
Every Actions runner RPC (`FetchTask` polling, `UpdateTask`,
`UpdateLog`, ...) goes through the `withRunner` interceptor, which
verifies the runner token with PBKDF2 (`auth_model.HashToken`, ~2 ms).
In a 60s CPU profile of a production instance with many runners, this
accounted for ~49% of all CPU samples.
- Cache successful verifications in a dedicated LRU of up to 10000
runners, keyed by the stored salt and hash and holding only a SHA-256
digest of the token. A hit costs ~100 ns.
- The runner is still loaded from the DB on every RPC and failures are
never cached, so deleted runners and wrong tokens never pass.
- The shared `auth_model.TokenCache` is not used because its default 20
entries miss on every poll once more runners poll in turn.
---------
Co-authored-by: silverwind <me@silverwind.io>
Adds a batched GraphQL path for GitHub migrations that fetches each
issue or pull request together with its comments, reviews, reactions and
timeline events instead of the REST N+1 pattern, sharply reducing
rate-limit pressure on large repositories.
- GraphQL is used for authenticated migrations with issues or pull
requests and falls back to REST automatically when it fails
- timeline events (close/reopen, lock, rename, label, milestone, merge,
branch deletion, pin, auto-merge) are imported as typed comments showing
their GitHub author
- GitBucket issue migrations no longer stop after the first page,
GitBucket sends no Link header
Fixes: https://github.com/go-gitea/gitea/issues/13243
Fixes: https://github.com/go-gitea/gitea/issues/31856
Signed-off-by: David E. Lovas <david@rexovas.com>
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
This PR introduces project workflows, allowing users to configure
event-based automations directly from the UI.
Users can define filters and actions for each workflow event, and
duplicate existing events when needed.
### Concepts
Every workflow is composed of `event`, `filters`, and `actions`.
- **Event** - An Event defines when a workflow should be triggered. It
represents specific activities such as an issue being added to the
project, a pull request being merged, or a project column being changed.
- **Filter** - A Filter refines when an event should take effect. It
allows users to specify conditions like issue type, labels, or project
columns to ensure actions only run in targeted scenarios.
- **Action** - An Action defines what happens when the event and filters
match. It performs operations such as adding labels, moving issues
between columns, or changing the issue or pull request state.
### Available Events
Currently, the following eight events are supported:
- On issues/pull requests closed
- On issues/pull requests reopened
- On issues/pull requests was added to the project, including when
created in it
- On issues/pull requests was removed from the project
- On issues/pull requests' column was changed
- On code changes requested
- On code review approved
- On pull request merged
Each event type supports its own set of filters and actions.
### Available Filters
There are four types of filters that can be configured for an event:
- **Apply to** - Specify the target type: `issues only`, `pull requests
only`, or `issues and pull requests`.
- **Labels** - Trigger the event only if the issue or pull request
contains one or more specified labels.
- **Move from column** - Filter events based on the original project
column.
- **Move to column** - Filter events based on the destination project
column.
### Available Actions
There are four types of actions that can be executed when an event is
triggered:
- **Add labels** - Automatically add one or more labels to the issue or
pull request.
- **Remove labels** - Remove one or more specified labels.
- **Move to column** - Move the issue or pull request to a designated
project column.
- **Issue state** - Close or reopen the issue. Pull requests can only be
closed.
### Enable/Disable an event workflow
Each event workflow can be individually enabled or disabled. When
disabled, the workflow will be ignored even if the event occurs. This
feature is useful when you want to temporarily stop certain automated
actions without deleting the workflow configuration. Workflows of closed
projects don't run either.
### Clone an event workflow
When you need multiple workflows for the same event, you can clone an
existing event to create a new one with different filters and actions.
This allows you to customize how the same event behaves under different
conditions, making workflows more flexible and powerful.
### Permissions
Permissions are checked when workflows are managed, not when they run:
- Viewing a project's workflows needs read access to the project
- Managing them needs write access to the project plus write access to
both issues and pull requests of a repository project's repository,
being an owner of an organization project's organization or a site
administrator, or being the user of a user project. Tokens limited to
public repositories can't manage owner-level projects' workflows
- A workflow runs when its event happens to an item of an open project
that can hold the item's repository, unless that repository is archived,
no matter who caused the event, and applies its actions without further
permission checks. Pull requests are never reopened
- Changes show as made by "Project workflow" on behalf of the user who
triggered the event and don't trigger other workflows
- Only users who can read an item can put it on a board or move its card
The management rule is also documented on `CanManageWorkflows` in
`services/projects/workflow.go`.
Fixes: #14359Fixes: #26704Fixes: #27990
Replaces: #28745
Documentation PR: https://gitea.com/gitea/docs/pulls/308
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
The Packages row in the team settings read "Manage repository
packages.", but packages belong to the owner, so a team's Packages
permission applies to every package of the organization, regardless of
the team's repositories. Reword it to say so. The docs get the same
clarification in https://gitea.com/gitea/docs/pulls/605.
The pnpm `engines` range was still `>= 11.0.0` while `packageManager` is
on pnpm 12. Raise it to match, and set Renovate's range strategy for it
to `bump`, because the default strategy leaves an already satisfied `>=`
range untouched.
Fixes#38646
### The problem
Each commit line of a push in the activity feed starts with the avatar
stack of its co-authors. The width of that stack grows with the number
of co-authors, so the hash of every commit started at a different
horizontal offset and the column looked ragged.
---------
Signed-off-by: Josway <jxh98@foxmail.com>
Co-authored-by: silverwind <me@silverwind.io>
The tags feed (`tags.rss` / `tags.atom`) lives in the tags route group,
which only requires code read access, yet it lists every published
release next to the plain tags and includes each release's title and
rendered note. The releases page and `releases.rss` require releases
read access, and the tags page only shows tag names to users without it,
so the feed is the one place that leaks. A team member granted Code but
not Releases on a private repo, or anyone on a public repo whose
Releases unit is disabled, can read all release notes this way. I
noticed it while comparing the feed routes with the tags template. When
the doer cannot read releases, the feed now lists releases as plain
tags, linking to `/src/tag/` like the tags page does.
On Debian/Ubuntu outside GitHub Actions, `tools/playwright.sh` always
ran `pnpm exec playwright install --with-deps`. Installing system
packages needs root: when it isn't root, Playwright tries `sudo` and
falls back to `su`, so a non-root user without passwordless sudo sees
`make playwright` — and the targets depending on it, `make test-e2e` and
`make test-frontend` — either stop and ask for the root password
mid-build or fail outright with `su: System error`.
Related: #38860, which added this script.
---------
Co-authored-by: silverwind <me@silverwind.io>
Document that reverse proxy authentication requires Gitea to be
reachable only through the authenticating proxy, and which routes each
setting covers.
Docs: https://gitea.com/gitea/docs/pulls/604
Co-authored-by: silverwind <me@silverwind.io>
Remove the inset bottom shadow from `<kbd>` elements in markup and on
keyboard shortcut hints. The skeuomorphic key cap look is not needed,
the border alone marks the element as a key.
For repos with many issues, the GitHub API only allows cursor-based
pagination instead of page-based pagination after a certain page.
A full migration of such repos failed with a 422 response.
Now, we dynamically switch how we migrate based on the response from the GitHub API.
Fixes#39882
---------
Signed-off-by: Phoenix <44333391+yuhongwei380@users.noreply.github.com>
Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: silverwind <me@silverwind.io>
Several `/repos/{owner}/{repo}/issues/{index}/...` API endpoints load
the issue by index without checking
`CanReadIssuesOrPulls(issue.IsPull)`, unlike `GetIssue`,
`ListIssueComments` and `GetIssueReactions`. The route group's
`mustEnableIssuesOrPulls` passes when the caller can read either unit,
so someone who can read issues but not pull requests (a team with only
the Issues unit, or per-unit anonymous access on a public repo) can read
a pull request's whole conversation through `/timeline`, plus its
labels, subscribers, attachments and tracked times, and can subscribe to
it. The reverse holds for issues.
These handlers now return 404 in that case, like their siblings.
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Fixes https://github.com/go-gitea/gitea/issues/39925
RFC 6749 §2.3.1 has clients form-urlencode the client id and secret
before building the HTTP Basic header. `openid-client` with
`client_secret_basic` does this and sends `-` as `%2D` and `_` as `%5F`.
The token and introspection endpoints used the raw header values, so
these clients failed with `invalid_client`. Both endpoints now decode
the values with `url.QueryUnescape` before the client lookup, the secret
check and the body consistency check, and reject a value that does not
decode. Plain HTTP Basic parsing in `httpauth` is unchanged, since the
encoding only applies to OAuth client authentication.
Clients that don't encode are unaffected, since no client id or secret
Gitea has ever issued contains `+` or `%`.
---------
Co-authored-by: silverwind <me@silverwind.io>
Sorting by an exclusive label scope joined `issue_label`, returning one
row per label. Issues with several labels were listed repeatedly and
pushed others off the page, and `IssueIDs` returned inflated totals.
The sort key now comes from a correlated subquery, so each issue yields
one row.
Fixes: https://github.com/go-gitea/gitea/issues/35711
Co-authored-by: silverwind <me@silverwind.io>
When `navigator.clipboard.readText()` runs before the page's copy has
landed, Firefox shows its "Paste" prompt, which cannot be interacted
with when headless. Avoid that and and use
`dom.events.testing.asyncClipboard` which is a pref meant for testing
that disabes this prompt.
Fixes: https://github.com/go-gitea/gitea/actions/runs/39113819632/job/117635434106
Signed-off-by: silverwind <me@silverwind.io>
`go test -count=2` failed in 12 packages because tests leaked state into
later runs.
- Reset SQLite sequences after each fixture load, like PostgreSQL
already does
- Prepare the database in tests that relied on earlier tests doing it
- Restore mutated globals with `test.MockVariableValue`
`PATCH /repos/{owner}/{repo}/milestones/{id}` always overwrote the due
date with `due_on`, so a request that only changed other fields, such as
`{"state": "closed"}`, silently removed it.
The due date is now kept when `due_on` is omitted, and `"due_on": null`
or `"due_on": ""` remove it.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
`make lint-go` ran `go install` and then a bare `golangci-lint` from
`PATH`, which fails when `GOBIN` is not in `PATH` and overwrites any
`golangci-lint` installed there. It now uses `go run -exec 'env
GOOS=<target>'`, which builds golangci-lint for the host and passes the
lint target `GOOS` only to golangci-lint.
`deps-tools` prebuilds the tools with `go run -exec true` instead of `go
install`, because `go install` does not fill the executable cache that
`go run` uses.
Also drops the unused `TAGS` env and the redundant OS name in
`--build-tags`.
The compare page hid the diff when a comparison had no commits, even if
the trees differ. A direct comparison whose head is an ancestor of its
base hits this, which is where the Compare link on a force-push comment
leads after commits were dropped.
The diff box now shows whenever there are commits or changed files, with
the header reading "0 Commits".
Fixes: https://github.com/go-gitea/gitea/issues/36390
Co-authored-by: silverwind <me@silverwind.io>
Release webhook notifications never mention attached files, although the
payload carries them.
The summary shared by all webhook providers now appends an asset count
like `(assets: 2)` for created and updated releases.
Closes https://github.com/go-gitea/gitea/issues/39630
Co-authored-by: silverwind <me@silverwind.io>
Code comments next to a diff hunk could show one line too high in the
conversation view vs. in the code view that showed them on the correct
line.
- Treat the hunk end in `CutDiffAroundLine` as exclusive, so the line
after a hunk falls back to the unchanged-line context
- Don't count `\ No newline at end of file` markers as lines when
cutting the hunk
- Share the unchanged-line fallback with migrated review comments
The RubyGems compact index (the shared `/versions` file and per-package
`info/<name>` files) is line-based.
https://github.com/go-gitea/gitea/pull/38061 rejected line breaks in the
gem name, but the platform, dependency names and version requirements
from the gemspec still accepted them, letting an uploader forge index
lines for bundler clients.
The parser now rejects a line break in any of those fields. The Marshal
based endpoints are length prefixed and unaffected.
Co-authored-by: silverwind <me@silverwind.io>
Matching git's stderr text is fragile because it breaks silently when
git rewords a message. Where git offers an exit code or a query with a
defined answer, use that instead, and drop matches that could never
trigger. Follow-up to the discussion in https://github.com/go-gitea/gitea/pull/39857.
- Detect a missing remote by `git remote` exit code 2
- Resolve annotated tags to their tree with cat-file `^{tree}` instead
of spawning `ls-tree` when listing tree entries
- Skip the wiki page lookup when the default branch doesn't exist yet
- Remove unreachable "no merge base" and "unknown revision" checks
Hovering a button in a primary or red button group showed a grey frame,
because the generic group hover border applied to colored groups too.
Green groups had no colors at all.
Colored groups now let the hover background fill the border, and green
groups get the same colors as primary and red. The devtest page shows
all three.
<img width="569" height="304" alt="Hovered buttons in uncolored,
primary, red and green button groups, before and after"
src="https://github.com/user-attachments/assets/e0fb6b16-f1f1-459b-9848-9386cd9453c9"
/>
Extracted from https://github.com/go-gitea/gitea/pull/38404
strings.Split("", ",") returns []string{""} (length 1), not an empty
slice, so the previous len(splitted) > 0 guard was always true and never
skipped the label ID lookup, even when the labels query param was
absent. Check the raw trimmed param for emptiness before splitting,
mirroring the pattern already used elsewhere in this file.
Fixes: https://github.com/go-gitea/gitea/issues/39889
Replace Fomantic's hard-forked jQuery modal with native `<dialog>`
elements, advancing https://github.com/go-gitea/gitea/issues/29849. The
UI is visually unchanged. Dialogs open with `show()` over an `inert`
page, so tooltips, popups and toasts mounted on `<body>` stay usable
above them, unlike with `showModal()`
(https://github.com/whatwg/html/issues/9936).
- Remove the Fomantic modal and dimmer, add `showModal` and `hideModal`
- Declare modals as `<dialog class="modal-dialog">`, shown inside a
`.modal-backdrop`, instead of `ui modal` with its unused size classes
- Remove two modals that could never be opened
- Block the page while a modal is open, close the topmost one on Escape
and restore focus to its opener
- Allow stacked modals, use a confirm modal instead of `window.confirm`
for deleting content history
- Hide popups and toasts when a modal opens or closes
- Keep the release notes modal open when generating notes fails
Fixes: https://github.com/go-gitea/gitea/issues/39643
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Adds a repository setting for the default squash merge commit message
body with GitHub's options `PR_BODY`, `COMMIT_MESSAGES` and `BLANK`,
also settable via `squash_merge_commit_message` in the API. The commit
title stays the pull request title.
1. Breaking: API squash merges without `merge_message_field` now get the
same default message as the web merge box instead of a title-only
commit, which affects clients sending only `Do` like Renovate. An empty
value keeps title-only.
2. Breaking: `[repository.pull-request] DEFAULT_SQUASH_MERGE_MESSAGE`
replaces `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`, which still
works as a deprecated fallback. Repositories store the default when
created or next saved.
3. Deprecated app.ini keys now migrate through one table instead of
per-loader fallbacks, which also fixes the ignored `[mailer] CERT_FILE`
and `KEY_FILE` fallbacks. Deprecation warnings no longer claim a removal
version.
<img width="1891" height="571" alt="grafik"
src="https://github.com/user-attachments/assets/0fdcde13-1f11-4156-b598-d80eb0d04cbe"
/>
---------
Co-authored-by: silverwind <me@silverwind.io>
The token a request authenticated with now lives on the doer, so
permission and visibility checks apply its scope from the doer alone.
Replaces https://github.com/go-gitea/gitea/pull/39167, following
https://github.com/go-gitea/gitea/pull/39167#issuecomment-6063839619.
1. Public-only tokens get 404 instead of 403 for hidden users, orgs and
repos, like GitHub.
2. Org ownership, site admin, repo and org creation, generation and
transfer endpoints reject public-only tokens.
3. API pull request responses set `head.repo` to `null` and
`head.repo_id` to `-1` when the caller cannot see the head repository.
4. Public-only tokens see org memberships like anonymous callers, so
concealed members stay hidden.
5. Site admin access in activity feeds, follower lists, org members,
Actions run lists and packages needs a session or an `all`-scoped token.
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Move the remaining legacy init code, which probed page classes with
`querySelector`, to `data-global-init`, so each feature initializes from
its own element. `hashchange` listeners now go through one module that
runs them in priority order, because independent listeners broke when
mount order changed.
- Convert issue list, diff, repo create, settings and org team inits to
`data-global-init`
- Run `hashchange` listeners by priority, the diff scroll handler last
- Make the devtest mock's failing log requests deterministic
- Add e2e tests for diff tree navigation to folded files and job log
permalinks
---------
Co-authored-by: silverwind <me@silverwind.io>
Several handlers and model helpers skipped checks that their siblings
already perform.
- Only match principal keys when looking up SSH certificate principals
- Return no attachments for an unsaved issue instead of every issue-less
one
- Reject issue and comment attachments when adding release attachments
- Share one permission check for editing, deleting and reacting to issue
content and for comment attachments between the web UI and the API,
refusing them on locked issues for users who can't write
- Reject issue edits, deletion and label changes in archived
repositories via the API
- Apply cross-origin protection to the repository topics endpoint
- Return 404 for the repositories tab of individual users and respect
profile visibility for the heatmap
Fixes: https://github.com/go-gitea/gitea/issues/39226
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
Integration test processes now get their own work directory, config file
and random ports, so several runs can execute in parallel on one
machine. The slowest tests also lose fixed sleeps and needlessly
expensive setup, with their assertions kept.
- Batch PostgreSQL sequence resets, the main per-test fixture cost there
- Cheap hashing params in hash and OAuth2 fixture tests, full-cost
vectors stay
- Single fetch for negative Actions runner assertions instead of 1s
polls
- Queue flushes and backdated timestamps instead of sleeps
- In-memory SQLite test journal, tmpfs for PostgreSQL in CI
- E2E instance writes SSH keys into its work directory instead of
`~/.ssh/authorized_keys`
`TreeCommon` only holds `ID` and is embedded solely by `Tree`, with no
other users. Inline the field into `Tree`; `t.ID` access is unchanged.
Assisted-by: Codet:codet
Incoming email reply tokens are embedded in every notification email and
stay valid for a year. `DecodeToken` verifies them with `user.Rands`,
which is not rotated when an admin disables sign-in or the account is
deactivated, and `ReplyHandler` only checks repo read permission. A
suspended user could therefore keep posting comments, review comments
and attachments on any public repo by replying to old notifications,
while the web, API, git and package paths all refuse them. `DecodeToken`
now rejects tokens of users who are inactive or prohibited from signing
in, which covers every incoming mail handler.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
When pushing the merge commit to the base repo fails (e.g.
non-fast-forward), the merge intent recorded by
https://github.com/go-gitea/gitea/pull/39442 is left behind.
`merged_commit_id` then points to a commit that does not exist in the
base repo, and `git branch --contains` fails with `no such commit`, so
the PR can never be checked or merged again.
- Clear the recorded merge intent if the push fails and the commit is
not on the base branch
- `IsCommitInBranch` now returns false for a missing commit, so
already-stuck PRs recover on the next check
Fixes https://github.com/go-gitea/gitea/issues/39824
_Written with Codet assistance._
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
Users with "Only Email on Mention" can now still receive email for the
events they picked in a custom repository watch, covering issues, pull
requests and releases. The setting is renamed to "Only Email on Mention
and Custom Watches" to make this explicit, matching GitHub's
"Participating, @mentions and custom".
"All activity" watches don't bypass the setting, because they are also
created automatically by `AUTO_WATCH_NEW_REPOS` for repository creators
and team members. To keep picking every event distinct from "All
activity", the custom watch dialog now stores a new `WatchModeCustom`.
Existing custom watches keep working without a migration.
## Screenshots
### Before
<img width="1650" height="675" alt="grafik"
src="https://github.com/user-attachments/assets/ecc56e20-cd05-41e9-a840-607b352c50a8"
/>
### After
<img width="1657" height="671" alt="grafik"
src="https://github.com/user-attachments/assets/a406db94-3ac4-4587-b840-0cd472b135c5"
/>
Addresses https://github.com/openSUSE/openSUSE-git/issues/183
Ref: https://github.com/go-gitea/gitea/issues/35492
---------
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: Giteabot <teabot@gitea.io>
In Firefox and in Chromium on Linux, the `math` font family resolves to
Latin Modern Math when it is installed. Its hairline strokes are too
thin for screens, so MathML looks noticeably weaker than KaTeX on the
same page.
List sturdier math fonts before `math`: Cambria Math, STIX Two Math, New
Computer Modern Math Book (aliased via `local()` because it shares its
family name with the thinner Regular), Libertinus Math and DejaVu Math
TeX Gyre. `math` stays as the fallback before Noto Sans Math. No fonts
are shipped, so systems with only Latin Modern Math, or no math font at
all, are unchanged.
Fixes: https://github.com/go-gitea/gitea/issues/39665
Co-authored-by: silverwind <me@silverwind.io>
getPullRequestInfo and getIssuesInfo indexed into the last element of an
empty Assignees slice when Action was HookIssueAssigned, causing a panic
under a race where the assignee list is empty by the time the webhook
payload is built.
Fixes https://github.com/go-gitea/gitea/issues/39826
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Remove the misspell spell checker along with its `lint-spell` and
`lint-spell-fix` make targets, its CI step and its custom dictionary.
`make lint` and `make lint-fix` no longer run a spell check.
Co-authored-by: Giteabot <teabot@gitea.io>
https://github.com/go-gitea/gitea/pull/38046 accidentally dropped
`gitea/unescaped-html-literal` from the eslint config, so it has been
off since. Raised in
https://github.com/go-gitea/gitea/pull/39829#issuecomment-6246720235.
- Re-enable the rule, exempting e2e tests like unit tests
- Detect tags anywhere in strings and template chunks, not only at the
start
- Allow `htmlRaw` templates, they escape like `html`
- Fix the existing violations, which escapes the heatmap tooltip and
shortcut keys
Adds OpenID Connect workload identity to Gitea Actions: jobs declaring
`id-token: write` can request short-lived tokens to authenticate to
cloud providers and other services without stored secrets. Tokens follow
GitHub's format, so clients like `@actions/core` and existing trust
policies carry over.
- Issuer at `/api/actions/oidc` with discovery and JWKS, signed with a
dedicated RS256 key
- GitHub's claims, including `workflow_ref`, `job_workflow_ref` and the
immutable `repo:owner@id/repo@id:ref:...` subject
- `id-token: write` or `write-all` grant it, default permissions and
fork pull requests never get it, owner and repository maximum
permissions can disable it
- Reusable workflows record the exact ref they were loaded from, and
dispatch rejects refs that only resolve through a same-named tag
- Generated signing keys are written atomically, and generated ES384 and
ES512 OAuth2 keys use their own curves instead of P-256
- `github.ref_type` is `branch` for pull request runs, like on GitHub
Fixes https://github.com/go-gitea/gitea/issues/26383
Fixes https://github.com/go-gitea/gitea/issues/33681
Docs: https://gitea.com/gitea/docs/pulls/526
Assisted-by: ChatGPT:gpt-5.6-sol, Claude Code:claude-opus-5-5
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Links to the new issue and new pull request pages can preselect sidebar
values with the same query parameters as the issue list filters:
`labels`, `milestone`, `project` and `assignee`, each taking
comma-separated IDs, e.g. `compare/main...branch?labels=1,2&assignee=3`.
Previously, pull requests supported none of them and issues only
`milestone` and `project`. On the compare page they also expand the new
pull request form, like `title` and `body`. Query values take precedence
over issue template labels and assignees.
* Fix https://github.com/go-gitea/gitea/issues/27341
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
parseProtobufBody read the full Twirp request body via io.ReadAll with
no size limit, allowing an authenticated runner token holder to force
unbounded memory allocation. Cap it at 1 MiB via io.LimitReader,
matching the existing pattern in job_summary.go.
Fixes https://github.com/go-gitea/gitea/issues/39814
Several Actions code paths issued one or more queries per listed row.
They now load in batch, with unchanged output.
- API job list: tasks, steps and runners load in batch
- API run list: latest attempts and their trigger users load in batch
- Repository runs page: the jobs of all active runs load in one query
- Cancelling previous runs and replacing concurrency group entries fetch
their jobs in one query
Per call with 50 rows on SQLite, base vs head:
| Path | Queries | Time |
| --- | --- | --- |
| API job list | 254 → 6 | 12.9 → 1.9 ms |
| API run list | 305 → 207 | 11.7 → 6.2 ms |
| Runs page job loading | 100 → 1 | 6.5 → 0.47 ms |
| `CancelPreviousJobs` | 546 → 496 | ~120 ms, per-job writes dominate |
The remaining run list queries come from per-run repository conversion,
which this PR doesn't change.
---------
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Bound the memory used when parsing uploaded packages, so instances with
low memory limits survive non-standard packages.
1. Metadata files are read through a size-limited reader, archives
themselves stay unbounded so large payloads keep working
2. zstd decoding caps the window at 128 MiB, the zstd CLI default
3. RPM headers go through a first-party reader with rpmlib's size and
array count limits that allocates only what the upload contains, until
https://github.com/sassoftware/go-rpmutils/pull/44 is released. Signing
still verifies the payload digest, and its signature header matches
rpmsign's byte for byte
4. Swift and NuGet read the zip entry they size-checked, a duplicate
name could stand in for it before
5. Terraform states check `LIMIT_SIZE_TERRAFORM_STATE` before parsing,
NuGet symbol PDBs share a 256 MiB budget
6. Oversized container configs are rejected instead of stored without
metadata, invalid Vagrant boxes answer 400 instead of 500
Signed-off-by: techknowlogick <git@tklk.dev>
Co-authored-by: techknowlogick <git@tklk.dev>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Folded (`>`) scalars in workflows gained blank lines and had spaces
turned into newlines every time Gitea re-serialized a job. This bumps
go-yaml to its main branch and drops our workarounds. The runner gets
the same change in https://gitea.com/gitea/runner/pulls/1321.
- go-yaml main fixes the accumulating blank lines
(https://github.com/yaml/go-yaml/pull/366) and the block scalar
indentation indicators (https://github.com/yaml/go-yaml/pull/414), so
`BlockSafeString` and the folded scalar requoting go away
- The job encoder no longer wraps lines, because go-yaml still wraps
more-indented lines of folded scalars and turns a space into a newline
- The fix applies to folded scalars anywhere in a workflow, including
workflow-level `env`
Fixes https://github.com/go-gitea/gitea/issues/39775
Co-authored-by: silverwind <me@silverwind.io>
With "Autodetect manual merge" enabled, an empty pull request (one with
no commits of its own, for example opened to discuss a branch) was
closed as manually merged as soon as it was checked. Its head commit is
already an ancestor of the base branch, so the detection took the
"fast-forwarded" path and recorded the head itself as the merge commit,
even though nothing was merged.
Detection now skips a PR whose head commit is its merge base, the same
condition Gitea already uses to show a PR as "already included in the
target branch". Real manual merges are still detected, including when
the base branch and the PR branch are pushed together.
The merge base is refreshed on recheck, so a fast-forward manual merge
whose merge base was already refreshed to the head is not detected
either. This only misses a detection and never closes a PR wrongly;
telling the two apart would need a value stored at PR creation.
Fixes https://github.com/go-gitea/gitea/issues/31947
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
* Remove unused `"root"` data attributes from templates
* Use our "confirmModal" as much as possible (partially for #39643)
* Use "data-global-init" to refactor a lot of legacy code
* Refactor and clearify initRepoIssueCommentDelete's behavior to help #36862
* Remove dead code `common-organization.ts`
28.0.0 bumped `github.com/go-webauthn/webauthn` from v0.17.4 to
v0.18.x. v0.18.0 made the ceremony fail whenever the client returns a
client extension output the relying party did not request, unless
`Config.ExtensionsUnsolicitedOutputPolicy` says otherwise; the strict
behavior is the zero value, so not configuring a policy opts into it
(upstream MIGRATION.md §2.4).
Every security-key and passkey login from Safari on iOS fails with 403:
Client returned the "appid" extension output which was not requested
To fix, set `ExtensionsUnsolicitedOutputPolicy:
protocol.UnsolicitedOutputPolicyIgnore` in the webauthn config
Fixes#39790
---------
Signed-off-by: choksi2212 <choksi2212@users.noreply.github.com>
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: choksi2212 <choksi2212@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The [Gitea VS Code extension](https://gitea.com/gitea/gitea-vscode)
currently signs in through the builtin `tea` OAuth2 application. Gitea
keeps one grant per user and application, so anyone who has already
signed in with the tea CLI gets `a grant exists with different scope`
when the extension requests its own scopes, and sign-in fails
(gitea/gitea-vscode#24).
This adds a dedicated builtin application for the extension, following
the same mechanism as the existing CLI entries:
The legacy Fomantic `line-height: 1` on buttons made ellipsized button
text clip descenders like `g` letters, like in the PR target branch
selector. Selection dropdowns were 3px taller than the inputs and
buttons next to them because their padding assumed `line-height: 1em`.
- Buttons and selection dropdowns use `--line-height-default` with
smaller vertical padding, `min-height` keeps their sizes for every font
including CJK
- Remove overrides that only compensated for the old values and dropdown
declarations that never applied
- Label-edit and push-mirror modals use regular-size action buttons like
all other modals
Try to figure out whether we can reuse the "comment" table for #36862
The legacy "CommitID" field is removed, and updated the comment.
Some useless and outdated "doctor fixes" are also removed together.
Cancelling running schedule runs has not worked since
https://github.com/go-gitea/gitea/pull/35767: callers pass a branch name
like `main`, while schedule runs store the full ref like
`refs/heads/main`. Disabling Actions, archiving the repository or
changing the default branch left them running.
Fixing the ref alone would also cancel them on every push to the default
branch, which GitHub does not do, and whenever the Actions setting is
saved, so those paths now only replace the schedules.
Changing or renaming the default branch deleted the schedules without
registering the new branch's ones
(https://github.com/go-gitea/gitea/pull/28691), so they stopped until
the next push, and the API path kept the old ones. Both now switch the
schedules to the new default branch, since scheduled workflows only run
on the default branch.
Fixes https://github.com/go-gitea/gitea/issues/39770
`\(x^2\)is` and `\(x^2\)123` rendered as plain text because `\(...\)`
went through the same check on surrounding characters as `$...$`. That
check is there to skip things like `US$1`, which can't happen with
explicit `\(` `\)` delimiters, so it's now skipped for them, the same as
for "$`...`$". `$x$a` still isn't parsed.
`parseGitDiffTreeLine` (`services/gitdiff/git_diff_tree.go`) read
`BasePath`/`HeadPath` straight from `git diff-tree --raw` output without
undoing git's C-quoting, unlike its sibling `parseLsTreeLine`
(`modules/git/parse.go`), which already does this with
`strconv.Unquote`.
This fix applies the same unquoting to both paths in the diff-tree
parser.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This adds a Branch Deletion setting with three modes: disabled by
default, anyone with push access, or a restricted user/team/deploy-key
allowlist. The policy applies to Git pushes, API/UI deletion, and
post-merge cleanup; the default branch and the pull request target
branch remain undeletable.
Closes https://github.com/go-gitea/gitea/issues/33305
---------
Co-authored-by: Enoque Duarte <12515551+enoquefcd@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
1. use correct links for srcset attribute of picture source
2. wrap the picture element with a link correctly
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
1. migrate protected branch fields into one json column
2. use the dropdown list for push/force-push user selection, and Actions
user can be added into the list now.
1. Fixes: https://github.com/go-gitea/gitea/issues/39641
* The OAuth2 authorize/grant endpoint accepted and persisted arbitrary
scope strings without validation, unlike personal access token creation
which rejects unknown scopes. Normalize and validate form.Scope before
creating or updating a grant, ignore invalid scopes
* `// TODO: at the moment invalid tokens are treated as "all" to avoid
breaking 1.22 behavior` is fixed.
2. To avoid CSRF attack for OpenID endpoint, add a state check
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
* Fixes#39746
* Fixes#35505
The click handlers were bound only to the first `.reference-list-menu`
(branches), so clicking a tag did nothing. Bind them to the items of
both lists.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Gitea changed the `dist.tarball` it advertises for already published
versions twice:
| Gitea | `dist.tarball` path of `@scope/name@1.0.0` |
|---|---|
| 1.27 | `%40scope%2Fname/-/1.0.0/name-1.0.0.tgz` |
| 28.0, https://github.com/go-gitea/gitea/pull/39061 |
`@scope%2Fname/-/1.0.0/name-1.0.0.tgz` |
| 28.1, https://github.com/go-gitea/gitea/pull/39434 |
`@scope/name/-/name-1.0.0.tgz` |
pnpm 11.5.2+ verifies the tarball URLs pinned in its lockfile against
the registry metadata, so frozen installs of lockfiles written against
1.27 fail with `ERR_PNPM_TARBALL_URL_MISMATCH`.
- Versions uploaded before this PR get their 1.27 URL back.
- New versions store their canonical tarball path at upload, so later
URL changes can't reach published versions. The host still follows the
request.
Lockfiles written against 28.0 for scoped packages, or by pnpm 12 with
`@scope:registry` against 28.1, need those pinned `tarball:` entries
removed, as 1.27 lockfiles of the same versions need the other URL.
Signed-off-by: silverwind <me@silverwind.io>
The six batched loaders in `IssueList` iterate their rows and never call
`rows.Err()`.
Refactored the code by a new helper function.
---------
Signed-off-by: Calvin Tjoaquinn <calvintjoa23@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Fixes a few Actions bugs where user input or nondeterminism caused
failures:
- The job log view indexed task steps with the client-supplied cursor
step without a bounds check, so an out-of-range value panicked and
returned a 500.
- Workflow activity type filters (`types:`) were compiled with
`glob.MustCompile`, so an invalid pattern in a workflow file panicked
during event detection. Invalid patterns now simply don't match.
- Listing workflow runs with an unknown `actor` returned a 500; it now
returns 404.
- The v4 artifact `ListArtifacts` response was built by ranging over a
map, so its order changed between calls. It now follows database order.
- The error from marshalling a workflow job when inserting run jobs was
silently ignored.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Secret and variable functions took an adjacent `ownerID, repoID int64`
pair, so swapping the two compiled and only failed at runtime. They now
take a `BelongingScope` value instead.
`BelongingScope` also replaces the "drop OwnerID when RepoID is set"
step that `InsertEncryptedSecret` and `InsertVariable` each implemented
separately
No behaviour change. It also makes adding another scope dimension, such
as the environment ID in https://github.com/go-gitea/gitea/pull/38149, a
one-field change.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Since https://github.com/go-gitea/gitea/pull/39703, Gitea fails to start
when its gitconfig holds a `gc.reflogExpire` other than `90` (e.g.
`30.days`), because `git config --unset-all` exits 5 when no value
matches. Value patterns were also unanchored regexps, so `90` also
removed `90.days`.
- Match values exactly in `configUnsetAll` and `configAddNonExist`
- Treat "nothing to unset" as success
- Use `--replace-all` in `configSet` so a key with multiple values no
longer fails startup
Collection of small, behaviour-preserving cleanups in the Actions code.
- Fix log and error prefixes that named non-existent or wrong functions,
and wrap errors with `%w` so `errors.Is` keeps working.
- Remove unused code.
- Rely on `ctx.ServerError` / `ctx.APIErrorAuto`, which already map
`util.ErrNotExist` and friends, instead of equivalent hand-written
switches.
- Load runs referenced by commit statuses with one query instead of one
per run (used on every PR view); the result order is now stable.
- Encode an event's payload once instead of up to three times.
- Share the duplicated runner-token "get or create" logic and the
"notify and emit" call pair.
- Simplify `ToActionArtifact` / `ToActionRunner` signatures.
---------
Signed-off-by: bircni <bircni@icloud.com>
Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Fixes#39702
**Root Cause:**
Cancelling a reusable workflow caller wrote it `cancelled` right away,
even while a child was still `cancelling`. When the runner acknowledged,
the caller did not change, so the run was never refreshed and stayed
`cancelling`.
SQLite hid this because the child is cancelled a second time, which
re-aggregates the caller, while MySQL reports no affected rows for that
unchanged update.
**Changes:**
- A caller now takes its status only from its children: it stays
`cancelling` until its last child finishes, and its final status then
refreshes the run.
- Cancelling a run whose jobs are all done only settles the run, which
did not wake the runs waiting for its concurrency group. It now does, so
force-cancelling an already stuck run also frees them.
---------
Signed-off-by: BenV <165034+benv666@users.noreply.github.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Gitea's default `gc.reflogExpire = 90` is parsed by git as
`1990-<month>-<day>`, currently `1990-10-09`, so reachable reflog
entries never expired.
Since git 2.54, auto maintenance runs its reflog-expire task in the
foreground of every push, and its trigger ignores reachability, so busy
repos rerun `git reflog expire --all` on every push without pruning
anything, stalling large repos for over a minute.
- Stop setting `gc.reflogExpire` so git's own defaults apply, and remove
the `90` written by earlier versions
- Treat the legacy `[git.reflog] EXPIRATION` as days, as documented
The next push to each repo prunes the accumulated entries once.
Fixes: https://github.com/go-gitea/gitea/issues/39693
Extract the repeated user and team dropdowns of the branch and tag
protection forms into shared templates, keeping the same fields and
rendered output.
Co-authored-by: Enoque Duarte <12515551+enoquefcd@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
The OIDC discovery document at `/.well-known/openid-configuration`
advertised `id_token` in `response_types_supported`, but
`/login/oauth/authorize` only implements the authorization code flow and
rejects any other response_type with `unsupported_response_type`. This
mismatch caused OIDC client libraries that rely on discovery to attempt
the implicit flow and fail silently.
This removes `id_token` from `response_types_supported` so discovery
matches actual server behavior, and adds an integration test asserting
`response_type=id_token` is rejected consistently.
Fixes#39482.
Fix two builds that reported a non-semver version.
Docker images built with a `VERSION` file reported a bare commit hash as
their version, and binaries built from the nightly source tarball
reported `main-nightly`. Docker builds now honor `VERSION` like `make`
does, and the nightly source tarball stores the actual version.
Also remove the obsolete `main` to `main-nightly` rename, the branch
case already appends `-nightly`.
| Build | Before | After |
|---|---|---|
| Docker release | `28.1.0` | `28.1.0` |
| Docker nightly | `29.0.0+dev-83-gab9bcd6a79` |
`29.0.0+dev-83-gab9bcd6a79` |
| Docker with `VERSION` | `c55da3e` | `29.0.0+custom` |
| Release tarball | `28.1.0` | `28.1.0` |
| Nightly tarball | `main-nightly` | `29.0.0+dev-83-gab9bcd6a79` |
This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [go](https://go.dev/)
([source](https://redirect.github.com/golang/go)) | toolchain | patch |
`1.27.1` → `1.27.2` |
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.
♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
This PR has been generated by [Mend Renovate
CLI](https://redirect.github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMjEuNCIsInVwZGF0ZWRJblZlciI6IjQ0LjEyMS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->
Previously, recognition of escaped alert markers such as `\[!NOTE\]`
accidentally depended on enabling the optional `inline-parentheses` math
setting, resulting in such escaped alerts not rendering properly.
Goldmark splits the escaped marker differently based on the setting
being enabled or disabled, and only one form was handled. This commit
handles both forms.
For example, in a README or issue:
```
> \[!NOTE\]
> text
```
Before this commit, we'd get an ordinary blockquote displaying literal
`[!NOTE]` followed by `text`. With this commit, we get the styled
admonition.
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Release notes: revert commits and the commits they revert are now left
out of the git-cliff changelog. A step before git-cliff finds commits
whose subject starts with "revert", reads the PR numbers on their revert
lines, and writes both SHAs to `.cliffignore`.
Snap: since the move from Launchpad to GitHub Actions, every build was
uploaded to `latest/edge`, including the stable-grade build that
`part-gitea-pull.sh` makes for an unreleased tag. Launchpad used to
release those builds to candidate and stable automatically. Without
that, v28.0.0 had to be promoted by hand and v28.1.0 stayed in edge.
Stable-grade snaps now go to `latest/stable` and `latest/candidate`;
main builds still go to edge. Candidate has to be updated too because
the pull script compares against it.
---------
Signed-off-by: bircni <bircni@icloud.com>
Restore the old layout by converting line breaks (`\n`, `\r\n`, `\r`) to `<br>`
in `createTelegramPayloadHTML`, after sanitizing, covering every Telegram
payload type at once.
Fixes https://github.com/go-gitea/gitea/issues/39649
Go rejects scp-style addresses like `git@host:owner/repo.git` in the
go-import meta tag because they have no URL scheme, so
GO_GET_CLONE_URL_PROTOCOL=ssh did not work with the default SSH port
unless USE_COMPAT_SSH_URI was set. Always use the ssh:// form for
go-get.
When DISABLE_HTTP_GIT is enabled, the https clone URL can never work, so
GO_GET_CLONE_URL_PROTOCOL now defaults to ssh in that case.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Only extracted shared code, removed unnecessary code, and use
form-fetch-action to replace RenderWithErrDeprecated, slightly
refactored JS to use data-global-init
Nothing else is changed, although there are still many problems.
Written by AI
Diff with hiding spaces:
https://github.com/go-gitea/gitea/pull/39672/changes?w=1
* Fix#5796
Global shortcuts can be easily introduced by adding "data-shortcut-keys"
to the elements
---------
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Reject the breaking marker `!` on `build`, `chore`, `ci`, `docs`,
`style` and `test` PR titles, since these types never change user-facing
behavior. The check runs in `lint-pr-title`, so it fails before labels
are synced and such PRs never get `pr/breaking`.
Co-authored-by: silverwind <me@silverwind.io>
Creating a repository with an initial commit, via `auto_init` or a
template with git content, pushed that commit through receive-pack and
three server-side hooks that do nothing for this internal push except
refresh server info. Fetching it into the bare repository instead makes
such repository creation about 3.7x faster locally (median 790ms to
214ms). E2E tests that create initialized repositories get about
0.2-0.3s faster each.
1. Run `update-server-info` after template content generation, which the
push's `post-receive` hook did before.
2. Remove the now unused `InternalPushingEnvironment` and
`PushOptions.LocalRefName`.
Fixes#39598
The AGit update path in `services/agit` moved `refs/pull/N/head` without
recomputing commits_behind.
The AGit update path now calls `syncCommitDivergence` in
`UpdateRefForAgit`, the same as AGit PR creation (`NewPullRequest`)
already does.
---------
Signed-off-by: dziulatex <paweldziurasoftware@yahoo.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Introduce second list of addresses which are classified as dialable if
explicitly allowed when in Lax mode.
Restricted pool now includes: link-local, site local, private (including
ULA), CGNAT, discard, dummy, documentation and test addreses.
Reserved pool shrinks to: this network, wireserver embedding/translation
ranges and multicasts
Rationale for the choice is that while items in restricted pool can be
dangerous to allow they could be a legitimate target in some
deployments. Ranges left in reserved list are ranges which make no sense
to dial, are public (wireserver) or are 6to4 embedding which cannot be
reasonably verified to be safe. To unlock those a proxy should be used
instead
fixes: https://github.com/go-gitea/gitea/issues/39557
---------
Signed-off-by: TheFox0x7 <thefox0x7@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Automate release tagging as proposed in
https://github.com/go-gitea/gitea/pull/39544#issuecomment-5955939142,
part of https://github.com/go-gitea/gitea/issues/39550.
A maintainer selects a release branch and version in the
`release-create-tag` workflow. After approval through the
`release-signing` environment, it pushes a GPG-signed tag. The tag
starts the existing release build, which generates GitHub release notes
with git-cliff from commits since the previous release, skipping `chore`
and `ci` commits.
`CHANGELOG.md` and release-candidate releases are removed. The workflow
reuses the existing `GPGSIGN_KEY`, `GPGSIGN_PASSPHRASE`, and
`RELEASE_TOKEN` repository secrets.
Closes https://github.com/go-gitea/gitea/issues/39550
---------
Co-authored-by: bircni <bircni@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
* Fix#39610
* Regression of #39262
Also, the old code is very fragile: `#branch_target` is from translation
string, so refactored it together
---------
Co-authored-by: silverwind <me@silverwind.io>
Creating a PR fetches the head commit into the base repo, then pushes
the same objects into `refs/pull/N/head`. Since git 2.54, background
repacks race that push:
1. The push can be rejected with "unable to migrate objects to permanent
storage", see
https://github.com/go-gitea/gitea/actions/runs/37171442185/job/111345034829.
2. For heads with 100+ new objects, the repack can delete the reused
pack, leaving the PR ref pointing at missing objects.
Fetching the head commit with `FetchRemoteTempCommit` and setting the PR
ref with `UpdateRef` avoids both, as the fetch transfers nothing when
the objects exist. Fork PR refs are now updated like AGit PR refs
already are, without going through receive hooks.
Also syncs the PR ref when a PR is reopened again.
https://github.com/go-gitea/gitea/pull/37077 inverted that condition, so
a reopened PR kept a stale ref.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
Closes https://github.com/go-gitea/gitea/issues/39287
Security key nicknames could only be set at registration, so a skipped
nickname left an auto-generated hex name until the key was
re-registered. Each key now has a Rename button opening a dialog with
the current nickname. A nickname used by another of the user's keys
(case-insensitive) or a blank nickname is rejected. Renames are recorded
as `user:webauth:rename` audit events.
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The SSH integration tests read the user's `~/.ssh/config`, so options
like `ControlMaster` reused a connection authenticated with another
test's key and the tests failed with "Cannot find key". Pass `-F none`
so ssh reads no config files.
Rendering a CITATION.cff could use memory far out of proportion to the
file, as every YAML alias copies its target into the formatted citation
and the parser copies `%TAG` prefixes into every node. Files past these
limits show no citation, like unparseable ones do today.
- Skip files over 256 KiB, largest real-world file found is 80 KiB
- Skip files with `%TAG` directives
- Skip files whose aliases add more than 64 Ki nodes and value bytes
- Skip self-referencing anchors, except a sequence listing itself
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
## Summary
When an Actions runner's registration has been deleted (or the
UUID/token is invalid), `FetchTask` and other authenticated runner RPCs
currently return **HTTP 500**
## Change
- Return `connect.NewError(connect.CodeUnauthenticated, ...)` via a
small `unregisteredRunnerError()` helper for both unregistered /
bad-token paths in the interceptor.
- Leave Internal `status.Error` paths unchanged (those should remain
5xx).
- Add a unit test asserting `connect.CodeOf(err) ==
connect.CodeUnauthenticated`.
Fixes#39576
---------
Signed-off-by: Alex Mitre <mitre88@users.noreply.github.com>
Co-authored-by: Alex Mitre <mitre88@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The labels page shows open issue counts, but "Most issues" and "Least
issues" sorted by the total including closed issues, so a label with no
open issues could land in the middle of the list.
- Sort repository and organization labels by their open issue count
- Sort organization labels on a repository's labels page by their open
issues in that repository, which is the count they display
Fixes: https://github.com/go-gitea/gitea/issues/39346
---------
Signed-off-by: piyush295 <mr.piyush295@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Raise the minimum git version to 2.34, the version in Ubuntu 22.04,
Debian 12 and RHEL 8 ship newer, and remove the fallbacks it makes
obsolete.
- Always enable AGit
- Use `diff --skip-to` and `apply -3` unconditionally
- Set the default branch of new repos and wikis via `git init
--initial-branch`
- Detect rebase conflicts via `REBASE_HEAD`
Fixes#39572
The shared user cards template calls the User-only `IsTypeBot` method.
The profile organizations tab passed `*organization.Organization` values
to that template, so `/{username}?tab=organizations` returned a 500
error.
Organizations are now converted to Users before rendering.
Allow bot accounts to use the API when a legacy password-change flag is
set, since bots cannot complete the interactive password-change flow.
Preserve password-change enforcement for human accounts and restrictions
for inactive or prohibited accounts.
Fixes: https://github.com/go-gitea/gitea/issues/39542
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Clean up the navbar template and styling, and fix the navbar stopwatch,
which navigated to the issue instead of opening its popup since
https://github.com/go-gitea/gitea/pull/36965.
1. Add hover background to the create and user menus
2. Simplify navbar HTML and CSS and remove Fomantic styles
3. Render the notification and stopwatch icons once instead of separate
mobile and desktop copies
4. Make the stopwatch a keyboard accessible button whose popup updates
on push and closes when the stopwatch stops
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
Adds `Select All` and `Deselect All` buttons to the custom events
section of the webhook form. This is useful when you need all events but
one.
- The buttons toggle every event checkbox and keep the unsaved-changes
prompt working
- Fix the "Trigger On" radio spacing by removing a leftover Fomantic
checkbox margin
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Due dates set through the API were stored inconsistently. The create
endpoints kept the raw timestamp, issue and pull request edits used end
of day in the client's offset, and only the deadline and milestone edit
endpoints normalized to end of day in the server's UI timezone like the
web UI does. All API due dates now go through
`ParseAPIDeadlineToEndOfDay`.
Editing a pull request with `unset_due_date: false` and no `due_date`
dereferenced a nil pointer and returned 500. It now shares the issue
edit logic and returns 400.
Related to https://github.com/go-gitea/gitea/issues/37620, which is
about the web sidebar and isn't fixed here.
---------
Co-authored-by: silverwind <me@silverwind.io>
`pull_request_review` runs on fork PRs, which includes all backport PRs,
get a read-only token and no secrets, so giteabot cannot write lgtm
labels and statuses there. A no-op `giteabot-review` workflow now
triggers giteabot through `workflow_run`, which gets both. This allows
retiring the legacy fly.io webhook bot.
Part of https://github.com/go-gitea/giteabot/issues/15
The API filter `head_sha` on `GET /repos/{owner}/{repo}/actions/runs`
selects runs by `commit_sha`, so `commit_sha` needs an index. For a
action_run table with 212k rows:
- Without the index: the query read 212k rows, and the API request took
35-52 s.
- With the index: the query read 94 rows in 0.14 s, and the API request
took 2-4 s.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
MSSQL's default READ COMMITTED makes reads wait on writers, so the
runner pickup deadlocks with concurrent claims, flaking
`TestCreateTaskForRunnerConcurrentClaim`.
- Enable `READ_COMMITTED_SNAPSHOT` on MSSQL so it reads like PostgreSQL
and MySQL
- Read the pickup cursor before claiming, a lost claim could skip
waiting jobs
- Add tests that fail without consistent READ COMMITTED
Performance: Writes on MSSQL now also store the previous row version in
tempdb, the same versioning cost PostgreSQL and MySQL always pay, and
Azure SQL enables it by default. Reads no longer block on writers, and a
32-runner pickup stress test ran 2.5x faster with it.
---------
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
Replace citation-js with a Go port of ruby-cff, which GitHub uses for
"Cite this repository", rendering APA and BibTeX server-side and
dropping about 770KB of JS. Output matches GitHub on 1568 of 1571
real-world files, the rest are improvements over GitHub.
- `CITATION.cff` wins over `CITATION.bib`, matched case-insensitively
and through symlinks
- `CITATION.bib` is offered as-is, without APA
Signed-off-by: silverwind <me@silverwind.io>
Vue files only got `eslint-plugin-vue` rules, so stylistic and
TypeScript rules never ran on them, and `import-x` could not see cycles
between `.ts` and `.vue` files.
- Apply the main ESLint config to `.vue` files
- Let `import-x` parse `.vue` files, which surfaced a cycle between
`repo-findfile.ts` and `RepoFileSearch.vue`
- Fix the resulting lint issues
`vue-eslint-parser` is added as a direct dependency because `import-x`
resolves parsers by package name, see
https://github.com/un-ts/eslint-plugin-import-x/issues/381.
On Linux and ChromeOS, Chromium resolves the `math` font family to Latin
Modern Math, which neither installs, so MathML renders with a text font
and brackets and large operators don't stretch, see
https://issues.chromium.org/issues/40069293. The new `--fonts-math`
variable keeps `math` first, so browsers that always resolve it keep
their font. Only Chromium falls through to the math fonts Linux and
ChromeOS install by default:
- `STIX Two Math`: Fedora, and `fonts-stix` on Ubuntu 26.04
- `DejaVu Math TeX Gyre`: Debian 13 and openSUSE
- `Noto Sans Math`: Fedora and ChromeOS, last because Debian and Ubuntu
ship an older version without a `MATH` table
Math fonts also have smaller x-heights than UI fonts, so MathML rendered
smaller than KaTeX and the surrounding text in every browser, see
https://github.com/w3c/mathml-core/issues/41. `font-size-adjust:
ex-height 0.52` scales whichever math font is used to KaTeX's x-height.
KaTeX output is unchanged.
Fixes: https://github.com/go-gitea/gitea/issues/39489
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: bircni <75789103+bircni@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
`UpdatePushMirror` used `AllCols()`, so a sync could overwrite columns
changed concurrently (e.g. `interval`) with stale values. It now updates
only `last_update` and `last_error`, and is renamed to
`UpdatePushMirrorSyncStatus` to match.
Co-authored-by: Giteabot <teabot@gitea.io>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 5) <noreply@anthropic.com>
The `gitea.com/go-chi/session` package only exists for Gitea, so it
moves into `modules/session` to fix its bugs directly. Fixes the flake
in
https://github.com/go-gitea/gitea/actions/runs/36726154500/job/109923538400.
- Sessions are only written back when changed, so a read-only request
can't revert a concurrent change or restore a logged-out session, like
ae808a4a46
- The session cookie is only set once a session holds data
- Every backend refreshes the expiry on load and file sessions are
written atomically
- Also fix https://github.com/go-gitea/gitea/issues/36176
## ⚠️ BREAKING ⚠️
* the `mysql`, `postgres`, `couchbase` and `memcache` session providers
are removed, use `file`, `db` or `redis` instead
* login-related cookies are renamed to `gitea_session` and
`gitea_remember`, if you'd like to use the old names, set `COOKIE_NAME`
and `COOKIE_REMEMBER_NAME` in app.ini
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The `gitea.com/go-chi/cache` package only exists for Gitea, so it moves
into `modules/cache`.
- `ITEM_TTL = -1` disables caching as documented
- Sub-second TTLs round up instead of never expiring
- The Redis adapter no longer grows a `MacaronCache` hash
- Use two-queue cache for in-memory cache
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The `gitea.com/go-chi/captcha` package only exists for Gitea,
so it moves into `modules/imagecaptcha`.
- Reloading an expired challenge shows a new image instead of a broken one
- Every answer is consumed on its first check
- OpenID registration stops after a failed captcha
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The `gitea.com/go-chi/binding` package only exists for Gitea, so it
moves into `modules/web/binding` to fix its bugs directly. Split out of
https://github.com/go-gitea/gitea/pull/39504.
- GET and HEAD always bind the query
- JSON `null` slice elements and nested `TrimSpace` fields bind
correctly
- Integer fields reject out-of-range values instead of wrapping
- An empty JSON body binds nothing and an unknown binding rule is an
error
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
MariaDB 11.6.2+ defaults `innodb_snapshot_isolation` to `ON`, which
fails REPEATABLE READ transactions with error 1020 when a row they write
changed after their first read. Gitea's background work like push
processing writes the same rows, so merges, issue closes and workflow
runs fail sporadically.
- Use READ COMMITTED on MySQL and MariaDB, like PostgreSQL and MSSQL
- Update xorm to v1.4.3
Replaces: https://github.com/go-gitea/gitea/pull/39494
Fixes: https://github.com/go-gitea/gitea/issues/39492
---------
Signed-off-by: silverwind <me@silverwind.io>
Replace time.After with a stoppable time.Timer in BatchChecker.CheckPath.
Avoid accumulating up to 6 pending 5-second timers per file on the normal path.
Preserve the existing per-attribute timeout behavior.
---------
Signed-off-by: Calvin Tjoaquinn <calvintjoa23@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Speeds up two slow git paths. Results match `main` apart
from the `log.follow` fix.
- Activity top authors skip `--numstat` and an unused `rev-list
--count`, they only need names and emails
- Subdirectory listings pass only the directory as pathspec, which
already matches all its entries
- Directory listings pass `--no-follow` to `git log`, a configured
`log.follow` disabled parent rewriting and gave wrong last commits
| Benchmark | main | PR | Change |
|---|--:|--:|--:|
| Top authors, gitea, 1 month | 632 ms | 21 ms | -97% |
| Top authors, gitea, 1 year | 3216 ms | 68 ms | -98% |
| Top authors, tea | 62 ms | 12 ms | -80% |
| Listing, git `Documentation/technical` | 189 ms | 91 ms | -52% |
| Listing, gitea `options/license` | 329 ms | 208 ms | -37% |
| Listing, gitea `templates/repo` | 294 ms | 223 ms | -24% |
Tested with unit and sqlite integration tests in default and `gogit`
builds on git 2.25 and 2.56, and by comparing listing results and cache
writes with `main` on randomized histories and the gitea, tea and git
repos. Benchmarks are medians of 8 interleaved macOS runs.
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>
description:Track a Gitea release (for release managers).
title:"Release Gitea "
body:
- type:markdown
attributes:
value:|
Follow the [release management guide](https://github.com/go-gitea/gitea/blob/main/docs/release-management.md).
Replace the examples below and mark inapplicable tasks as such.
CI signs the tag, generates release notes, and publishes binaries and containers. Track verification here; no manual changelog PR or release upload is needed.
- type:input
id:version
attributes:
label:Version
placeholder:"28.0.1"
validations:
required:true
- type:input
id:branch
attributes:
label:Release branch
placeholder:"release/v28"
validations:
required:true
- type:textarea
id:checklist
attributes:
label:Release checklist
description:Keep workflow runs, release URLs, and follow-up PRs alongside the relevant tasks.
value:|
### Preparation
- [ ] Resolve release blockers and confirm milestone issues and PRs are resolved or deferred.
- [ ] Confirm required backports are merged and release branch CI passes.
- [ ] For a new release line, create the release branch and tag its fork point on main with the next version's -dev tag.
### Release
- [ ] Run https://github.com/go-gitea/gitea/actions/workflows/release-create-tag.yml on the release branch with the selected version and obtain maintainer approval.
- [ ] Confirm https://github.com/go-gitea/gitea/actions/workflows/release-tag-version.yml succeeds for the new tag (binaries and containers).
- [ ] Verify the public GitHub release, generated notes, binary attachments, and signatures at https://github.com/go-gitea/gitea/releases.
- [ ] Verify binaries and signatures at https://dl.gitea.com/gitea/ for this version.
- [ ] Verify versioned regular and rootless images on Docker Hub and GHCR, and smoke-test the release.
### Follow-up
- [ ] Verify the automated https://dl.gitea.com/gitea/version.json update, where applicable to this release line.
- [ ] Verify automated Helm chart and Terraform provider update PRs and follow up if needed.
- [ ] Check Homebrew and Snap availability; record any outstanding packaging follow-up.
- [ ] Confirm documentation reflects the release, where applicable.
- [ ] Confirm and merge the release blog post, if planned: https://gitea.com/gitea/blog.
- [ ] Announce the release in Discord #announcements.
validations:
required:true
- type:textarea
id:notes
attributes:
label:Blockers and notes
description:Link outstanding work or release-specific checks using full URLs. Do not include undisclosed security details.
-Read relevant developer documentation in the `docs` folder
-PR descriptions: minimal, only what and why, no task or file listings. Include screenshots for UI changes, before and after when modifying existing UI. Aim for less than 1000 characters
-Before working on code, read the `docs` guides for every area touched: `guidelines-backend.md` for Go, `guidelines-frontend.md` for TS, CSS and Vue, both for templates, `testing.md` for tests, `development.md` for config options and migrations, `guidelines-refactoring.md` for refactors
-Before designing a feature or behavior, check prior art in GitHub and GitLab
- Don't add code for theoretical edge cases, only for ones that can realistically occur. Ask humans to make decisions if the edge case matters, comment the edge case for why it can be ignored if it doesn't matter
- Write docs concisely and user-facing, skip implementation details
- PR descriptions: minimal, only what and why, no task or file listings, no headings. Aim for less than 1000 characters. References go at the end of the description like `Fixes: <url>`.
- PR descriptions and update comments: Include screenshots for UI changes, before and after side by side. Take them at 4x scale in dark theme, cropped to the changes with some padding, and embed them at half CSS size
- Reference issues and PRs by full URL, not by number
- Use Conventional Commits for commit messages and PR titles, plus Gitea's `enhance` type for user-facing enhancements
- Add an `Assisted-by: AGENT_NAME:MODEL_VERSION` trailer to commit messages, never `Co-Authored-By` or `Signed-off-by`
- Add an `Assisted-by: AGENT_NAME:MODEL_VERSION` trailer to commit messages and PR descriptions, never `Co-Authored-By` or `Signed-off-by`
- Attribute agent authorship on one trailing line in issue and PR comments, never as a PR description section
- Never rewrite git history unless asked, update PRs with new commits and normal push
- Comments: write almost none, short and preferably same-line, explaining why for a future reader. Never narrate code, the change or the prompt. Preserve existing ones that still apply. If you need to write a paragraph-long comment, rethink your implementation, it is likely too complicated
@@ -12,6 +16,7 @@
- In `options/locale`, only edit `locale_en-US.json`, other locales are synced automatically
- In TS, use `!` instead of `?.`/`??` when a value always exists
- In Go, prefer to use modern language features wherever possible
- In Go, function-name prefixes in errors like `fmt.Errorf("Foo: %w", err)` must always name the function they are in
- Write sizes as multiplications like `64 * 1024`, not bit shifts like `64 << 10`
- Prefer `tw-*` utilities over inline `style` and `flex-*` helpers over per-child `tw-ml-*`/`tw-mr-*` margins, falling back to `tw-*` where specificity requires `!important`
- Run `make fmt` after `.go` edits, `make tidy` after `go.mod` edits, `make generate-swagger` after API changes, and lint what changed with `make lint-go`, `lint-js`, `lint-css` or `lint-templates`
@@ -58,7 +58,8 @@ Contributions made with the assistance of AI tools are welcome, but contributors
2. Manually test the changes and add appropriate automated tests where feasible.
3. Only use AI to assist in contributions that you understand well enough to explain, defend, and revise yourself during review.
4. Disclose AI-assisted content clearly.
5. Do not use AI to reply to questions about your issue or pull request. The questions are for you, not an AI model.
5. Do not rely solely on AI to generate replies or engage in discussions.
Ensure your responses are concise, accurate, and thoughtful, avoiding generic or low-quality AI-generated content.
6. AI may be used to help draft issues and pull requests, but contributors remain responsible for the accuracy, completeness, and intent of what they submit.
Maintainers reserve the right to close pull requests and issues that do not disclose AI assistance, that appear to be low-quality AI-generated content, or where the contributor cannot explain or defend the proposed changes themselves.
@@ -169,13 +170,13 @@ In the PR title, describe the problem you are fixing, not how you are fixing it.
Use the first comment as a summary of your PR. \
In the PR summary, you can describe exactly how you are fixing this problem.
PR titles must follow the [Conventional Commits](https://www.conventionalcommits.org/) format, because PRs are squash-merged and the PR title becomes the resulting commit message:
PR titles must follow the [Conventional Commits](https://www.conventionalcommits.org/) format, because PRs are squash-merged and the PR title becomes the resulting commit message and release notes entry:
```text
type(scope)!: subject
```
The scope in parentheses is optional. A `!` immediately before the colon marks a [breaking change](https://www.conventionalcommits.org/en/v1.0.0/#summary): either `type!:` or `type(scope)!:` (not `type!(scope):`).
The scope in parentheses is optional. A `!` immediately before the colon marks a [breaking change](https://www.conventionalcommits.org/en/v1.0.0/#summary): either `type!:` or `type(scope)!:` (not `type!(scope):`). Do not mark `build`, `chore`, `ci`, `docs`, `style`, or `test` as breaking.
@@ -68,7 +68,7 @@ Get more information from [the translation section of our contributing guide](CO
## Official and Third-Party Projects
We provide an official [go-sdk](https://gitea.com/gitea/go-sdk), a CLI tool called [tea](https://gitea.com/gitea/tea) and an [action runner](https://gitea.com/gitea/runner) for Gitea Action.
We provide an official [go-sdk](https://gitea.com/gitea/go-sdk), a CLI tool called [gt](https://gitea.com/gitea/cli) and an [action runner](https://gitea.com/gitea/runner) for Gitea Action.
We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea), where you can discover more third-party projects, including SDKs, plugins, themes, and more.
@@ -120,7 +120,7 @@ See [app.example.ini](https://github.com/go-gitea/gitea/blob/main/custom/conf/ap
**Where can I find the security patches?**
In the [release log](https://github.com/go-gitea/gitea/releases) or the [change log](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md), search for the keyword `SECURITY` to find the security patches.
Check the [release notes](https://github.com/go-gitea/gitea/releases) and [security advisories](https://github.com/go-gitea/gitea/security/advisories) for security patches.
(more FAQs are listed in [FAQ documentation](https://docs.gitea.com/help/faq))
@@ -14,12 +14,28 @@ Reports must be filed against a supported version. Before reporting, verify that
## Reporting a Vulnerability
Please **DO NOT** file a public issue. Instead, report the vulnerability privately by opening a [GitHub security advisory](https://github.com/go-gitea/gitea/security/advisories/new), or optionally by sending your report to `security@gitea.io`.
Please **DO NOT** file a public issue. Instead, report the vulnerability privately by opening a [GitHub security advisory](https://github.com/go-gitea/gitea/security/advisories/new), or optionally by sending your report to `security@gitea.io`. Former is preferred if given a choice.
If reporting via GitHub, open an advisory per found issue. You can group the found issues if they relate to a single area (same feature, same fault present in multiple routes) but do not group unrelated reports into one as it makes it much harder to assess and track for duplicates.
Do not use LLM to write the reports. It's meant for people to read so take your time and use your own words to write it. If you have used LLM to find the bug, be transparent about it and verify that it is not hallucination before submitting the report.
If your report turns out to be a duplicate, you will be credited in the original report.
### Report requirements
When filing the report please document it. Reports must include:
- Version or a range of versions it was successfully tested against. If reproducible against the `main` branch, a commit hash is also acceptable. Ambiguous versions like `latest` are not acceptable.
- Settings used, especially when they diverge from the defaults
- Reproduction steps from freshly installed instance. If the fault is only reproducible through upgrade from older version, reproduction should state that.
Take your time to format it properly as it'll be much easier to read. Prefer a concise writing style.
When submitting an advisory, it should have the `gitea.dev` go package as package `name.cd`
### Triage process
Github does not have any search or tagging in GHSA so in most cases your report will leave triage state when we have a fix for it in works for next release.
This helps us keep track of items fixed and pending release and separate them from things that we either haven't looked at yet or have not fixed.
## Protecting Security Information
Due to the sensitive nature of security information, you can use the below GPG public key to encrypt your mail body.
;; Interpret X-Forwarded-For header or the X-Real-IP header and set this as the remote IP for the request
;; Interpret the X-Real-IP, X-Forwarded-For or Forwarded header, in that order, and set this as the remote IP for the request.
;; The client port is taken from the header value or, with X-Real-IP, from X-Real-Port.
;REVERSE_PROXY_LIMIT = 1
;;
;; List of IP addresses and networks separated by comma of trusted proxy servers. Use `*` to trust all.
@@ -536,7 +537,7 @@ INTERNAL_TOKEN =
;CONTENT_SECURITY_POLICY_GENERAL =
;;
;; Egress mode toggles between strictness of outgoing requests:
;; Lax requires addresses to be allowed only if they are in private ranges, it allows all public ones
;; Lax requires non-public targets (private, loopback, link-local, CGNAT and special-use ranges) to be allowed, it allows all public ones
;; Strict requires an explicit allow of all addresses
; EGRESS_MODE = lax
;;
@@ -551,10 +552,12 @@ INTERNAL_TOKEN =
;; a bracketed set of ports and ranges, | separated: *.mydomain.com:[80|443|3000-3010]
;; all ports: *.mydomain.com:*
;; A portless entry covers all ports in Lax mode, only 80 and 443 in Strict mode
;; Port specs apply only where the list is consulted: in Lax mode that is private, loopback and CGNAT
;; targets alone, public targets are allowed on every port whatever the list says. In Strict mode every
;; Port specs apply only where the list is consulted: in Lax mode that is non-public targets alone,
;; public targets are allowed on every port whatever the list says. In Strict mode every
;; target is checked, so ports restrict public hosts too.
;; Reserved addresses like link-local and cloud metadata are denied
;; Non-public targets need an IP or built-in entry, a host name entry alone never covers them.
;; Reserved addresses (the IPv4-embedding NAT64, Teredo and 6to4 ranges, this-network, multicast and
;; broadcast) are denied whatever the list says. To reach them configure an HTTP proxy
;; This list is enforced on direct connections only. When an HTTP proxy is configured, restricting the proxied target is the proxy server's responsibility.
;ALLOWED_HOST_LIST =
@@ -634,9 +637,10 @@ ENABLED = true
;; Pre-register OAuth2 applications for some universally useful services
;; Change the number of users that are displayed in reactions tooltip (triggered by mouse hover).
;REACTION_MAX_USER_NUM = 10
;;
@@ -1670,13 +1672,13 @@ LEVEL = Info
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; General queue queue type, currently support: persistable-channel, channel, level, redis, dummy
;; default to persistable-channel
;TYPE = persistable-channel
;; General queue type, currently support: level, channel, redis, dummy
;; default to level
;TYPE = level
;;
;; data-dir for storing persistable queues and level queues, individual queues will default to `queues/common` meaning the queue is shared.
;; data-dir for storing level queues, individual queues will default to `queues/common` meaning the queue is shared.
;; Relative paths will be made absolute against "APP_DATA_PATH"
;DATADIR = queues/
;DATADIR = queues/common
;;
;; Default queue length before a channel queue will block
;LENGTH = 100000
@@ -1684,7 +1686,7 @@ LEVEL = Info
;; Batch size to send for batched queues
;BATCH_LENGTH = 20
;;
;; When `TYPE` is `persistable-channel`, this provides a directory for the underlying leveldb
;; When `TYPE` is `level`, this provides a directory for the underlying leveldb
;; or additional options of the form `leveldb://path/to/db?option=value&....`, and will override `DATADIR`.
;; When `TYPE` is `redis` and this is left empty, it falls back to the shared [redis] CONN_STR.
;CONN_STR =
@@ -1714,59 +1716,18 @@ LEVEL = Info
;; - manage_ssh_keys: a user cannot configure ssh keys
;; - manage_gpg_keys: a user cannot configure gpg keys
;; - manage_mfa: a user cannot configure mfa devices
;; - manage_credentials: a user cannot configure emails, passwords, or openid
;; - manage_credentials: a user cannot configure emails or passwords
;USER_DISABLED_FEATURES =
;; Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be "deletion", "manage_ssh_keys", "manage_gpg_keys", "manage_mfa", "manage_credentials". This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior.
;; - deletion: a user cannot delete their own account
;; - manage_ssh_keys: a user cannot configure ssh keys
;; - manage_gpg_keys: a user cannot configure gpg keys
;; - manage_mfa: a user cannot configure mfa devices
;; - manage_credentials: a user cannot configure emails, passwords, or openid
;; - manage_credentials: a user cannot configure emails or passwords
;; - change_username: a user cannot change their username
;; - change_full_name: a user cannot change their full name
;; Image URL for the "image" fallback, used as-is, defaults to Gitea's builtin repository avatar
;REPOSITORY_AVATAR_FALLBACK_IMAGE =
;;
;; Max Width and Height of uploaded avatars.
;; This is to limit the amount of RAM used when resizing the image.
@@ -2093,7 +2053,7 @@ LEVEL = Info
;ENABLED = true
;;
;; Comma-separated list of allowed issue/pull-request attachment file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
@@ -74,15 +74,13 @@ Changes to Gitea must be reviewed before they are accepted, including changes fr
We require two maintainer approvals for every PR. When that is satisfied, your PR gets the `lgtm/done` label. After that, you mainly fix merge conflicts and respond to or implement maintainer requests; maintainers drive getting the PR merged.
If a PR has `lgtm/done`, no open discussions, and no merge conflicts, any maintainer may add `reviewed/wait-merge`. That puts the PR in the merge queue. PRs are merged from the queue in the order of this list:
If a PR has `lgtm/done`, no open discussions, and no merge conflicts, a merger rewrites its title and description into the final [commit message](#commit-messages) and clicks **Merge when ready**. That adds the PR to the [merge queue](https://github.com/go-gitea/gitea/queue/main), which tests it on top of the latest `main` and the PRs ahead of it, and merges it once CI passes. Edits to the title and description no longer reach the commit once the queue starts building the PR, so make them before queueing.
Gitea uses its own tool, <https://github.com/go-gitea/gitea/blob/main/.github/workflows/giteabot.yml>, to automate parts of the review process. The backporter:
Gitea uses its own tool, <https://github.com/go-gitea/gitea/blob/main/.github/workflows/giteabot.yml>, to automate parts of the review process. It:
- Counts maintainer approvals and sets the `lgtm/*` labels and the `giteabot/lgtm` status.
- Takes a PR out of the merge queue when it loses its approval.
- Creates a backport PR when needed after the initial PR merges.
- Removes the PR from the merge queue after it merges.
- Keeps the oldest branch in the merge queue up to date with merges.
### Final call
@@ -164,7 +162,7 @@ Any account with write access (including bots and TOC members) **must** use [2FA
Mergers are the maintainers who carry out the final merge of approved PRs. Their responsibilities, described throughout this guide, are:
-Merging PRs from the [merge queue](#getting-prs-merged) in order, once a PR has `lgtm/done`, no open discussions, and no merge conflicts.
-Adding PRs to the [merge queue](#getting-prs-merged) once a PR has `lgtm/done`, no open discussions, and no merge conflicts.
- Rewriting the PR title and description prior to the merge, making the [commit message](#commit-messages) clear.\
In particular, mergers should edit the PR description.\
Mergers should **not** edit the actual commit message except to remove unnecessary information. Because of that, even if users are looking at the PR, they can understand what changed.
@@ -8,10 +8,9 @@ This document describes the release cycle, backports, versioning, and the releas
We backport PRs given the following circumstances:
1.Feature freeze is active, but `<version>-rc0` has not been released yet. Here, we backport as much as possible. <!-- TODO: Is that our definition with the new backport bot? -->
2.`rc0` has been released. Here, we only backport bug- and security-fixes, and small enhancements. Large PRs such as refactors are not backported anymore. <!-- TODO: Is that our definition with the new backport bot? -->
3. We never backport new features.
4. We never backport breaking changes except when
1.We backport bug- and security-fixes and small enhancements. Large changes such as refactors are not backported.
2.We never backport new features.
3. We never backport breaking changes except when
1. The breaking change has no effect on the vast majority of users
2. The component triggering the breaking change is marked as experimental
@@ -25,6 +24,8 @@ Now, that's not a requirement anymore as our [backport bot](https://github.com/G
The `backport/manual` label signifies either that you want to backport the change yourself, or that there were conflicts when backporting, thus you **must** do it yourself.
The bot labels open PRs targeting `main` that have a `backport/<version>` label with `backport/conflict-free` when they would cherry-pick onto the latest release branch without conflicts.
### Format of backport PRs
The title of backport PRs should be
@@ -53,7 +54,6 @@ We use a release schedule so work, stabilization, and releases stay predictable.
### Cadence
- Aim for a major release about every three or four months.
- Roughly two or three months of general development, then about one month of testing and polish called the **release freeze**.
- *Starting with v1.26 the release cycle will be more predictable and follow a more regular schedule.*
### Release schedule
@@ -65,16 +65,6 @@ We will try to publish a new major version every three months:
- v1.28.0 in September 2026
- v1.29.0 in December 2026
#### How is the release handled?
- The release manager will tag the release candidate (e.g. `v1.26.0-rc0`) and publish it for testing in the **first week of the release month**.
- If there are no major issues, the release manager will check with the other maintainers and then tag the final release (e.g. `v1.26.0`) in the **one or two weeks following the release candidate**.
### Feature freeze
- Merge feature PRs before the freeze when you can.
- Feature PRs still open at the freeze move to the next milestone. Watch Discord for the freeze announcement.
- During the freeze, a **release branch** takes fixes backported from `main`. Release candidates ship for testing; the final release for that line is maintained from that branch.
### Patch releases
During a cycle we may ship patch releases for an older line. For example, if the latest release is v1.2, we can still publish v1.1.1 after v1.1.0.
@@ -99,17 +89,13 @@ be reviewed by two maintainers and must pass the automatic tests.
## Releasing Gitea
- Let MAJOR, MINOR and PATCH be Major, Minor and Patch version numbers, PATCH should be rc1, rc2, 0, 1, ...... MAJOR.MINOR will be kept the same as milestones on github or gitea in future.
- Before releasing, confirm all the version's milestone issues or PRs has been resolved. Then discuss the release on Discord channel #maintainers and get agreed with almost all the owners and mergers. Or you can declare the version and if nobody is against it in about several hours.
-If this is a big version first you have to create PR for changelog on branch `main` with PRs with label `changelog` and after it has been merged do following steps:
-Create `-dev` tag as `git tag -s -F release.notes vMAJOR.MINOR.0-dev` and push the tag as `git push origin vMAJOR.MINOR.0-dev`.
-When CI has finished building tag then you have to create a new branch named `release/vMAJOR.MINOR`
-If it is bugfix version create PR for changelog on branch `release/vMAJOR.MINOR` and wait till it is reviewed and merged.
- Add a tag as `git tag -s -F release.notes vMAJOR.MINOR.PATCH`, release.notes file could be a temporary file to only include the changelog this version which you added to `CHANGELOG.md`.
- And then push the tag as `git push origin vMAJOR.MINOR.$`. CI will automatically create a release and upload all the compiled binary. (But currently it doesn't add the release notes automatically. Maybe we should fix that.)
- If needed send a frontport PR for the changelog to branch `main` and update the version in `docs/config.yaml` to refer to the new version.
- Send PR to [blog repository](https://gitea.com/gitea/blog) announcing the release.
Track each release using the [release issue template](https://github.com/go-gitea/gitea/issues/new?template=release.yaml).
-Before releasing, confirm all the version's milestone issues or PRs have been resolved. Then discuss the release on Discord channel#maintainers and get agreed with almost all the owners and mergers. Or you can declare the version and if nobody is against it in about several hours.
-When creating a release branch, tag its fork point on `main` as the next version's `-dev` tag, e.g. `v30.0.0-dev` for `release/v29`.
-In the GitHub Actions tab, open the `release-create-tag` workflow, click "Run workflow", select the release branch and enter a version such as `28.0.1`. After maintainer approval, it pushes a signed tag and CI publishes the release with generated notes.
-Optionally send a PR to the [blog repository](https://gitea.com/gitea/blog) announcing the release.
- Verify all release assets were correctly published through CI on dl.gitea.com and GitHub releases. Once ACKed:
-bump the version of https://dl.gitea.com/gitea/version.json
-verify the automated update of https://dl.gitea.com/gitea/version.json, where applicable to the release line
- merge the blog post PR
- announce the release in discord `#announcements`
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.