Compare commits

..
2 Commits
Author SHA1 Message Date
silverwind fb79898c28 fix(api): return language of starred and watched repositories (#40127) (#40157)
Backport https://github.com/go-gitea/gitea/pull/40127

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.
2026-11-01 09:05:28 -08:00
96b2997c06 ci: use GitHub merge queue (#40130) (#40162)
Backport #40130 by @bircni

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: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-11-01 10:40:02 +00:00
10 changed files with 43 additions and 20 deletions

No files matched your search

+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 30 timeout-minutes: 30
steps: steps:
- uses: go-gitea/giteabot@36ea99f821fefa3e8fb26aeac6211735664cd9c0 # v1.3.1 - uses: go-gitea/giteabot@b830f7aa810e43afc1b2f8e8968a30754e6ff5d3 # v1.4.1
with: with:
github_token: ${{ secrets.GITEABOT_TOKEN }} github_token: ${{ secrets.GITEABOT_TOKEN }}
gitea_fork: giteabot/gitea gitea_fork: giteabot/gitea
+6 -12
View File
@@ -1,16 +1,11 @@
name: giteabot name: giteabot
on: on:
# When main advances, rerun merge queue maintenance so the oldest
# reviewed/wait-merge PR can be updated against the new base promptly.
push:
branches:
- main
# pull_request_target gives this workflow access to GITEABOT_TOKEN on PRs from # pull_request_target gives this workflow access to GITEABOT_TOKEN on PRs from
# forks, which the bot needs to write labels, statuses and comments. Safe here # forks, which the bot needs to write labels, statuses and comments. Safe here
# because the job only runs a pinned action and never checks out PR HEAD. # because the job only runs a pinned action and never checks out PR HEAD.
# These PR lifecycle events drive label maintenance, queue maintenance, and # These PR lifecycle events drive label maintenance, lgtm, and explicit bot
# explicit bot actions triggered by relevant label changes. # actions triggered by relevant label changes.
pull_request_target: # zizmor: ignore[dangerous-triggers] pull_request_target: # zizmor: ignore[dangerous-triggers]
types: types:
- opened - opened
@@ -26,8 +21,7 @@ on:
- giteabot-review - giteabot-review
types: types:
- requested - requested
# Periodic maintenance is still useful as a backstop for queue cleanup and # Daily housekeeping like locks, stale feedback issues, last calls and milestones
# other housekeeping, even though main pushes now trigger it promptly.
schedule: schedule:
- cron: "15 3 * * *" - cron: "15 3 * * *"
# Allow maintainers to rerun selected checks manually when debugging bot # Allow maintainers to rerun selected checks manually when debugging bot
@@ -37,7 +31,7 @@ on:
checks: checks:
description: Comma-separated list of non-backport checks to run description: Comma-separated list of non-backport checks to run
required: false required: false
default: backport_branches,labels,merge_queue,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions default: backport_branches,labels,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions
permissions: {} permissions: {}
@@ -56,7 +50,7 @@ jobs:
pull-requests: write pull-requests: write
statuses: write statuses: write
steps: steps:
- uses: go-gitea/giteabot@36ea99f821fefa3e8fb26aeac6211735664cd9c0 # v1.3.1 - uses: go-gitea/giteabot@b830f7aa810e43afc1b2f8e8968a30754e6ff5d3 # v1.4.1
with: with:
github_token: ${{ secrets.GITEABOT_TOKEN }} github_token: ${{ secrets.GITEABOT_TOKEN }}
checks: ${{ github.event.inputs.checks || 'backport_branches,labels,merge_queue,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions' }} checks: ${{ github.event.inputs.checks || 'backport_branches,labels,lock,feedback,last_call,milestones,lgtm,translation_comment,pr_actions' }}
+16
View File
@@ -0,0 +1,16 @@
name: merge-group
# Reports the required giteabot/lgtm check on merge queue commits. giteabot only sets it
# on PR heads, which already passed it before entering the queue.
on:
merge_group:
permissions: {}
jobs:
lgtm:
name: giteabot/lgtm
runs-on: ubuntu-latest
steps:
- run: "true"
+1
View File
@@ -2,6 +2,7 @@ name: compliance
on: on:
pull_request: pull_request:
merge_group:
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
+1
View File
@@ -2,6 +2,7 @@ name: db-tests
on: on:
pull_request: pull_request:
merge_group:
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
+1
View File
@@ -2,6 +2,7 @@ name: e2e-tests
on: on:
pull_request: pull_request:
merge_group:
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
+5 -7
View File
@@ -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. 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.
<https://github.com/go-gitea/gitea/pulls?q=is%3Apr+label%3Areviewed%2Fwait-merge+sort%3Acreated-asc+is%3Aopen> 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:
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:
- 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. - 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 ### 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: 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.\ - 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.\ 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. 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.
+4
View File
@@ -32,6 +32,9 @@ func getStarredRepos(ctx *context.APIContext, user *user_model.User, private boo
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := repo_model.RepositoryList(starredRepos).LoadAttributes(ctx); err != nil {
return nil, err
}
repos := make([]*api.Repository, 0, len(starredRepos)) repos := make([]*api.Repository, 0, len(starredRepos))
for _, starred := range starredRepos { for _, starred := range starredRepos {
@@ -113,6 +116,7 @@ func GetMyStarredRepos(ctx *context.APIContext) {
repos, err := getStarredRepos(ctx, ctx.Doer, true) repos, err := getStarredRepos(ctx, ctx.Doer, true)
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return
} }
ctx.SetLinkHeader(int64(ctx.Doer.NumStars), utils.GetListOptions(ctx).PageSize) ctx.SetLinkHeader(int64(ctx.Doer.NumStars), utils.GetListOptions(ctx).PageSize)
+5
View File
@@ -30,6 +30,9 @@ func getWatchedRepos(ctx *context.APIContext, user *user_model.User, private boo
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
if err := repo_model.RepositoryList(watchedRepos).LoadAttributes(ctx); err != nil {
return nil, 0, err
}
repos := make([]*api.Repository, len(watchedRepos)) repos := make([]*api.Repository, len(watchedRepos))
for i, watched := range watchedRepos { for i, watched := range watchedRepos {
@@ -73,6 +76,7 @@ func GetWatchedRepos(ctx *context.APIContext) {
repos, total, err := getWatchedRepos(ctx, ctx.ContextUser, private) repos, total, err := getWatchedRepos(ctx, ctx.ContextUser, private)
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return
} }
ctx.SetLinkHeader(total, utils.GetListOptions(ctx).PageSize) ctx.SetLinkHeader(total, utils.GetListOptions(ctx).PageSize)
@@ -103,6 +107,7 @@ func GetMyWatchedRepos(ctx *context.APIContext) {
repos, total, err := getWatchedRepos(ctx, ctx.Doer, true) repos, total, err := getWatchedRepos(ctx, ctx.Doer, true)
if err != nil { if err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
return
} }
ctx.SetLinkHeader(total, utils.GetListOptions(ctx).PageSize) ctx.SetLinkHeader(total, utils.GetListOptions(ctx).PageSize)
ctx.SetTotalCountHeader(total) ctx.SetTotalCountHeader(total)
+3
View File
@@ -9,6 +9,7 @@ import (
"testing" "testing"
auth_model "gitea.dev/models/auth" auth_model "gitea.dev/models/auth"
"gitea.dev/models/db"
"gitea.dev/models/perm" "gitea.dev/models/perm"
repo_model "gitea.dev/models/repo" repo_model "gitea.dev/models/repo"
"gitea.dev/models/unittest" "gitea.dev/models/unittest"
@@ -206,6 +207,7 @@ func TestAPIStarDisabled(t *testing.T) {
func TestAPIStarPublicOnly(t *testing.T) { func TestAPIStarPublicOnly(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
require.NoError(t, db.Insert(t.Context(), &repo_model.LanguageStat{RepoID: 4, IsPrimary: true, Language: "Go", Size: 1}))
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly)
req := NewRequest(t, "GET", "/api/v1/user/starred"). req := NewRequest(t, "GET", "/api/v1/user/starred").
@@ -215,6 +217,7 @@ func TestAPIStarPublicOnly(t *testing.T) {
repos := DecodeJSON(t, resp, []api.Repository{}) repos := DecodeJSON(t, resp, []api.Repository{})
if assert.Len(t, repos, 1) { if assert.Len(t, repos, 1) {
assert.Equal(t, "user5/repo4", repos[0].FullName) assert.Equal(t, "user5/repo4", repos[0].FullName)
assert.Equal(t, "Go", repos[0].Language)
} }
req = NewRequest(t, "GET", "/api/v1/users/user2/starred"). req = NewRequest(t, "GET", "/api/v1/users/user2/starred").