Skip to content
Merged
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: 2 additions & 0 deletions src/content/docs/merge-queue.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ Jump straight to the feature that matches your pain point:
| Hotfixes blocked behind feature PRs | [Priority rules](/merge-queue/priority) |
| Monorepo with independent packages | [Scopes](/merge-queue/scopes) and [Monorepo guide](/merge-queue/monorepo) |
| Want to auto-queue approved PRs | [Auto-Merge](/merge-protections/auto-merge) and [Queue rules](/merge-queue/rules) |
| Landing stacked PRs in order | [Stacked PRs](/merge-queue/stacks) |

## What Mergify Solves

Expand Down Expand Up @@ -181,6 +182,7 @@ which services a PR affects and only batches compatible changes together.
| [Priority](/merge-queue/priority) | Let urgent PRs jump the queue | Ship hotfixes in minutes |
| [Scopes](/merge-queue/scopes) | Separate queues for monorepo paths | No cross-service blocking |
| [Pause](/merge-queue/pause) | Freeze merges during incidents | Instant incident response |
| [Stacked PRs](/merge-queue/stacks) | Queue a whole stack from one comment | Stacks land bottom-up automatically |

## Mergify vs GitHub Native Merge Queue

Expand Down
193 changes: 193 additions & 0 deletions src/content/docs/merge-queue/stacks.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
---
title: Stacked Pull Requests
description: How the merge queue handles stacks created by mergify stack push — auto-propagation, stack-aware batching, and cascade dequeue.
---

import GitGraph from '~/components/GitGraph.astro';

The Merge Queue understands [Stacks](/stacks) natively. When you queue a stacked
pull request, the queue treats the whole chain as a unit: it propagates the
queue command up the stack, keeps stacked PRs together when batching, and
cascades failures so the rest of the stack stops cleanly when something
breaks.

## How a Stack Is Detected

The queue recognizes a stack only when **both** signals hold at every step:

- The PRs are physically chained: each PR's base branch is the previous PR's
head branch.

- Each PR carries a `Depends-On: #N` marker in its body, declaring its
dependency on the previous PR.

This is exactly what [`mergify stack push`](/stacks/creating) produces. PRs
chained only by branch refs (for example, GitFlow promotion chains like
`dev` → `staging` → `prod`) are **not** treated as a stack. Without the
`Depends-On:` marker, the queue keeps each PR's literal base ref and queues
them independently.

<GitGraph
commits={["A", "B", "C"]}
commitColor="green"
prs={[
{ label: "PR #1", commits: 0, annotation: "base: main" },
{ label: "PR #2", commits: 1, annotation: "base: PR #1" },
{ label: "PR #3", commits: 2, annotation: "base: PR #2" },
]}
/>

## Queueing a Whole Stack at Once

Run [`@mergifyio queue`](/commands/queue) on the **top** PR of a stack and the
queue command propagates synthetically to every predecessor. The whole stack
enters the queue from a single comment. You don't need to comment on each
PR.

For a stack `PR1 → PR2 → PR3`, commenting `@mergifyio queue` on PR3 enqueues
PR1, PR2, and PR3 in the right order. While PR3 waits for its predecessors to
join the queue, its checks display the condition
`stack-predecessor-queued` as pending. That's the queue holding PR3 back
until PR1 and PR2 are queued ahead of it.

:::tip
This works the same with [Auto-Merge](/merge-protections/auto-merge):
approve the top PR with Auto-Merge enabled and the entire stack flows into
the queue as soon as `queue_conditions` are met.
:::

## Stack-Aware Base

Every stacked PR is queued against the **stack root** (e.g. `main`), not its
immediate parent branch. Without this, PR2 would be queued against PR1's head
branch and could never reach `main`, so the queue would have nothing to merge
into.

```dot class="graph" style="max-width: 320px; height: auto; display: block; margin: 1.5em auto"
strict digraph {
fontname="sans-serif";
fontsize=10;
rankdir="LR";
nodesep=0.2;
ranksep=0.4;

node [style=filled, fontname="sans-serif", fontcolor="white", fontsize=10, shape=circle, width=0.45, height=0.45, fixedsize=true];
edge [fontname="sans-serif", fontsize=9, color="#5B21B6", fontcolor="#5B21B6"];

PR1 [fillcolor="#347D39"];
PR2 [fillcolor="#347D39"];
PR3 [fillcolor="#347D39"];
main [label="main", shape=rectangle, fillcolor="#111827", width=0.7, height=0.4, fixedsize=false];

PR1 -> main;
PR2 -> main;
PR3 -> main;
}
```

You don't configure this. It's automatic for any PR detected as part of a
stack.

## Stack-Aware Batching

The queue treats a stack as an ordered chain when assembling
[batches](/merge-queue/batches). Two guarantees hold:

- **Same scope group.** With [scopes](/merge-queue/scopes) enabled, stacked
PRs are consolidated into the scope group of the bottom PR, even if their
individual scopes differ. The stack always travels through the same CI lane
rather than getting split across unrelated lanes.

- **Bottom-up order.** Within that group, predecessors always queue ahead of
successors. PR3 is never validated before PR1 and PR2.

In sequential batching, the queue actively packs a stack into the same batch
when its predecessors still fit in the remaining capacity. In parallel
checks, a stack longer than `batch_size` (or a stack sharing its scope group
with higher-priority unrelated PRs) lands across consecutive batches. Order
is preserved either way.

```dot class="graph"
strict digraph {
fontname="sans-serif";
rankdir="LR";
label="Merge Queue with batch_size: 5"

node [style=filled, shape=circle, fontcolor="white", fontname="sans-serif"];
edge [color="#374151", arrowhead=normal, fontname="sans-serif"];

subgraph cluster_batch_0 {
style="rounded,filled";
color="#1CB893";
fillcolor="#1CB893";
fontcolor="#000000";
node [fillcolor="#347D39"];
PR1 -> PR2 -> PR3 -> PR_other;
PR_other [label="PR4"];
label = "Batch 1 (stack PR1→PR2→PR3 kept together)";
}

PR_other -> PR5;
PR5 [label="…", fillcolor="#347D39"];
}
```

A PR only joins a batch if it and its still-waiting predecessors fit inside
the remaining capacity. When the stack is larger than `batch_size`, it lands
across consecutive batches bottom-first: the first batch validates the deepest
PRs that fit, and once they merge they drop out of the predecessor set, so the
next batch picks up where the previous one stopped.

## Cascade Dequeue

If any PR in a queued stack fails validation or is dequeued, every successor
still in the queue is dequeued automatically with the reason
`StackPredecessorDequeued`. This stops the queue from validating PRs whose
dependency just broke. There's no point checking PR3 if PR1 just failed.

```dot class="graph"
strict digraph {
fontname="sans-serif";
rankdir="LR";
label=""

node [style=filled, shape=circle, fontname="sans-serif"];

PR1 [fillcolor="#DC2626", fontcolor="white", label="PR1\n(failed)"];
PR2 [fillcolor="#9CA3AF", fontcolor="white", label="PR2\n(cascaded)"];
PR3 [fillcolor="#9CA3AF", fontcolor="white", label="PR3\n(cascaded)"];
PR4 [fillcolor="#347D39", fontcolor="white"];

PR1 -> PR2 [label="dequeue", color="#DC2626", fontcolor="#DC2626", fontsize="9"];
PR2 -> PR3 [label="dequeue", color="#DC2626", fontcolor="#DC2626", fontsize="9"];
PR3 -> PR4 [style="dashed", color="#9CA3AF"];
}
```

After fixing the broken PR, re-queue the stack from the top with
`@mergifyio queue`. Propagation re-enqueues the predecessors as needed.

:::note
Cascade dequeue only affects PRs that are still **queued**. PRs that already
merged successfully (lower in the stack) are untouched.
:::

## Limits

- **Maximum stack depth: 20.** Stacks deeper than 20 PRs aren't recognized as
a stack by the queue and fall back to per-PR queueing.

- **Drafts break the stack.** A draft predecessor blocks queue propagation:
the queue won't pull a stack through a PR still marked as draft. Mark the
PR ready for review first.

## Related

- [Stacks](/stacks): create and update stacks with `mergify stack push`.

- [`@mergifyio queue`](/commands/queue): the command that triggers stack
propagation.

- [Batches](/merge-queue/batches): batch-size and CI-cost trade-offs.

- [Scopes](/merge-queue/scopes): how stacks interact with monorepo scopes.
5 changes: 5 additions & 0 deletions src/content/docs/stacks/concepts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ The stack comment shows where each PR sits in the chain, with links to jump
between them. This gives both authors and reviewers a clear map of the entire
change.

The `Depends-On: #NNN` marker isn't only for humans. The
[Merge Queue](/merge-queue/stacks) also uses it to recognize a stack and queue
it as a unit. Without both the branch chain and the `Depends-On:` marker, the
queue treats each PR independently.

## Smart Updates

When you push again after making changes, Stacks doesn't recreate everything. It
Expand Down
6 changes: 3 additions & 3 deletions src/content/docs/stacks/reviewing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ merges first. Once it lands, the next PR's base automatically updates to `main`,
and you can review and merge that one. Each PR in turn, working up the chain.

:::tip
Stacks work well with [Merge Queue](/merge-queue). Each stacked PR can enter
the queue independently as its dependencies land, keeping your merge pipeline
flowing.
Comment `@mergifyio queue` on the top PR and the Merge Queue enqueues the
whole stack at once, bottom-up. See
[Stacked PRs in the Merge Queue](/merge-queue/stacks) for the full mechanics.
:::

You don't need to merge the entire stack at once. If the first two PRs are
Expand Down
10 changes: 5 additions & 5 deletions src/content/docs/stacks/team.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ hard-to-review PR:

## Pairing with Merge Queue

[Merge Queue](/merge-queue) complements Stacks well: Stacks break large changes
into reviewable pieces, and Merge Queue lands those pieces safely and
efficiently. Each stacked PR can enter the queue independently as its
dependencies land. The bottom PR merges first, then the next one enters the
queue, and so on, without manual coordination.
[Merge Queue](/merge-queue) understands stacks natively. A single
`@mergifyio queue` on the top PR enqueues the whole stack in order; the queue
keeps stacked PRs together when batching and cascades dequeues if any
predecessor fails. See [Stacked PRs in the Merge Queue](/merge-queue/stacks)
for the full mechanics.

## Comparing with Alternatives

Expand Down
1 change: 1 addition & 0 deletions src/content/navItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ const navItems: NavItem[] = [
icon: 'tabler:arrows-split-2',
},
{ title: 'Batches', path: '/merge-queue/batches', icon: 'tabler:packages' },
{ title: 'Stacked PRs', path: '/merge-queue/stacks', icon: 'bi:stack' },
{
title: 'Scopes',
path: '/merge-queue/scopes',
Expand Down
Loading