From 67ff74439014c42dddba64d3e4efb3d308fd98a5 Mon Sep 17 00:00:00 2001 From: Julien Danjou Date: Wed, 22 Apr 2026 20:17:45 +0200 Subject: [PATCH] docs(merge-queue): document stack support Add a dedicated `/merge-queue/stacks` page describing how the Merge Queue handles stacks created by `mergify stack push`. The integration was previously undocumented despite covering several engine features: - Stack detection (head/base chain plus `Depends-On:` markers) - Auto-propagation of `@mergifyio queue` from the top PR to every predecessor - Stack-aware base resolution (every stacked PR is queued against the stack root, not its parent branch) - Stack-aware batching guarantees: same scope group, bottom-up order - Cascade dequeue (`StackPredecessorDequeued`) when a predecessor fails - Limits: max stack depth of 20, drafts block propagation Also adds a "Stacked PRs" sidebar entry under Merge Queue, and cross-links the new page from the places users already look for stack and merge-queue information: - Merge Queue index: rows for "Stacked PRs" in both the problem-to-feature and key-features tables - /stacks/team: replace the generic blurb with single-comment stack queueing - /stacks/reviewing: rewrite the merge tip with the actual @mergifyio queue workflow - /stacks/concepts: explain that the Depends-On marker is also the signal the queue uses for stack detection Change-Id: I1fc66332f5f474a5b6a0cbd89362c9c8c20dbec0 --- src/content/docs/merge-queue.mdx | 2 + src/content/docs/merge-queue/stacks.mdx | 193 ++++++++++++++++++++++++ src/content/docs/stacks/concepts.mdx | 5 + src/content/docs/stacks/reviewing.mdx | 6 +- src/content/docs/stacks/team.mdx | 10 +- src/content/navItems.tsx | 1 + 6 files changed, 209 insertions(+), 8 deletions(-) create mode 100644 src/content/docs/merge-queue/stacks.mdx diff --git a/src/content/docs/merge-queue.mdx b/src/content/docs/merge-queue.mdx index c0d66ef783..25ae136a80 100644 --- a/src/content/docs/merge-queue.mdx +++ b/src/content/docs/merge-queue.mdx @@ -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 @@ -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 diff --git a/src/content/docs/merge-queue/stacks.mdx b/src/content/docs/merge-queue/stacks.mdx new file mode 100644 index 0000000000..a5a2a18029 --- /dev/null +++ b/src/content/docs/merge-queue/stacks.mdx @@ -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. + + + +## 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. diff --git a/src/content/docs/stacks/concepts.mdx b/src/content/docs/stacks/concepts.mdx index 8658273141..b147be07d4 100644 --- a/src/content/docs/stacks/concepts.mdx +++ b/src/content/docs/stacks/concepts.mdx @@ -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 diff --git a/src/content/docs/stacks/reviewing.mdx b/src/content/docs/stacks/reviewing.mdx index 331fc88ffd..7b28c7aec1 100644 --- a/src/content/docs/stacks/reviewing.mdx +++ b/src/content/docs/stacks/reviewing.mdx @@ -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 diff --git a/src/content/docs/stacks/team.mdx b/src/content/docs/stacks/team.mdx index c6505db38d..a85b64abdc 100644 --- a/src/content/docs/stacks/team.mdx +++ b/src/content/docs/stacks/team.mdx @@ -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 diff --git a/src/content/navItems.tsx b/src/content/navItems.tsx index cc851fa172..bfeac09ed9 100644 --- a/src/content/navItems.tsx +++ b/src/content/navItems.tsx @@ -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',