Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/modify.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ func runModifyAbort(cfg *config.Config) error {
cfg.Printf("The stack may be in an inconsistent state.")
cfg.Printf("Try `%s` to fix, or `%s` + `%s` to recreate.",
cfg.ColorCyan("gh stack rebase"), cfg.ColorCyan("gh stack unstack --local"),
cfg.ColorCyan("gh stack init --adopt"))
cfg.ColorCyan("gh stack init"))
return ErrSilent
}
cfg.Successf("Stack restored successfully")
Expand Down
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ locally, then push to GitHub to create your stack of PRs.`,
$ gh stack init

# Or turn an existing set of branches into a stack
$ gh stack init --adopt branch1 branch2 branch3
$ gh stack init branch1 branch2 branch3

# Make changes and commit, then add a branch to the stack
$ gh stack add branch4
Expand Down
4 changes: 3 additions & 1 deletion docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default defineConfig({
integrations: [
starlight({
title: 'GitHub Stacked PRs',
description: 'Break large changes into small, reviewable pull requests that build on each other — with native GitHub support and the gh stack CLI.',
description: 'Break large changes into small, reviewable pull requests. Manage your stacks on GitHub, with the gh stack CLI, or via our APIs.',
favicon: '/favicon.svg',
logo: {
src: './src/assets/github-invertocat.svg',
Expand Down Expand Up @@ -72,6 +72,8 @@ export default defineConfig({
label: 'Reference',
items: [
{ label: 'CLI Commands', slug: 'reference/cli' },
{ label: 'REST API', slug: 'reference/rest-api' },
{ label: 'GraphQL API', slug: 'reference/graphql-api' },
{ label: 'Webhooks', slug: 'reference/webhooks' },
],
},
Expand Down
12 changes: 0 additions & 12 deletions docs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified docs/src/assets/screenshots/add-to-existing-stack.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/src/assets/screenshots/modify-stack-tui.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/src/assets/screenshots/newly-created-stack.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/src/assets/screenshots/stack-merge-box.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/src/assets/screenshots/stack-navigator.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/src/assets/screenshots/stacked-prs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/src/assets/screenshots/unstack-entire-stack.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
77 changes: 62 additions & 15 deletions docs/src/content/docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ gh stack submit

You can also create stacks entirely from the GitHub UI — create the first PR normally, then when creating subsequent PRs, select the option to add them to a stack. See [Creating a Stack from the UI](/gh-stack/guides/ui/#creating-a-stack-from-the-ui) for a walkthrough.

If you already have open PRs whose branches line up, GitHub will detect and suggest turning them into a stack. See [Turning Existing PRs into a Stack](/gh-stack/guides/ui/#turning-existing-prs-into-a-stack).

### How do I add PRs to my stack?

Use `gh stack add <branch-name>` to add a new branch on top of the current stack. When you run `gh stack submit`, a PR is created for each branch, and they are linked together as a Stack on GitHub.

You can also add PRs to an existing stack from the GitHub UI. See [Adding to an Existing Stack](/gh-stack/guides/ui/#adding-to-an-existing-stack) for details.
You can also add PRs to an existing stack from the GitHub UI — either a brand-new PR or an already-open PR (via the recommendation banner), added to the top of the stack. See [Adding to an Existing Stack](/gh-stack/guides/ui/#adding-to-an-existing-stack) for details.

### How can I modify my stack?

Expand All @@ -52,7 +54,9 @@ gh stack init db-migrations api-routes frontend

**From the CLI** — Run `gh stack unstack` (or `gh stack delete`) to delete the stack on GitHub and remove local tracking. You can also unstack any stack by its number from anywhere in the repository — `gh stack unstack 7` — whether or not it's checked out locally. Use `--local` to only remove local tracking.

**From the UI** — You can unstack PRs from the GitHub UI — see [Unstacking](/gh-stack/guides/ui/#unstacking) for a walkthrough. This dissolves the association between PRs, turning them back into standard independent PRs.
**From the UI** — You can unstack PRs from the GitHub UI — see [Unstacking](/gh-stack/guides/ui/#unstacking) for a walkthrough. This dissolves the association between the PRs, turning them back into standard independent PRs.

Unstacking only removes **open, draft, and closed** PRs from the stack. **Merged and queued PRs remain part of the stack** — once a PR has merged (or is queued for merge) as part of a stack, it can't be unstacked. A stack is fully dissolved only when none of its PRs have merged or are queued for merge; otherwise it persists with those PRs still in it.

### Can stacks be created across forks?

Expand All @@ -75,7 +79,7 @@ GitHub Actions workflows trigger as if each PR in the stack is targeting the bas

### How do I access stack metadata in my GitHub Actions workflow?

For advanced use cases, you can access the stack's base ref and base SHA in workflow expressions via `github.event.pull_request.stack`. This property is only present when the PR belongs to a stack.
For advanced use cases, you can access the stack's metadata in workflow expressions via `github.event.pull_request.stack`. This property is only present when the PR belongs to a stack.

```yaml
jobs:
Expand All @@ -89,6 +93,7 @@ jobs:
run: |
echo "Stack base ref: ${{ github.event.pull_request.stack.base.ref }}"
echo "Stack base SHA: ${{ github.event.pull_request.stack.base.sha }}"
echo "PR ${{ github.event.pull_request.stack.position }} of ${{ github.event.pull_request.stack.size }} in the stack"

- name: Run a step only when the stack targets a release branch
if: github.event.pull_request.stack != null && startsWith(github.event.pull_request.stack.base.ref, 'release/')
Expand All @@ -97,10 +102,40 @@ jobs:

| Expression | Description |
|------------|-------------|
| `github.event.pull_request.stack.number` | The stack's number, scoped to the repository. |
| `github.event.pull_request.stack.size` | Total number of pull requests in the stack. |
| `github.event.pull_request.stack.position` | 1-based position of this PR within the stack (`1` is the bottom). |
| `github.event.pull_request.stack.base.ref` | The branch the entire stack ultimately targets (e.g., `main`). |
| `github.event.pull_request.stack.base.sha` | The HEAD SHA of that target branch at the time of the event. |
| `github.event.pull_request.stack.base.sha` | The HEAD SHA of the stack's base branch. |

See the [Webhooks reference](/gh-stack/reference/webhooks/) for the full details on the `stack` object in webhook payloads, or the [REST API reference](/gh-stack/reference/rest-api/) to read the same object on demand from a pull request.

### How can I optimize CI usage for a stack?

Because a workflow runs for every PR in a stack, a large stack can multiply your CI usage. You can use the `stack` fields to selectively run jobs based on the position of the current PR in the stack.

Two conditions are especially useful for deciding where a job should run:

- **Lowest unmerged PR** — the PR currently at the bottom of the remaining stack. Because it targets the stack base directly, `github.event.pull_request.stack.base.ref` equals `github.event.pull_request.base.ref`.
- **Top PR** — the last PR in the stack, containing the full set of changes. It's the PR where `github.event.pull_request.stack.position` equals `github.event.pull_request.stack.size`.

```yaml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Run for the lowest unmerged PR in the stack
if: github.event.pull_request.stack != null && github.event.pull_request.stack.base.ref == github.event.pull_request.base.ref
run: echo "Lowest unmerged PR in the stack"

- name: Run for the top PR in the stack
if: github.event.pull_request.stack != null && github.event.pull_request.stack.position == github.event.pull_request.stack.size
run: echo "Top PR in the stack"
```

See the [Webhooks reference](/gh-stack/reference/webhooks/) for the full details on the `stack` object in webhook payloads.
As PRs merge from the bottom up, the lowest unmerged PR changes: once the bottom PR lands, the next PR is rebased to target the stack base directly, so it becomes the new lowest unmerged PR on the following workflow run. You can also gate on the original bottom PR with `github.event.pull_request.stack.position == 1`, or on any specific layer using `position`.

### Do all previous PRs need to be passing checks before I can merge?

Expand All @@ -121,13 +156,21 @@ If the stack is not linear (e.g., after changes were pushed to a lower branch),

Every PR in a stack must meet the same merge requirements as a PR targeting the stack base (e.g., `main`): required reviews, passing CI checks, CODEOWNER approvals, and a linear history. All PRs below it must also meet these requirements. See the [Checks, Rules & Requirements](#checks-rules--requirements) section above for details.

### Can an admin bypass merge requirements on a Stacked PR?

Not yet — admin bypass is coming soon, but currently unavailable for stacked PRs. Admins can't merge a stack before its requirements are met, so every PR in the stack must satisfy its branch protection rules and required checks before the stack can land.

### Can I enable auto-merge on a Stacked PR?

Not yet — auto-merge is coming soon, but currently unavailable for stacked PRs, for both direct merges and the merge queue. You can't set a PR in a stack to land automatically once its requirements are met. Until then, merge the stack (or the part you want to land) yourself once its PRs are ready.

### How does merging a stack of PRs differ from merging a regular PR?

Stacks merge from the bottom up as a single atomic operation. When you click merge on a PR in a stack, that PR and all unmerged PRs below it land on the base branch together. PRs above remain open, and the remaining stack is automatically rebased so the next PR targets `main` directly.
Stacks merge from the bottom up. When you click merge on a PR in a stack, that PR and all unmerged PRs below it land on the base branch together; PRs above remain open, and the remaining stack is automatically rebased so the next PR targets your base branch directly. With a direct merge the group lands as a single atomic operation; through a merge queue the PRs enter the queue together and are evaluated individually, from the bottom up.

### What happens when you merge a PR in the middle of the stack?

When you click merge on a PR in the middle of the stack, that PR and all unmerged PRs below it land on the base branch together as a single atomic operation, ordered from the bottom up in the resulting history. PRs above the selected one remain open. After the merge, the lowest unmerged PR is updated to target the stack base directly, and a cascading rebase runs across the remaining branches.
When you click merge on a PR in the middle of the stack, that PR and all unmerged PRs below it land on the base branch together, ordered from the bottom up in the resulting history. PRs above the selected one remain open. After the merge, the lowest unmerged PR is updated to target the stack base directly, and a cascading rebase runs across the remaining branches.

It is not possible to merge a middle PR in isolation: the PRs below it always merge with it.

Expand Down Expand Up @@ -175,13 +218,14 @@ When you merge a stack using the merge commit strategy, it creates **one merge c

### How does rebase merge work?

With rebase merge, the commits from each PR in the stack are replayed onto the base branch, creating a linear history without merge commits. The full set of commits lands as a single atomic operation.
With rebase merge, the commits from each PR in the stack are replayed onto the base branch, creating a linear history without merge commits. The full set of commits lands on the base branch.

### Do all PRs get merged at once or one at a time?

All PRs in the stack land in a single atomic operation. When you click merge on a PR, that PR and all unmerged PRs below it are merged together onto the base branch at the same time, ordered from the bottom up in the resulting history. PRs above the selected one remain open.
It depends on the merge method:

This applies whether or not a merge queue is enabled. With a merge queue, the same atomic landing happens once the stack's merge group reaches the front of the queue.
- **Direct merge** — All the included PRs land in a single atomic operation. The selected PR and every unmerged PR below it are merged together onto the base branch at once, ordered from the bottom up. Either the whole group lands, or if any part fails, none of it does.
- **Merge queue** — The PRs enter the queue together and are evaluated individually, from the bottom up. If a PR fails while in the queue, it and all its descendants are ejected, while the PRs below it are unaffected.

### Can I merge only part of a stack? What happens to the remaining unmerged PRs?

Expand All @@ -197,15 +241,18 @@ Closing a PR in the middle of the stack will block all PRs above it from being m

### What happens when there is an error merging a PR in the middle of a stack?

Pre-merge checks run before any merge attempt, but a merge can still fail (e.g., due to an unexpected merge conflict or intermittent failure). If a failure occurs partway through, merging stops at that PR. PRs below it that successfully merged remain landed on the base branch; the failed PR and PRs above it stay open. Resolve the issue on the failed PR and retry to land the rest of the stack.
Pre-merge checks run before any merge attempt, but a merge can still fail (e.g., due to a merge conflict or intermittent failure). The behavior depends on the merge method:

- **Direct merge** — The merge is atomic. If any part fails, the entire operation is rolled back and nothing is merged.
- **Merge queue** — Because each PR is evaluated individually, a failure ejects that PR and all its descendants (the PRs stacked above it) from the queue, while the PRs below it are unaffected. You can fix the issue on the failed PR and requeue the ejected PRs.

### Do Stacked PRs support merge queue?

Yes, Stacked PRs fully support merging via merge queue. When you merge a stack through the merge queue:
Yes, Stacked PRs fully support merging via GitHub merge queue. When you merge a stack through the merge queue:

- **All PRs in the stack are added to the queue** in the correct order, ensuring a linear sequence.
- **If a PR is removed or ejected from the merge queue**, all PRs above it in the stack are also ejected and removed from the queue.
- **Stacks are kept in the same merge group on a best-effort basis.** To keep a stack together, the merge queue allows the merge group to exceed its configured max size by up to 50%. If the stack is too large to fit within that buffer, it splits 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.
- **All PRs in the stack enter the queue together** in the correct order and are evaluated individually, from the bottom up.
- **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.
- **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.

## Local Development

Expand Down
17 changes: 10 additions & 7 deletions docs/src/content/docs/guides/stacked-prs.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Practical guide for reviewing, merging, and managing stacked pull r

This guide covers the practical day-to-day experience of working with Stacked PRs — how to review them, how merging works step by step, and how to keep things in sync from the CLI.

For an introduction to what stacks are and how GitHub supports them natively, see the [Overview](/gh-stack/introduction/overview/). For a visual walkthrough of the UI, see [Stacked PRs in the GitHub UI](/gh-stack/guides/ui/).
For an introduction to what stacks are and how it works in GitHub, see the [Overview](/gh-stack/introduction/overview/). For a visual walkthrough of the UI, see [Stacked PRs in the GitHub UI](/gh-stack/guides/ui/).

## Reviewing Stacked PRs

Expand All @@ -21,14 +21,17 @@ Each PR in a stack shows only the diff for its layer — the changes between its
- **Review individual PRs** when you're focusing on a specific concern (e.g., reviewing only the API layer).
- **Use the stack map** to navigate between PRs without going back to the PR list.

## Merging from the Bottom Up
## Merging a Stack

Stacks are merged **from the bottom up** — you can merge any number of PRs at once, as long as they form a contiguous group starting from the lowest unmerged PR. For example, in a stack of four PRs, you can merge just the bottom one, or the bottom three together, but you cannot merge only the second and third PRs while leaving the first unmerged. Mid-stack merges are not allowed.
Merging is driven by a single action: **click Merge on the highest PR you want to land, and that PR plus every unmerged PR below it are merged together, from the bottom up.** You do not need to merge PRs one at a time, unless you choose to.

1. When the lowest unmerged PR (and any PRs above it that you want to include) meet all merge requirements, merge them.
2. After the merge, the remaining stack is **automatically rebased** — the next unmerged PR's base is updated to target `main` directly.
3. The next unmerged PR is now at the bottom and can be reviewed, approved, and merged.
4. Repeat until the entire stack is landed.
- **To land the whole stack**, merge the **top** PR — every PR below it lands with it in a single step.
- **To land part of the stack**, merge a lower PR — the PRs below it come along, and the PRs above stay open.
- **To land a single PR**, merge the **bottom** PR - only that PR will be merged, and the rest of the PRs stay open.

You can merge any contiguous group, as long as it starts from the lowest unmerged PR. In a stack of four PRs you can land just the bottom one, or the bottom three together, but you can't merge only the second and third while leaving the first unmerged — a PR always brings the unmerged PRs below it along. Merging a stacked PR always merges all the unmerged PRs below it as well.

When you land only part of a stack, the remaining PRs are **automatically rebased** and retargeted so the next unmerged PR targets your base branch directly and is immediately ready to review and merge.

Once the entire stack has landed, it is complete and can't be extended. If you add new branches on top and run `gh stack submit`, the CLI automatically starts a **new** stack rooted at the trunk for those branches (a new PR on a fully merged stack would target the trunk directly rather than chaining onto the merged PRs).

Expand Down
Loading
Loading