fix: avoid enumerating every public repository in issue search - #38992
Conversation
`/repos/issues/search` and `/issues/search` build the repository filter by
calling `repo_model.SearchRepositoryIDs` and passing the resulting slice to
the indexer as `SearchOptions.RepoIDs`.
Both call sites already intend to leave public repositories to the indexer:
if opts.AllPublic {
allPublic = true
opts.AllPublic = false // set it false to avoid returning too many repos, we could filter by indexer
}
However `SearchRepoOptions.AllPublic` is only read inside the
`if opts.OwnerID > 0` branch of `SearchRepositoryCondition`, and `OwnerID` is
only set when the `owner` query parameter is present. Without `owner` the flag
has no effect, so the search condition falls back to
`AccessibleRepositoryCondition`, whose first clause matches every non-private
repository on the instance. `SearchRepositoryIDs` is called without a
`PageSize`, so no `LIMIT` is applied and every one of those IDs is materialised
into a slice and then expanded into `repo_id IN (?, ?, ...)`.
The enumerated public IDs are redundant: `allPublic` is forwarded to the
indexer, which already ORs in every public repository (`is_private = false` for
the db indexer, `is_public` for bleve/elasticsearch/meilisearch). On a large
instance this makes the endpoint materialise tens of thousands of IDs per
request and can exceed the driver's bind parameter limit, turning the endpoint
into a 500 for every filter combination. Site administrators are affected
worst, because `SearchRepositoryCondition` skips the accessible-repository
condition for them and enumerates the whole `repository` table.
Restrict the enumeration to private repositories when `allPublic` is set. The
final result set is unchanged, since the enumerated public IDs were a subset of
what the indexer's `AllPublic` filter already matches.
Verification: `make test-integration#TestAPIIssue` and
`make test-integration#TestSearchIssues`, plus `TestIssue*`, `TestPullMerge*`,
`TestAPIRepo*` and `TestExplore*` in `tests/integration`.
The existing integration tests assert result counts, which this change deliberately leaves untouched, so none of them can fail if the fix is reverted. Assert the property the fix is actually about: when allPublic is set, the repository IDs handed to the indexer contain no public repository. Both a site administrator and a regular user are covered, because SearchRepositoryCondition takes a different path for admins. Each case also asserts that an accessible private repository is still present, so narrowing the query too far fails here rather than silently dropping results. Verified to fail without the fix: the admin case reports 18 public repositories in the enumerated list.
routers/api/v1/repo and routers/web/repo carried near-identical copies of the same ~50 lines resolving the repository filter for an issue search, so the previous fix had to be applied twice and only one copy was reachable from a unit test. Move the block to routers/common as SearchIssuesRepoIDs, taking the request-scoped inputs as a struct so both callers keep mapping errors to their own response format. The web copy now maps util.ErrNotExist and util.ErrInvalidArgument to 400 the same way the API copy already did, instead of testing each error type separately. The regression test moves along with the code and now covers both endpoints. It also gains an anonymous case, which asserts the filter collapses to the indexer's allPublic flag: before the fix that request enumerated all 36 public repositories in the fixtures.
There was a problem hiding this comment.
Pull request overview
This PR fixes a performance/scalability bug in cross-repository issue search by preventing the handlers from materializing and binding IDs for every public repository when the indexer can already match public repositories via an AllPublic flag.
Changes:
- Extracted shared “issue search repo filter” logic into
routers/common.SearchIssuesRepoIDsfor both API and web handlers. - When
allPublicis in effect, restricted repository ID enumeration to private repositories only (leaving public repos to the indexer). - Added focused unit tests to ensure no public repositories are enumerated into the repo ID list when
allPublicis set.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| routers/web/repo/issue_list.go | Switches web issue search to use the shared repo-ID resolver helper. |
| routers/common/issue_filter.go | Introduces SearchIssuesRepoIDs helper and applies private-only restriction when allPublic is enabled. |
| routers/common/issue_filter_test.go | Adds tests asserting public repos are not enumerated when allPublic is set and validates owner/team edge cases. |
| routers/api/v1/repo/issue.go | Removes the local repo-ID builder and uses the shared helper instead. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Why does your agent not read AGENTS.md? PR description is 4.8k characters, must be below 1k. I'm inclined to close PRs that obviously never read that file. Line 4 in 89b891b |
The added comments narrated the change instead of explaining why for a future reader. Keep the reason inline where it is not obvious and drop the rest. Assisted-by: Codet:unspecified
Updated. |
The enumeration was narrowed to private repositories, but bleve, elasticsearch and meilisearch index is_public as `!repo.IsPrivate && repo.Owner.Visibility.IsPublic()`. A public repository under a limited or private owner matches neither that flag nor the narrowed list, so its issues fell out of search on every instance not running the db indexer, which is not the default. Exclude exactly what the indexer already matches instead. Spelling the condition positively keeps the owner subquery over the limited/private minority rather than every public user, and composing it onto SearchRepositoryCondition avoids both a new SearchRepoOptions field and an ORDER BY over a set that nothing orders. An anonymous or public-only search resolves to an unsatisfiable condition, so it now skips the query altogether. AllPublic and AllLimited only take effect under an owner filter, where this function always cleared them, so they never reached the query and are gone. Assisted-by: Claude:Opus 5
|
Cleanups and bug fixes done in 52c657e. |
HTTPError renders only its first content string, so the error passed alongside "SearchIssuesRepoIDs" was discarded and nothing else logged it. An internal failure here left an operator with a 500 carrying a bare handler name and no trace. The API twin has always logged via APIErrorInternal. Assisted-by: Claude:Opus 5
… (#39000) Backport #38992 by @lunny Both issue search endpoints resolve their repository filter with `SearchRepositoryIDs` and pass the result to the indexer as `RepoIDs`. They mean to leave public repositories to the indexer, but `SearchRepoOptions.AllPublic` is only read when `OwnerID > 0`, so without an `owner` filter the flag does nothing and every public repository is enumerated, without a `LIMIT`, into `repo_id IN (...)`. Those IDs are redundant, as `allPublic` is passed to the indexer, which already matches every public repository. On a large instance this binds tens of thousands of parameters and can fail in the driver, making the endpoint return 500 for every filter. Admins are worst hit, as `SearchRepositoryCondition` skips their accessible-repository condition and enumerates the whole table. Restrict the enumeration to private repositories. The result set is unchanged, as the dropped IDs are a subset of what `allPublic` matches. Both endpoints held copies of this block, so it moves to `routers/common`. Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
* origin/main: fix(actions): show "Complete job" logs when the last step is skipped (go-gitea#38939) fix: avoid enumerating every public repository in issue search (go-gitea#38992) refactor: deploy key and private route handlers (go-gitea#38999) chore: update eslint and stylelint configs and re-sync `modern-normalize` (go-gitea#38982) enhance: use browser's locale to detect week's first day for the contribution map (go-gitea#38995) fix(actions): Fix how jobs in matrixes are grouped (go-gitea#38980) enhance(ui): forced colors mode enhancements (go-gitea#38991) fix: resolve YAML anchors and aliases in Actions workflows (go-gitea#38984) [skip ci] Updated translations via Crowdin fix(lfs): ensure lock listing paginates with a total order (go-gitea#38850) fix: resolve actions commit status permission per repository (go-gitea#38977) chore(deps): update dependency go to v1.26.7 (go-gitea#38987) # Conflicts: # models/asymkey/deploy_key.go # routers/api/v1/repo/key.go # routers/web/repo/setting/deploy_key.go # services/asymkey/deploy_key.go # services/convert/convert.go # templates/repo/settings/deploy_keys.tmpl
* origin/main: (1064 commits) fix(actions): enforce fork pull request trust boundaries (go-gitea#39005) fix(git): restrict hook permissions (go-gitea#39008) fix(api): enforce repository creation token authorization (go-gitea#39007) fix(api): enforce public-only scope for compare heads (go-gitea#39006) fix(repo): hide repositories of hidden owners (go-gitea#39009) [skip ci] Updated translations via Crowdin fix(actions): allow larger scheduled workflows (go-gitea#38985) fix(actions): show "Complete job" logs when the last step is skipped (go-gitea#38939) fix: avoid enumerating every public repository in issue search (go-gitea#38992) refactor: deploy key and private route handlers (go-gitea#38999) chore: update eslint and stylelint configs and re-sync `modern-normalize` (go-gitea#38982) enhance: use browser's locale to detect week's first day for the contribution map (go-gitea#38995) fix(actions): Fix how jobs in matrixes are grouped (go-gitea#38980) enhance(ui): forced colors mode enhancements (go-gitea#38991) fix: resolve YAML anchors and aliases in Actions workflows (go-gitea#38984) [skip ci] Updated translations via Crowdin fix(lfs): ensure lock listing paginates with a total order (go-gitea#38850) fix: resolve actions commit status permission per repository (go-gitea#38977) chore(deps): update dependency go to v1.26.7 (go-gitea#38987) chore: form binding trim space (go-gitea#38978) ... # Conflicts: # modelmigration/migrations.go # modelmigration/v1_26/v326.go # modelmigration/v1_26/v326_test.go # models/fixtures/release.yml # services/convert/release.go # services/release/release.go # templates/repo/home_sidebar_bottom.tmpl
* refactor: private endpoints (go-gitea#38964) 1. remove dead code (SetDefaultBranch) 2. remove useless and unsafe code (AddLogger) * ci: improve caching (go-gitea#38958) - only `cache-seeder` writes caches, every other workflow restores. Saves were being rejected once the repo went over its cache budget, leaving main's caches stale and PR runs building cold - seed the pnpm store and uv caches next to the go ones, so PRs warm-start on them rather than installing from scratch - prune keeps a single generation per key, including across go versions, where a toolchain bump leaves the previous build cache unusable. Reclaims ~2.6 GB immediately - prune runs every 6h instead of daily and trims to 6 GB, since CodeQL writes ~200 MB per push to main from outside this repo's workflows - pull requests and release branches no longer write pnpm, uv and binfmt caches, whose ref-scoped copies are never read again --------- Signed-off-by: silverwind <me@silverwind.io> * enhance(ui): tint toast backgrounds by level (go-gitea#38919) Toasts now use the same tinted backgrounds and borders as the flash messages, replacing the solid full-color style. The first commit reverts go-gitea#38842, the second re-applies it with tinting. --------- Co-authored-by: silverwind <me@silverwind.io> * [skip ci] Updated translations via Crowdin * refactor: http request binding (go-gitea#38971) Better than before, still not good enough (more work can be done in the future) And add the missing error handling in the PrivateContext "bind" middleware. By the way, picked some "TrimSpace" changes from "fix: trim whitespace from SMTP address and port - go-gitea#38934" (fix go-gitea#38926) * fix: honor environment variables during install (go-gitea#38974) Environment variables must be applied to the "install form" config before the config values are used. Fixes go-gitea#38911 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> * chore: form binding trim space (go-gitea#38978) Use "binding:TrimSpace" instead of fragile IsEmptyString And fix a bug in locale's `HasKey`: it should also try the default language if current language doesn't have the translation key, a new test is added. * ui * refactor: share access token scope parsing between admin and user handlers Extract the duplicated "scope-*" form parsing into auth.AccessTokenScopeFromForm and use it from both the user-facing ApplicationsPost and the new admin bot-token handler. * fix(auth): block bot and organization accounts from interactive sign-in Bot accounts are meant for token-based automation only, but the reverse-proxy and external-source (LDAP/SMTP/PAM) auth paths resolved users by name/email without checking the user type. A bot whose name or email matched a proxy header or external identity could obtain an interactive session. Guard reverse-proxy auth and the UserSignIn external-source fallback so only individual users may sign in interactively, matching the existing local-password and OAuth2 behavior. Add regression tests covering both. Assisted-by: Claude:claude-opus-4-8 * feat: convert users between individual and bot via UI, API and CLI Allow a site admin to convert an existing account between the individual and bot types. Only individual <-> bot is permitted; organizations and reserved types are rejected. When converting to a bot the account becomes a local, token-only account: password, auth source and persisted sessions are cleared, OAuth2 applications/grants and external login links are removed, while access tokens and owned content (repositories, org membership, ...) are kept. Exposed through the admin Edit User page (User Type dropdown), the API (POST /admin/users/{username}/convert-type) and the CLI (gitea admin user change-type), all backed by user_service.ConvertUserType. The CLI user-type parsing is shared with "user create". Assisted-by: Claude:claude-opus-4-8 * docs: add bot user design document Document the bot account model: definition, the no-interactive-sign-in guarantee across all auth paths, the capability matrix, and the individual <-> bot conversion rules with every side effect spelled out. Assisted-by: Claude:claude-opus-4-8 * fix: harden bot account creation, conversion and token management - reject and clear passwords when creating a bot via the admin UI, so bots stay passwordless like the CLI and edit-user paths already enforce - wrap the individual->bot credential teardown in a transaction so a mid-sequence failure cannot leave a half-converted account - guard DeleteBotToken so the admin bot-token route only acts on bots Assisted-by: Claude Code:claude-opus-4-8 * refactor * fix lint * fixes * fix(admin): block impersonation of bot users Bot accounts are non-interactive: converting a user to a bot strips its password and auth source so it can never sign in. Impersonation sets the session UID directly, bypassing credentials entirely, which would hand out a session that signing in could never produce. Guard the handler and hide the button on the admin user view page. Assisted-by: Claude:claude-opus-5 * feat(admin): add user type filter to the admin user list The admin user list mixes individuals, bots, reserved and remote accounts with no way to narrow them down. Add a "User Type" dropdown that filters to individuals or bots; unfiltered behaviour is unchanged. * adress comments * adress comments * cleanup * adress feedback * cleanup * review: address remaining feedback on bot-account PR - Replace the new RenderWithErrDeprecated call for the bot-admin error with a flash message and redirect, per reviewer request to not add more usages of the deprecated helper. - Drop the unit TestImpersonateUser duplicate and cover bot impersonation rejection in the TestAdminBotUser integration test. - Update TestConvertUserTypeRejectsNonConvertibleTarget to expect 400, matching APIErrorAuto's current mapping of invalid-argument errors. Pick-up of go-gitea#38181 by bircni, whose authorship is preserved via the merged commits. 💘 Generated with Crush Assisted-by: Crush:glm-5.3 * test(activities): cover org-branch bot skip in repo transfer notifications The organization path of CreateRepoTransferNotification skips bot members and notifies the rest by their real user id, but had no test coverage. Assisted-by: Claude Code:claude-fable-5 * docs: restore and expand the bot user design document Reinstates the design doc removed during the original review, with the sign-in enforcement matrix and conversion side-effects corrected to match the current implementation (renamed error, SSPI/session/OpenID handling, kept vs cleared artifacts). Assisted-by: Claude Code:claude-fable-5 * cleanup * fix: repair tests after rebase onto new form-binding API * chore(deps): update dependency go to v1.26.7 (go-gitea#38987) * fix: resolve actions commit status permission per repository (go-gitea#38977) Various pages did not display the correct action run list tooltips. Fix those tooltips like here on the `/pulls` page: `ctx.Repo.Permission` is the zero value outside a repository route, so on `/pulls`, `/issues`, `/notifications/subscriptions` and the dashboard repo list the commit status "Details" link was always stripped. The live job status is looked up from that target URL, so running checks also rendered as a static pending dot instead of a spinner. Resolve the Actions unit permission per repository instead. Also drops the releases page's gate on *loading* statuses, which hid external CI results from anyone without Actions read; it now loads them and hides only the URL, like every other page. Co-authored-by: bircni <bircni@icloud.com> * fix(lfs): ensure lock listing paginates with a total order (go-gitea#38850) `GetLFSLockByRepoID` applies `LIMIT`/`OFFSET` to a query with no `ORDER BY`. The order of such a query is unspecified (according to the SQL standard), so the resulting queryset might be inconsistent. These locks AFAIK are never updated, so in practice the order is insertion-based, but that's not guaranteed. * fix: use checked type assertions in handleAdminCreateUserError * [skip ci] Updated translations via Crowdin * fix: resolve YAML anchors and aliases in Actions workflows (go-gitea#38984) Workflows using YAML anchors are rejected as invalid, because a workflow is split into one document per job and an alias whose anchor lands in another job's document no longer resolves. Aliases are now expanded once, right after the workflow is parsed and before anything reads or splits it, bounded like GitHub's parser so nested aliases cannot expand without limit. Merge keys stay unsupported, as they are upstream. Fixes go-gitea#38983 Signed-off-by: silverwind <me@silverwind.io> * enhance(ui): forced colors mode enhancements (go-gitea#38991) Improve various UI elements while in [forced color mode](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/forced-colors). * fix(actions): Fix how jobs in matrixes are grouped (go-gitea#38980) The workflow graph decided which job rows belonged to the same matrix by parsing display names: it stripped a trailing `" (...)"` off `name` and grouped rows sharing the prefix. That guesses at a string the user controls, and it fails both ways. `jobparser` only appends the ` (<combination>)` suffix when `name:` contains no `${{ }}`, so a leg named `E2E on ${{ matrix.browser }}` never grouped, while two unrelated jobs `build (fast)` and `build (slow)` folded into one bogus matrix panel. Matrix legs already have a real identity: expansion clones one row per combination, all sharing the workflow's `JobID` and differing only in `Name`. Group on that instead, so a matrix is whatever the backend says it is. Matrix expansion state is keyed on the graph node id for the same reason. Closes go-gitea#38975, though that report's own example already groups on main, since `explicit (${{ matrix.leg }})` interpolates to a name that still ends in a suffix. The interpolated shapes above are the broken ones. Assisted-by: Claude Code:claude-opus-5 Co-authored-by: bircni <bircni@icloud.com> Co-authored-by: silverwind <me@silverwind.io> * enhance: use browser's locale to detect week's first day for the contribution map (go-gitea#38995) fix go-gitea#6058 --------- Signed-off-by: silverwind <me@silverwind.io> Co-authored-by: silverwind <me@silverwind.io> * chore: update eslint and stylelint configs and re-sync `modern-normalize` (go-gitea#38982) - update the vendored `modern-normalize` to v3.0.1 - require descriptions for lint disables in TS and CSS, same as we already have in Go. - disable core rules covered by `regexp/*` and `unicorn/*`, and ones that cannot fire - stop applying vitest rules to the playwright files in `tests/e2e` - enable 7 stylelint rules, mostly `no-unknown` and `no-invalid` checks - drop 2 unnecessary vendor prefixes (safari v17+, chrome v120+) - look up ids via `querySelector` with `CSS.escape` instead of `getElementById` - remove stale doc about `@ts-expect-error`, it's forbidden - misc dev doc fixes Every declaration that `modern-normalize` v3 removes was checked against chromium, webkit and firefox defaults first. The `hr` color and the `:-moz-focusring` outline are kept as documented deviations, dropping those does change rendering. --------- Signed-off-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> * refactor: deploy key and private route handlers (go-gitea#38999) clean up legacy code, fix various bugs: * add missing "return" * fix: avoid enumerating every public repository in issue search (go-gitea#38992) Both issue search endpoints resolve their repository filter with `SearchRepositoryIDs` and pass the result to the indexer as `RepoIDs`. They mean to leave public repositories to the indexer, but `SearchRepoOptions.AllPublic` is only read when `OwnerID > 0`, so without an `owner` filter the flag does nothing and every public repository is enumerated, without a `LIMIT`, into `repo_id IN (...)`. Those IDs are redundant, as `allPublic` is passed to the indexer, which already matches every public repository. On a large instance this binds tens of thousands of parameters and can fail in the driver, making the endpoint return 500 for every filter. Admins are worst hit, as `SearchRepositoryCondition` skips their accessible-repository condition and enumerates the whole table. Restrict the enumeration to private repositories. The result set is unchanged, as the dropped IDs are a subset of what `allPublic` matches. Both endpoints held copies of this block, so it moves to `routers/common`. --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> * fix(actions): show "Complete job" logs when the last step is skipped (go-gitea#38939) `FullSteps` only gave the synthetic "Complete job" step the remaining log range when the last step that had run was also the final step of the job. A skipped step does not count as having run, so any job ending in a skipped step left the post step with an empty range: its logs were stored but never rendered, and the duration showed as `0s`. Reproducible with any job whose last step is skipped, which is common for failure notifications: ```yaml steps: - run: echo hello - run: echo never if: failure() ``` The gate now checks whether the final step is done, which preserves the behaviour from go-gitea#29926 of showing the post step as waiting while steps are still pending. --> Regression from go-gitea#29926 --------- Signed-off-by: bircni <bircni@icloud.com> * fix(actions): allow larger scheduled workflows (go-gitea#38985) MySQL stores `action_schedule.content` as `BLOB`, limiting scheduled workflow definitions to 65,535 bytes. Oversized workflows fail schedule refresh and can also suppress default-branch push handling. Store scheduled workflow content as `LONGBLOB`, migrate existing MySQL columns, and cover the migration by persisting 65,536 bytes. Fixes go-gitea#38613 * [skip ci] Updated translations via Crowdin * fix(repo): hide repositories of hidden owners (go-gitea#39009) Exclude public repositories owned by hidden individual accounts from broad repository listings, while preserving visibility through explicit access and ownership. _Assisted-by: Codex:GPT-5_ * fix(api): enforce public-only scope for compare heads (go-gitea#39006) Enforce public-only token scope for repositories resolved as compare heads. _Assisted-by: Codex:GPT-5_ * fix(api): enforce repository creation token authorization (go-gitea#39007) Reject public-only tokens for repository migrations and require repository scope for canonical organization repository creation. This aligns both routes with the existing token authorization boundaries. _Assisted-by: Codex:GPT-5_ * fix(git): restrict hook permissions (go-gitea#39008) Create delegate hook files and directories without group or other write access, including correcting existing hook directories. _Assisted-by: Codex:GPT-5_ * fix(actions): enforce fork pull request trust boundaries (go-gitea#39005) Preserve fork pull request restrictions across review-triggered workflows, reusable workflow access, job scheduling, and filtered workflow statuses. This prevents untrusted fork workflow content from bypassing approval, accessing private reusable workflows, or satisfying protected status checks. _Assisted-by: Codex:GPT-5_ * fix(packages): bound Alpine metadata entries (go-gitea#39026) * fix(packages): limit Maven checksum uploads (go-gitea#39028) Bound checksum uploads to the maximum usable digest length before buffering their content. * fix(packages): limit Swift package manifests (go-gitea#39025) Bound the number and aggregate size of Swift manifests retained from an uploaded archive. * fix(migrations): bound OneDev version responses (go-gitea#39024) Limit OneDev version responses before parsing so a remote server cannot make a migration retain an unbounded response. * fix: make local queue PopItem can be notified (go-gitea#39011) * fix(migrations): cancel GitLab version probes (go-gitea#39023) Bind the GitLab version probe to the migration context so a cancelled migration does not remain blocked on a remote response. * enhance: add permalinks to pull request reviews (go-gitea#38849) 1. Make review threads linkable via `#pullrequestreview-<reviewID>` 2. Improve CSS so username and timestamp go colored on hover. 3. CSS cleanup, remove dead rules, nonexistant class name, make `.suppressed` actually do what it says in the doc above. * feat(api): list all packages for site administrators (go-gitea#38968) Add `GET /admin/packages` so site administrators can review packages across every owner without querying each owner separately. It returns the same package version representation as `GET /packages/{owner}` and supports `page`, `limit`, `type`, and `q` filters. --- Assisted by Codet(DeepSeek) --------- Signed-off-by: bircni <bircni@icloud.com> Signed-off-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: bircni <bircni@icloud.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> * fix(api): hide limited users from restricted viewers (go-gitea#39004) Use the canonical profile-visibility check for user API content and prevent restricted users from enumerating public repositories owned by limited users. This keeps feeds, heatmaps, keys, and issue search consistent with profile visibility. --------- Co-authored-by: silverwind <me@silverwind.io> * fix(release): separate publication time from the release date (go-gitea#36761) `published_at` was an alias for `created_at`, so a release created from an existing tag reported that tag's commit date as its publication time, and drafts reported one despite never having been published. It is now stored separately, set when a release is published and null for drafts. `created_at` in turn means the date of the commit the release points at, matching what GitHub documents it to be, and the latest release is selected by it again. Publishing a release for an old commit no longer takes over the latest badge, and a tag created in the web UI is dated the same way as one pushed from the CLI. Fixes go-gitea#11206 Fixes go-gitea#38714 Fixes go-gitea#31789 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix(base): correct natural sort of numbers with leading zeros (go-gitea#38163) ### Description `NaturalSortCompare` (`modules/base/natural_sort.go`) compares two numeric run parts by **raw string length**: ```go if len(part1) != len(part2) { return len(part1) - len(part2) } ``` "Longer digit string = larger number" only holds without leading zeros. With zero-padded numbers the comparison inverts: - `file0001` vs `file2` → claims `file0001 > file2`, but `1 < 2` - `a08` vs `a9` → claims `a08 > a9`, but `8 < 9` This affects any natural-ordered listing where zero-padded and shorter unpadded numbers mix (branch/tag/file names, etc.). ### Fix Strip leading zeros before comparing digit-count magnitude; on equal magnitude fall back to collation, then to the original length so fewer leading zeros sort first. Added a small `naturalSortTrimZeros` helper (keeps one char so `"000"` → `"0"`). Signed-off-by: Seonghyun Hong <s3onghyun.hong@gmail.com> Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> * fix(repo): limit gitignore template selections (go-gitea#39027) Bound gitignore template selections at both web and API request boundaries before repository initialization. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> * build(release): use native golang toolchain for official release builds (go-gitea#37828) Official releases are built by Golang toolchain with CGO disabled. For packagers who need to cross-compile with CGO, use "build" target with proper TAGS/LDFLAGS/CGO_CFLAGS to make "$(EXECUTABLE)" target run the "go build" command. By the way, drop i386 arch support --------- Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> * test: speed up tests, fix transaction bug (go-gitea#39030) Speed up tests: `make test-backend` 103s to 37s, `make test-integration` 908s to 852s. Most of it is a detached system notice insert blocking on the SQLite write lock until the busy timeout expired, and `ExternalServiceHTTP` re-probing on every call with an untimed `http.Get`. - fixed one correctness bug with nested transactions: files were deleted while the outer transaction was open, so a later failure could roll the database back with the files gone - git push branch counts were far above the hook batch size - Fix makefile dependencies so running tests and lint work in fresh worktrees. --------- Co-authored-by: Giteabot <teabot@gitea.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> * fix: drop queued job updates for deleted runs instead of requeueing forever (go-gitea#39037) When a repository is deleted while one of its Actions runs still has a pending job update in the emitter queue, `checkJobsByRunID` returns an error because the run no longer exists. The queue handler in `jobEmitterQueueHandler` treats every error as unhandled and requeues the item, creating an infinite retry loop that fills the log with error messages. ### Changes 1. **`services/actions/job_emitter.go`** — swap the `!exist`/`err` check order so a database error is reported first, then treat a non-existent run as handled (nil error). The queue consumer drops the item instead of requeueing it. 2. **`services/actions/job_emitter_test.go`** — add `Test_checkJobsByRunID_DeletedRunIsHandled`, which verifies that a deleted run produces nil (handled, not requeued). ### Related issue Fixes go-gitea#39034 --------- Co-authored-by: bircni <bircni@icloud.com> * fix(actions): verify raw artifact signatures first (go-gitea#39049) Validate raw-artifact signatures before resolving the requested artifact. --------- Co-authored-by: silverwind <me@silverwind.io> * fix(markup): enforce same-repository issue access (go-gitea#39045) Enforce Issues and Pull Requests access for references within the current repository. --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> * fix(repo): preserve transfer recipient collaboration (go-gitea#39042) Remove temporary recipient access after a transfer ends while preserving existing collaboration. --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> * feat(actions)!: add RUN_RETENTION_DAYS to delete old action runs (go-gitea#38855) Gitea keeps completed Actions runs forever. Artifacts and logs expire on their own schedule, but the run rows never go away, so `action_run` and its child tables grow without bound. Adds `RUN_RETENTION_DAYS` to delete completed runs along with their jobs, tasks and anything the earlier expiries left behind. It defaults to 400 days, matching how long GitHub keeps run history browsable. A dedicated `cleanup_action_runs` cron task performs the cleanup, so admins can schedule it separately from the nightly artifact and log sweep. `0` now means "keep forever" for all three retention settings, where `LOG_RETENTION_DAYS` and `ARTIFACT_RETENTION_DAYS` previously took it literally and deleted everything at the next sweep. Docs: https://gitea.com/gitea/docs/pulls/502 ---- ##⚠️ BREAKING⚠️ `RUN_RETENTION_DAYS` defaults to 400, so completed runs older than that are deleted when the cron task next runs at midnight. Set `RUN_RETENTION_DAYS = 0` before upgrading to keep all runs. --------- Co-authored-by: bircni <bircni@icloud.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: silverwind <me@silverwind.io> * [skip ci] Updated translations via Crowdin * review: address silverwind and copilot feedback on bot-account PR - Fix notification.go: UserID used loop index instead of users[i].ID (Copilot) - Add comment explaining login_type "0" = LoginNoType (silverwind) - Rename j- prefix to js- for consistency with project convention (silverwind) - Add specific case for ErrUserTypeCanNotConvert before generic ErrInvalidArgument (Copilot) - Add locale key for the new error message * fix(org): hide limited organizations from restricted users (go-gitea#39047) Do not expose limited organization memberships to restricted viewers. --------- Co-authored-by: silverwind <me@silverwind.io> * fix(repo): require organization owners for team access (go-gitea#39046) Require organization ownership before changing repository team associations when team access is restricted. --------- Co-authored-by: silverwind <me@silverwind.io> * fix(actions): enforce workflow badge token scope (go-gitea#39044) Apply repository token-scope and public-only checks to workflow badges. --------- Co-authored-by: silverwind <me@silverwind.io> * fix(api): enforce organization listing token scope (go-gitea#39041) Enforce organization token scope before listing organizations and retain public-only filtering. --------- Co-authored-by: silverwind <me@silverwind.io> * fix(packages): restrict limited owner package access (go-gitea#39043) Apply restricted-viewer visibility rules when resolving package access. --------- Co-authored-by: silverwind <me@silverwind.io> * fix(db): make paginated database reads always require "order" option (go-gitea#39017) Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> * fix(auth): record last sign-in on reverse proxy login (go-gitea#38672) Reverse proxy and SSPI logins establish a session but never recorded `last_login_unix`, so those users stayed "Never Signed-In" in admin. The write is folded into the language update that `handleSignIn` already does, so it stays at one query and only runs when a session is established. Fixes go-gitea#7836 --------- Co-authored-by: roman s <roman.sukach@dust-labs.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> * feat(diff): Add search and extension filter to diff sidebar (go-gitea#37068) Adds a search box and a file-extension filter to the pull request diff sidebar, so reviewers can narrow a large diff down to the files they care about. Both filters apply to the file tree and to the diff itself. The extension menu follows GitHub: extensions sorted alphabetically, dotfiles and extension-less files in their own buckets, and the selection kept in the same `file-filters[]` query parameter, so a filtered view is shareable and survives a reload. The menu can list every extension in a diff, so `createTippy` gains an opt-in `limitSizeToViewport` option that caps a popup to the space left in the viewport and scrolls its content. Popups that do not ask for it are unchanged. Closes go-gitea#27256 Signed-off-by: silverwind <me@silverwind.io> Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com> Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Nicolas <bircni@icloud.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> * refactor: prefix admin user form classes with js- for grepability Rename .non-bot, .local, .non-local → .js-non-bot, .js-local, .js-non-local across common.ts, new.tmpl, and edit.tmpl per silverwind's request for consistency with project convention. --------- Signed-off-by: silverwind <me@silverwind.io> Signed-off-by: bircni <bircni@icloud.com> Signed-off-by: Lunny Xiao <xiaolunwen@gmail.com> Signed-off-by: Seonghyun Hong <s3onghyun.hong@gmail.com> Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: GiteaBot <teabot@gitea.io> Co-authored-by: APZN <129354802+jsk1004ha@users.noreply.github.com> Co-authored-by: Nicolas <bircni@icloud.com> Co-authored-by: joestump-agent <joestump-agent@users.noreply.github.com> Co-authored-by: joestump-agent <agent@stump.wtf> Co-authored-by: Iaroslav <viralpraxis@evilmartians.com> Co-authored-by: bn-zr <174343671+bn-zr@users.noreply.github.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Julian Scholle <Julian.scholle@googlemail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: SEONGHYUN HONG <s3onghyun.hong@gmail.com> Co-authored-by: water <672684719@qq.com> Co-authored-by: Federico A. Corazza <20555025+facorazza@users.noreply.github.com> Co-authored-by: roman.s <55788842+majorissuerep@users.noreply.github.com> Co-authored-by: roman s <roman.sukach@dust-labs.com> Co-authored-by: McMichalK <michal.krela@monstercouch.com> Co-authored-by: Copilot <copilot@github.com>
This PR contains the following updates: | Package | Update | Change | |---|---|---| | [gitea/gitea](https://github.com/go-gitea/gitea) | patch | `1.27.2` → `1.27.3` | --- ### Release Notes <details> <summary>go-gitea/gitea (gitea/gitea)</summary> ### [`v1.27.3`](https://github.com/go-gitea/gitea/releases/tag/v1.27.3) [Compare Source](go-gitea/gitea@v1.27.2...v1.27.3) - SECURITY - fix(packages): restrict/limited/token-scope access ([#​39041](go-gitea/gitea#39041), [#​39043](go-gitea/gitea#39043), [#​39044](go-gitea/gitea#39044), [#​39047](go-gitea/gitea#39047), [#​39046](go-gitea/gitea#39046)) ([#​39058](go-gitea/gitea#39058)) - fix(attachments): enforce owning repository path ([#​39048](go-gitea/gitea#39048)) ([#​39077](go-gitea/gitea#39077)) - fix(markup): enforce same-repository issue access ([#​39045](go-gitea/gitea#39045)) ([#​39054](go-gitea/gitea#39054)) - fix(actions): verify raw artifact signatures first ([#​39049](go-gitea/gitea#39049)) ([#​39053](go-gitea/gitea#39053)) - fix(api): hide limited users from restricted viewers ([#​39004](go-gitea/gitea#39004)) ([#​39039](go-gitea/gitea#39039)) - fix(repo): limit gitignore template selections ([#​39027](go-gitea/gitea#39027)) ([#​39040](go-gitea/gitea#39040)) - fix(migrations): cancel GitLab version probes ([#​39023](go-gitea/gitea#39023)) ([#​39035](go-gitea/gitea#39035)) - fix(packages): limit Swift package manifests ([#​39025](go-gitea/gitea#39025)) ([#​39032](go-gitea/gitea#39032)) - fix(migrations): bound OneDev version responses ([#​39024](go-gitea/gitea#39024)) ([#​39033](go-gitea/gitea#39033)) - fix(packages): limit Maven checksum uploads ([#​39028](go-gitea/gitea#39028)) ([#​39031](go-gitea/gitea#39031)) - fix(packages): bound Alpine metadata entries ([#​39026](go-gitea/gitea#39026)) ([#​39029](go-gitea/gitea#39029)) - fix(actions): enforce fork pull request trust boundaries ([#​39005](go-gitea/gitea#39005)) ([#​39018](go-gitea/gitea#39018)) - fix(git): restrict hook permissions ([#​39008](go-gitea/gitea#39008)) ([#​39016](go-gitea/gitea#39016)) - fix(api): enforce repository creation token authorization ([#​39007](go-gitea/gitea#39007)) ([#​39014](go-gitea/gitea#39014)) - fix(api): enforce public-only scope for compare heads ([#​39006](go-gitea/gitea#39006)) ([#​39013](go-gitea/gitea#39013)) - fix(repo): hide repositories of hidden owners ([#​39009](go-gitea/gitea#39009)) ([#​39012](go-gitea/gitea#39012)) - fix: avoid enumerating every public repository in issue search ([#​38992](go-gitea/gitea#38992)) ([#​39000](go-gitea/gitea#39000)) - refactor: private endpoints ([#​38964](go-gitea/gitea#38964)) ([#​38965](go-gitea/gitea#38965)) - ENHANCEMENTS - enhance: add permalinks to pull request reviews ([#​38849](go-gitea/gitea#38849)) ([#​39036](go-gitea/gitea#39036)) - BUGFIXES - fix: add missing query parameters on runner list page ([#​39163](go-gitea/gitea#39163)) - fix(actions): keep step-level continue-on-error expressions unevaluated ([#​39141](go-gitea/gitea#39141)) ([#​39148](go-gitea/gitea#39148) - fix(packages): preserve SemVer prerelease identifiers in Swift Registry ([#​39156](go-gitea/gitea#39156)) ([#​39158](go-gitea/gitea#39158)) - fix(repo): prevent MarkAsBrokenEmpty when repository is being migrated ([#​39091](go-gitea/gitea#39091)) ([#​39092](go-gitea/gitea#39092)) - fix(asymkey): do not verify OpenPGP signatures with an SSH instance key ([#​39073](go-gitea/gitea#39073)) ([#​39086](go-gitea/gitea#39086)) - fix(pull): keep the merged state in sync with git ([#​39062](go-gitea/gitea#39062)) ([#​39118](go-gitea/gitea#39118)) - fix(pull): name the head repository in default compare links ([#​39075](go-gitea/gitea#39075)) ([#​39079](go-gitea/gitea#39079)) - fix(git): parse co-author trailers that are not RFC 5322 addresses ([#​39076](go-gitea/gitea#39076)) ([#​39081](go-gitea/gitea#39081)) - fix(actions): show "Complete job" logs when the last step is skipped ([#​38939](go-gitea/gitea#38939)) ([#​39003](go-gitea/gitea#39003)) - fix(actions): Fix how jobs in matrixes are grouped ([#​38980](go-gitea/gitea#38980)) ([#​38998](go-gitea/gitea#38998)) - fix: resolve YAML anchors and aliases in Actions workflows ([#​38984](go-gitea/gitea#38984)) ([#​38996](go-gitea/gitea#38996)) - fix: honor environment variables during install ([#​38974](go-gitea/gitea#38974)) ([#​38976](go-gitea/gitea#38976)) - fix: grant limited-org unit read access to authenticated non-members ([#​38871](go-gitea/gitea#38871)) ([#​38963](go-gitea/gitea#38963)) - fix: allow anonymous theme switching when REQUIRE\_SIGNIN\_VIEW is set ([#​38956](go-gitea/gitea#38956)) ([#​38961](go-gitea/gitea#38961)) - fix(actions): drop wrapper span around the action status icon ([#​38957](go-gitea/gitea#38957)) ([#​38959](go-gitea/gitea#38959)) - fix(issues): sort scoped labels by exclusive order in dropdowns ([#​38893](go-gitea/gitea#38893)) ([#​38954](go-gitea/gitea#38954)) - fix(indexer): correct bleve indexer token filters ([#​38853](go-gitea/gitea#38853)) ([#​38951](go-gitea/gitea#38951)) - fix: make "login\_name" field optional for API edit user ([#​38917](go-gitea/gitea#38917)) ([#​38945](go-gitea/gitea#38945)) - fix(actions): reject non-mapping matrix include/exclude ([#​38933](go-gitea/gitea#38933)) - fix(ui): respect FEED\_PAGING\_NUM on the dashboard feed ([#​38935](go-gitea/gitea#38935)) ([#​38936](go-gitea/gitea#38936)) - MISC - chore: repo compare link ([#​39088](go-gitea/gitea#39088)) ([#​39119](go-gitea/gitea#39119)) - ci: remove AWS S3 uploads from release workflows ([#​38928](go-gitea/gitea#38928)) ([#​38929](go-gitea/gitea#38929)) - chore: Pre-register a builtin OAuth2 application for the official Gitea mobile app ([#​38880](go-gitea/gitea#38880)) ([#​38922](go-gitea/gitea#38922)) Instances on **[Gitea Cloud](https://cloud.gitea.com)** will be automatically upgraded to this version during the specified maintenance window. </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **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://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC40NS4yIiwidXBkYXRlZEluVmVyIjoiNDQuNDMuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsicGF0Y2giLCJyZW5vdmF0ZSJdfQ==--> Reviewed-on: https://gitea.vcasaserver.com/omar/swarm/pulls/786 Co-authored-by: Renovate Bot <renovate-bot@vcasaserver.com>
Both issue search endpoints resolve their repository filter with
SearchRepositoryIDsand pass the result to the indexer asRepoIDs. They meanto leave public repositories to the indexer, but
SearchRepoOptions.AllPublicisonly read when
OwnerID > 0, so without anownerfilter the flag does nothingand every public repository is enumerated, without a
LIMIT, intorepo_id IN (...).Those IDs are redundant, as
allPublicis passed to the indexer, which alreadymatches every public repository. On a large instance this binds tens of thousands
of parameters and can fail in the driver, making the endpoint return 500 for every
filter. Admins are worst hit, as
SearchRepositoryConditionskips theiraccessible-repository condition and enumerates the whole table.
Restrict the enumeration to private repositories. The result set is unchanged, as
the dropped IDs are a subset of what
allPublicmatches.Both endpoints held copies of this block, so it moves to
routers/common.