Skip to content

Commit a1b4a3d

Browse files
skarimtalum
andauthored
Async merge API docs (#321)
* async merge api docs * address review comments * Apply batched suggestions from code review Co-authored-by: Tracy Lum <11878752+talum@users.noreply.github.com> Co-authored-by: Sameen Karim <skarim@github.com> --------- Co-authored-by: Tracy Lum <11878752+talum@users.noreply.github.com>
1 parent 68ce60c commit a1b4a3d

4 files changed

Lines changed: 163 additions & 2 deletions

File tree

‎docs/astro.config.mjs‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,10 @@ export default defineConfig({
7272
label: 'Reference',
7373
items: [
7474
{ label: 'CLI Commands', slug: 'reference/cli' },
75-
{ label: 'REST API', slug: 'reference/rest-api' },
76-
{ label: 'GraphQL API', slug: 'reference/graphql-api' },
7775
{ label: 'Webhooks', slug: 'reference/webhooks' },
76+
{ label: 'GraphQL API', slug: 'reference/graphql-api' },
77+
{ label: 'REST API', slug: 'reference/rest-api' },
78+
{ label: 'Merge API', slug: 'reference/merge-api' },
7879
],
7980
},
8081
{

‎docs/src/content/docs/faq.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,10 @@ Yes, Stacked PRs fully support merging via GitHub merge queue. When you merge a
273273
- **If a PR is ejected from the merge queue** (for example, because it fails), that PR and all its descendants are ejected too, while the PRs below it are unaffected.
274274
- **The queue makes a best-effort attempt to keep the stack together** in a single merge group. If the stack is too large to fit, it lands across consecutive merge groups: as much of the stack as fits goes into the current group, and the remaining PRs continue in subsequent groups until the full stack has landed. The stack order is preserved, so downstack PRs are merged before upstack PRs.
275275
276+
### How do I merge a stack programmatically?
277+
278+
Stacks are merged through GitHub's asynchronous [Merge API](/gh-stack/reference/merge-api/). The legacy synchronous merge APIs (REST and GraphQL) do not support stack merges. You submit a merge request for a PR and poll for the result; every PR in the stack up to and including the one you request is merged into the base branch. The [`gh stack merge`](/gh-stack/reference/cli/#gh-stack-merge) CLI command is built on this API and is the easiest way to merge from the command line.
279+
276280
## Local Development
277281
278282
### Do you have a CLI to help manage stacks?

‎docs/src/content/docs/reference/cli.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,8 @@ Only basic pull request state is checked before merging (open and not a draft);
478478

479479
If the base branch uses a merge queue, the stack is added to the queue instead of merging directly. The queue chooses the merge method, so the wizard skips the method step and any `--merge-method` (or `--squash`/`--rebase`/`--merge`) flag is ignored with a warning. The selected pull requests are added to the queue together but merge as the queue processes them — they may land in separate groups rather than all at once.
480480

481+
Under the hood, this command uses the asynchronous [Merge API](/gh-stack/reference/merge-api/).
482+
481483
| Flag | Description |
482484
|------|-------------|
483485
| `--merge-method <method>` | Merge method to use: `merge`, `squash`, or `rebase` |
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
---
2+
title: Merge API
3+
description: Reference for the asynchronous merge API — the required method for merging stacked pull requests.
4+
---
5+
6+
Stacked pull requests are merged through a new **asynchronous merge API**. Because a stack merge can involve several pull requests that may take up to a few minutes to merge, the merge runs in the background: you submit a merge request and then poll for its result.
7+
8+
This is the **required method for merging stacked PRs**. A stack cannot be merged with the legacy synchronous [merge endpoints](https://docs.github.com/rest/pulls/pulls#merge-a-pull-request) or [mutations](https://docs.github.com/en/graphql/reference/pulls#mutation-mergepullrequest). When you merge a stacked pull request, every pull request in the stack up to and including the one you request is merged or queued to merge into the base branch.
9+
10+
## How it works
11+
12+
Merging is a two-step flow:
13+
14+
1. **Submit** a merge request with `PUT .../merge-async`, then read the `status`. Only a `pending` response includes a `uuid` to poll. The submission may resolve immediately to `merged` (the pull request was already merged) or `failed` (the pull request is closed or a draft), both of which are terminal. It may also resolve to `enqueued` (the pull request was already added to the merge queue).
15+
2. **Poll** a `pending` request for its result with `GET .../merge-async/{uuid}` until the `status` is no longer `pending`.
16+
17+
Only basic pull request state is checked when you submit (the PR must be open and not a draft). Branch protection and repository rules are evaluated later, when the merge actually runs, and a rule failure is reported as a `failed` result while polling. A stack merge request is **atomic**: either the whole group of pull requests lands (or is added to the merge queue), or none of it does.
18+
19+
## Submit a merge request
20+
21+
```
22+
PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge-async
23+
```
24+
25+
Merges the pull request (and, for a stacked PR, everything below it in the stack) into the base branch in the background. Only a `pending` response returns a `uuid` to fetch the result. The submit can also resolve immediately — `merged` if the pull request was already merged, or `failed` if it cannot be merged (for example, it is closed or a draft). It may also resolve to `enqueued` (the pull request was already added to the merge queue).
26+
27+
All body fields are optional.
28+
29+
| Body field | Type | Description |
30+
|------------|------|-------------|
31+
| `merge_method` | `string` | The merge method: `merge`, `squash`, or `rebase`. Defaults to a merge commit. Not supported on `merge_queue` merge actions. |
32+
| `merge_action` | `string` | How to merge: `default` (recommended), `direct_merge`, or `merge_queue`. `default` picks the most appropriate option — it merges directly, or adds the stack to the base branch's merge queue when the branch requires one. `direct_merge` forces a direct merge; `merge_queue` uses the merge queue, if available. Omitting this field is equivalent to `default`. |
33+
| `commit_title` | `string` | Title for the automatic commit message. Not supported on `merge_queue` merge actions. |
34+
| `commit_message` | `string` | Extra detail to append to the automatic commit message. Not supported on `merge_queue` merge actions. |
35+
| `sha` | `string` | SHA that the pull request head must match to allow the merge. If the PR head does not match the provided SHA, the merge is rejected. |
36+
37+
```sh
38+
echo '{"merge_method": "squash", "merge_action": "default"}' | \
39+
gh api --method PUT repos/OWNER/REPO/pulls/102/merge-async --input -
40+
```
41+
42+
### Responses
43+
44+
| Status | When | Body `status` |
45+
|--------|------|---------------|
46+
| `202 Accepted` | The merge request was accepted and will run in the background. | `pending` |
47+
| `200 OK` | The pull request was already merged. | `merged` |
48+
| `409 Conflict` | A merge request already exists for this pull request. The existing request's `uuid` is returned — its options may differ from those you requested. | `pending` |
49+
| `400 Bad Request` | The pull request is not ready to be merged (for example, it is closed or a draft). | `failed` |
50+
| `404 Not Found` | Async merge is not available for this repository, or the pull request was not found. | — |
51+
| `422 Unprocessable Entity` | The request body failed validation (for example, an invalid `merge_method` or `merge_action` value). | — |
52+
53+
```json
54+
// 202 Accepted
55+
{
56+
"status": "pending",
57+
"details": {
58+
"message": "Merge request enqueued.",
59+
"uuid": "630b9d5e-3f2a-4f7e-8b0c-2d5f9a8c1e42",
60+
"merge_method": "squash",
61+
"merge_action": "default",
62+
"expected_head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e"
63+
}
64+
}
65+
```
66+
67+
## Get the result of a merge
68+
69+
```
70+
GET /repos/{owner}/{repo}/pulls/{pull_number}/merge-async/{uuid}
71+
```
72+
73+
Fetches the current result of a merge request, identified by the `uuid` returned when the merge was submitted. A valid lookup always returns `200 OK`. Read the `status` field to see where the merge stands. Poll this endpoint (e.g., once a second) until the `status` is no longer `pending`.
74+
75+
The result is retained for **24 hours** after its most recent update. After that window the request expires and this endpoint returns `404 Not Found` for the UUID.
76+
77+
```sh
78+
gh api repos/OWNER/REPO/pulls/102/merge-async/630b9d5e-3f2a-4f7e-8b0c-2d5f9a8c1e42
79+
```
80+
81+
```json
82+
// still running
83+
{
84+
"status": "pending",
85+
"details": {
86+
"message": "Merge request is in progress.",
87+
"uuid": "630b9d5e-3f2a-4f7e-8b0c-2d5f9a8c1e42",
88+
"merge_method": "squash",
89+
"merge_action": "default",
90+
"expected_head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e"
91+
}
92+
}
93+
94+
// merged directly
95+
{
96+
"status": "merged",
97+
"details": {
98+
"message": "Pull request was merged.",
99+
"sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e"
100+
}
101+
}
102+
103+
// added to the merge queue
104+
{
105+
"status": "enqueued",
106+
"details": {
107+
"message": "Pull request was added to the merge queue."
108+
}
109+
}
110+
111+
// could not be merged
112+
{
113+
"status": "failed",
114+
"details": {
115+
"message": "Merge conflict: the pull request could not be merged."
116+
}
117+
}
118+
```
119+
120+
## The result object
121+
122+
Both endpoints return the same object: a `status` enum and a `details` object.
123+
124+
| Field | Type | Description |
125+
|-------|------|-------------|
126+
| `status` | `string` | The state of the merge: `pending`, `merged`, `enqueued`, or `failed`. |
127+
| `details` | `object` | Details for the current state (see below). |
128+
129+
### Status values
130+
131+
| Status | Meaning |
132+
|--------|---------|
133+
| `pending` | The merge is running in the background. Keep polling. |
134+
| `merged` | The stack was merged directly. `details.sha` is the resulting merge commit. |
135+
| `enqueued` | The stack was added to the base branch's merge queue. It will merge once the queue processes it. This is a terminal state for the merge request — track the merge queue for the final outcome. |
136+
| `failed` | The merge was attempted but could not complete (for example, a merge conflict or an unmet branch rule). `details.message` explains why. Because the merge is atomic, nothing was merged. |
137+
138+
### Details fields
139+
140+
The fields present in `details` depend on the state:
141+
142+
| Field | Type | Present when | Description |
143+
|-------|------|--------------|-------------|
144+
| `message` | `string` | always | A human-readable description of the current state. |
145+
| `uuid` | `string` | `pending` | The identifier of the merge request, used to poll for the result. |
146+
| `merge_method` | `string` | `pending` | The merge method being used (`merge`, `squash`, or `rebase`). |
147+
| `merge_action` | `string` | `pending` | The requested merge action (`default`, `direct_merge`, or `merge_queue`). |
148+
| `expected_head_sha` | `string` | `pending` | The SHA the pull request head must match for the merge to proceed. |
149+
| `sha` | `string` | `merged` | The resulting merge commit SHA. |
150+
151+
## Limitations
152+
153+
- **Bypassing merge requirements is not supported.** You cannot use admin privileges to bypass a stack's branch protection rules or rulesets; every pull request in the stack must satisfy its requirements before the stack can land.
154+
- **Auto-merge is not supported.** A stacked pull request cannot be set to merge automatically once its requirements are met.

0 commit comments

Comments
 (0)