g1t/apps/docs/src/content/docs/guides/merge-queue.md
| 1 | --- |
| 2 | title: Merge queue |
| 3 | description: Test each pull request together with the ones ahead of it, so main only moves to a state whose required checks passed. |
| 4 | --- |
| 5 | |
| 6 | Merging one pull request at a time, each caught up with `main`, keeps every |
| 7 | merge clean as text. It does not prove the result works: two changes can |
| 8 | merge without a conflict and still break each other. With the merge queue |
| 9 | on, a pull request is tested together with everything ahead of it before it |
| 10 | lands, and `main` only ever moves to a state whose required checks passed. |
| 11 | |
| 12 | The merge queue runs in g1t's sandboxes, which need |
| 13 | [the g1t plan](/guides/usage-and-billing/#the-g1t-plan) or the trial after |
| 14 | a card check; a public repository can use the open-source pool instead. |
| 15 | Without one, an entry fails at once with a message saying so; turn the |
| 16 | queue off to merge directly. |
| 17 | |
| 18 | ## Turn it on |
| 19 | |
| 20 | 1. Open the project's **Settings → Branches and merging**. You need the Maintain |
| 21 | [role](/guides/access-and-roles/) or higher on its repository. |
| 22 | 2. Turn on **Merge through a queue**. |
| 23 | 3. Save. |
| 24 | 4. Add `merge_group` to the `on:` of every workflow behind a |
| 25 | [required status check](/guides/pull-requests/#required-status-checks), |
| 26 | so that it runs on the queue's states too |
| 27 | ([below](#what-each-state-is-held-to)). |
| 28 | |
| 29 | From the API, send `merge_queue` to `PATCH /repos/{owner}/{name}/settings` |
| 30 | (or `update_repo_settings`): |
| 31 | |
| 32 | ```sh |
| 33 | curl -X PATCH https://api.g1t.sh/repos/acme/web/settings \ |
| 34 | -H "Authorization: Bearer $G1T_TOKEN" \ |
| 35 | -H "Content-Type: application/json" \ |
| 36 | -d '{"merge_queue": true}' |
| 37 | ``` |
| 38 | |
| 39 | ## What merging does with the queue on |
| 40 | |
| 41 | Merging a pull request, from its page (**Add to the merge queue**), with |
| 42 | the `pull_request` tool's `merge` action, or with |
| 43 | `POST /repos/{owner}/{name}/pulls/{number}/merge`, |
| 44 | adds it to the queue instead of changing `main`. Everything a merge needs |
| 45 | is still checked first: the pull request must be ready for review, every |
| 46 | [required check](/guides/pull-requests/#required-status-checks) must have |
| 47 | passed on its head, and it must have the approvals the repository asks |
| 48 | for. Only people with the Write [role](/guides/access-and-roles/) or higher can add to the |
| 49 | queue. Merging a pull |
| 50 | request that is already queued changes nothing. |
| 51 | |
| 52 | The pull request's conversation records who added it, and its page shows |
| 53 | where it is in the queue. Anyone who can merge can take it out with **Remove from the |
| 54 | queue**. Closing a pull request also takes it out. |
| 55 | |
| 56 | ## How entries are tested |
| 57 | |
| 58 | g1t takes up to four entries from the front of the queue and tests them all |
| 59 | at once, speculatively, each in its own sandbox. Each sandbox builds `main` |
| 60 | with that entry and every entry ahead of it merged in, in queue order: |
| 61 | |
| 62 | | Entry | Tested as | |
| 63 | | --- | --- | |
| 64 | | 1st | `main` + #41 | |
| 65 | | 2nd | `main` + #41 + #44 | |
| 66 | | 3rd | `main` + #41 + #44 + #46 | |
| 67 | | 4th | `main` + #41 + #44 + #46 + #47 | |
| 68 | |
| 69 | If every entry passes, the four can land one after another without being |
| 70 | tested again. The next batch starts when nothing is being tested. A batch |
| 71 | that takes longer than 45 minutes is tested again. |
| 72 | |
| 73 | ### What each state is held to |
| 74 | |
| 75 | g1t pushes each state it built to a branch of its own, `g1t-queue/<entry>`, |
| 76 | and runs the repository's [workflows](/guides/actions/) that run on |
| 77 | `merge_group` on it. The entry waits for them, and passes only if: |
| 78 | |
| 79 | - every `merge_group` workflow it started passed; and |
| 80 | - every [required status check](/guides/pull-requests/#required-status-checks) |
| 81 | of the default branch passed on that commit. |
| 82 | |
| 83 | So a change that breaks something another change ahead of it relies on is |
| 84 | caught here, even when it merges without a conflict and its own checks |
| 85 | passed. The branch is deleted once the entry lands or leaves the queue. |
| 86 | |
| 87 | A workflow opts in like this: |
| 88 | |
| 89 | ```yaml |
| 90 | on: |
| 91 | pull_request: |
| 92 | merge_group: |
| 93 | ``` |
| 94 | |
| 95 | Required checks only report on a queued state if their workflows run on |
| 96 | `merge_group`. When the branch requires checks and no workflow runs on |
| 97 | `merge_group`, the entry fails with a message saying so: "the required |
| 98 | check CI cannot report on it: no workflow runs on merge_group events. Add |
| 99 | merge_group to the on: of the workflows the branch requires". A required |
| 100 | check that a workflow did not report on the state fails it the same way. |
| 101 | A repository that requires no checks and has no `merge_group` workflows |
| 102 | only has each state built: an entry passes once it merges cleanly with |
| 103 | what is ahead of it. |
| 104 | |
| 105 | ## How entries land |
| 106 | |
| 107 | Entries land in order. When an entry has passed and everything ahead of it |
| 108 | has landed, `main` moves to exactly the state that was tested. The issue |
| 109 | closes and the other pull requests for it are superseded, as with any |
| 110 | merge. |
| 111 | |
| 112 | Before landing, g1t checks that nothing has changed underneath: |
| 113 | |
| 114 | - If the pull request was pushed to after it was tested, it and the entries |
| 115 | tested on top of it are tested again. |
| 116 | - If `main` moved outside the queue, every entry is tested again on the new |
| 117 | `main`. |
| 118 | |
| 119 | A pull request that is already known to conflict with `main` is not added |
| 120 | to the queue: [its merge box](/guides/pull-requests/#conflicts) says which |
| 121 | files conflict and how to resolve them first. One that is only behind `main` |
| 122 | does not need to catch up to join the queue, since the queue tests it on |
| 123 | top of `main`. Where the repository requires pull requests to be up to |
| 124 | date, [catch it up](/guides/pull-requests/#catching-up) first: when it and |
| 125 | `main` changed different files that takes a few seconds and no agent. |
| 126 | |
| 127 | ## When an entry fails |
| 128 | |
| 129 | An entry fails when its `merge_group` workflows or required checks fail |
| 130 | on the combined state, when it does not merge cleanly with what is ahead of it, or |
| 131 | when the state cannot be built. |
| 132 | It leaves the queue, and: |
| 133 | |
| 134 | 1. Its pull request records the failure, saying why: which workflow failed |
| 135 | on the state, which required check did not report, or, for a conflict, |
| 136 | the pull request ahead it collided with and the files. |
| 137 | 2. Its conversation records that it was taken out of the queue, and why: a |
| 138 | conflict links the pull request it collided with and each conflicting |
| 139 | file, which opens in the pull request's changes. |
| 140 | 3. The entries that were tested on top of it are tested again without it. |
| 141 | |
| 142 | A g1t agent's pull request is then sent back to revise, as for any failed |
| 143 | check, starting from `main` as it is now. The revision counts towards |
| 144 | **Revisions before asking you**. Once it is ready again, a repository with |
| 145 | **Merge automatically when ready** on adds it to the queue again by itself; |
| 146 | otherwise it waits for someone to merge it again. A pull request you or |
| 147 | your own agent opened is yours to fix and merge again. |
| 148 | |
| 149 | ## The Merge queue page |
| 150 | |
| 151 | Every repository has a **Merge queue** page, at |
| 152 | `g1t.sh/<workspace>/<repo>/queue`, in the repository's sidebar. It |
| 153 | refreshes on its own while anything is queued. |
| 154 | |
| 155 | **In the queue** lists the entries in order, starting from `main`'s commit. |
| 156 | Each shows: |
| 157 | |
| 158 | | | | |
| 159 | | --- | --- | |
| 160 | | State | **Waiting**, **Testing** or **Passed**. | |
| 161 | | Tested as | `main` and the pull requests merged into it, such as `main + #41 + #44`. | |
| 162 | | Checks | How its state's checks stand. | |
| 163 | | Who | The agent or person who made the pull request, and who queued it. | |
| 164 | | Commit | The tested state's commit. | |
| 165 | |
| 166 | **Recently** lists the last 20 that left the queue: **Landed**, **Failed** |
| 167 | or **Removed**. A failed entry shows why. |
| 168 | |
| 169 | ## From the API or an agent |
| 170 | |
| 171 | The `pull_request` tool's `merge_queue` action, or |
| 172 | `GET /repos/{owner}/{name}/queue`, returns the queue. |
| 173 | It is public for a public repository. |
| 174 | |
| 175 | ```sh |
| 176 | curl https://api.g1t.sh/repos/acme/web/queue |
| 177 | ``` |
| 178 | |
| 179 | ```json |
| 180 | { |
| 181 | "enabled": true, |
| 182 | "active": [ |
| 183 | { |
| 184 | "number": 44, |
| 185 | "title": "Add a --shout flag", |
| 186 | "agent": "g1t-agent", |
| 187 | "state": "testing", |
| 188 | "ahead": [41], |
| 189 | "base_commit": "8f3c2e1…", |
| 190 | "combined_commit": null, |
| 191 | "results": [], |
| 192 | "enqueued_by": "g1t" |
| 193 | } |
| 194 | ], |
| 195 | "recent": [] |
| 196 | } |
| 197 | ``` |
| 198 | |
| 199 | | Field | | |
| 200 | | --- | --- | |
| 201 | | `enabled` | Whether the repository merges through the queue. | |
| 202 | | `active` | The entries waiting to land, in order. | |
| 203 | | `recent` | Those that landed or left, newest first. | |
| 204 | | `state` | `waiting`, `testing`, `passed`, `failed`, `landed` or `removed`. | |
| 205 | | `ahead` | The pull requests merged ahead of it in the state being tested. Empty when it was tested on `main` alone. | |
| 206 | | `base_commit` | The commit of `main` the state was built on. | |
| 207 | | `combined_commit` | The tested state. | |
| 208 | | `results` | What building the state recorded, each with `command`, `passed` and `output`. The workflow runs on it are on its commit, `combined_commit`. | |
| 209 | | `error` | Why it failed: a conflict, a workflow that failed on it, a required check that did not report, or what could not be built. | |
| 210 | | `enqueued_by` | Who added it: a username, or `g1t` when it was merged automatically. | |