From cce8f04ac9b309d823cf26ee3ecba588bce28ef0 Mon Sep 17 00:00:00 2001 From: Julien Danjou Date: Fri, 20 Mar 2026 16:49:34 +0100 Subject: [PATCH] docs: document merge strategies and fast-forward merge method Add a new Merge Strategies guide page covering all four merge methods (merge, squash, rebase, fast-forward) with git history diagrams, a comparison table, and a decision matrix. Update queue rules reference to document `merge_method: fast-forward` and its requirements (batch_size: 1, max_parallel_checks: 1, branch protection bypass). Clarify the batches page to distinguish `queue_branch_merge_method` (batch branch merging) from `merge_method` (individual PR merging). Closes Mergifyio/mergify#67 Co-Authored-By: Claude Opus 4.6 (1M context) Change-Id: I7c4763490a004d1dc95755162238d03915a9b781 Claude-Session-Id: f877eb17-51e5-4239-b143-80c699c0557c --- src/content/docs/merge-queue/batches.mdx | 23 +- .../docs/merge-queue/merge-strategies.mdx | 294 ++++++++++++++++++ src/content/docs/merge-queue/rules.mdx | 19 +- src/content/navItems.tsx | 5 + 4 files changed, 332 insertions(+), 9 deletions(-) create mode 100644 src/content/docs/merge-queue/merge-strategies.mdx diff --git a/src/content/docs/merge-queue/batches.mdx b/src/content/docs/merge-queue/batches.mdx index 1320343e72..75b48c06af 100644 --- a/src/content/docs/merge-queue/batches.mdx +++ b/src/content/docs/merge-queue/batches.mdx @@ -145,6 +145,15 @@ By setting `queue_branch_merge_method` to `fast-forward`, Mergify will merge the temporary branches instead of the original PRs. The default setting is `none`, which means the temporary branches are not merged. +:::note + `queue_branch_merge_method` controls how the **temporary batch branch** is + merged into the base branch. This is different from `merge_method`, which + controls how **individual PRs** are merged. To learn about using + `merge_method: fast-forward` for individual PR merges (preserving commit + SHAs without batching), see [Merge Strategies: + Fast-Forward](/merge-queue/merge-strategies#fast-forward). +::: + Here is an example configuration: ```yaml @@ -156,14 +165,12 @@ queue_rules: ``` :::note - - Please note that this setting only works if `merge_method` is set to `merge`. - This is necessary so GitHub can mark the original PR as "merged" once the - temporary branch is merged. This configuration makes sure that the original - SHA1 from the PRs are included in the temporary branch and thus are detected - as merged. Importantly, the final merged result is the exact SHA1 of what has - been tested by CI, ensuring consistency and traceability. - + `queue_branch_merge_method: fast-forward` only works if `merge_method` is set + to `merge`. This is necessary so GitHub can mark the original PR as "merged" + once the temporary branch is merged. This configuration makes sure that the + original SHA1 from the PRs are included in the temporary branch and thus are + detected as merged. Importantly, the final merged result is the exact SHA1 of + what has been tested by CI, ensuring consistency and traceability. ::: :::caution diff --git a/src/content/docs/merge-queue/merge-strategies.mdx b/src/content/docs/merge-queue/merge-strategies.mdx new file mode 100644 index 0000000000..155cc0541a --- /dev/null +++ b/src/content/docs/merge-queue/merge-strategies.mdx @@ -0,0 +1,294 @@ +--- +title: Merge Strategies +description: Choose how pull requests are merged into your base branch and control the shape of your git history. +--- + +import { Image } from "astro:assets" +import requiredPRbypassScreenshot from "../../images/merge-queue/batches/mergify-required-pull-request-bypass.png" + +The `merge_method` option in your queue rules controls how Mergify merges pull +requests into your base branch. Each method produces a different git history +shape, with trade-offs between linearity, SHA preservation, and throughput. + +## Merge Methods at a Glance + +| Method | History | Commits on base branch | SHAs preserved | Queue parallelism | +|---|---|---|---|---| +| `merge` | Non-linear | Original commits + merge commit | Yes | Full | +| `squash` | Linear | 1 new commit per PR | No | Full | +| `rebase` | Linear | Recreated copies of each commit | No | Full | +| `fast-forward` | Linear | Original commits moved to base | Yes | Serial only | + +## Merge (Default) + +```yaml +queue_rules: + - name: default + merge_method: merge +``` + +Creates a merge commit joining the PR branch into the base branch. This is the +default GitHub merge behavior. + +```dot class="graph" +strict digraph { + fontname="sans-serif"; + rankdir="LR"; + splines=line; + node [style=filled, shape=circle, fontname="sans-serif", fontcolor="white", width=0.5, fixedsize=true]; + edge [color="#374151", fontname="sans-serif"]; + + A [label="A", fillcolor="#6B7280"]; + B [label="B", fillcolor="#6B7280"]; + C [label="C", fillcolor="#347D39"]; + D [label="D", fillcolor="#347D39"]; + M [label="M", fillcolor="#2563EB"]; + + main [label="main", shape=plaintext, fontname="sans-serif", fontcolor="#2563EB", fontsize=10]; + + A -> B; + B -> C; + C -> D; + B -> M; + D -> M; + main -> M [style=dashed, color="#2563EB", arrowhead=none]; + + { rank=same; M; main; } +} +``` + +- **History:** non-linear — the PR branch and base branch are visible as + separate lines in `git log --graph` + +- **Merge commits:** yes — each PR produces a merge commit on the base branch + +- **SHAs preserved:** yes — original PR commits keep their SHAs + +- **Use case:** most teams; simplest setup with no constraints on parallelism + or batching + +## Squash + +```yaml +queue_rules: + - name: default + merge_method: squash +``` + +Squashes all PR commits into a single commit on the base branch. + +```dot class="graph" +strict digraph { + fontname="sans-serif"; + rankdir="LR"; + splines=line; + node [style=filled, shape=circle, fontname="sans-serif", fontcolor="white", width=0.5, fixedsize=true]; + edge [color="#374151", fontname="sans-serif"]; + + A [label="A", fillcolor="#6B7280"]; + B [label="B", fillcolor="#6B7280"]; + S [label="S", fillcolor="#D97706"]; + C [label="C", fillcolor="#347D39", style="filled,dashed"]; + D [label="D", fillcolor="#347D39", style="filled,dashed"]; + + main [label="main", shape=plaintext, fontname="sans-serif", fontcolor="#D97706", fontsize=10]; + + A -> B -> S; + B -> C [style=dashed, color="#9CA3AF"]; + C -> D [style=dashed, color="#9CA3AF"]; + D -> S [style=dashed, color="#9CA3AF", label="squashed\ninto one commit", fontsize=9, fontcolor="#9CA3AF"]; + main -> S [style=dashed, color="#D97706", arrowhead=none]; + + { rank=same; S; main; } +} +``` + +- **History:** linear — one commit per PR on the base branch +- **Merge commits:** no +- **SHAs preserved:** no — a new commit is created +- **Use case:** teams that want a clean `git log` where one commit = one PR + +## Rebase + +```yaml +queue_rules: + - name: default + merge_method: rebase +``` + +Replays each PR commit on top of the base branch, creating new commits with new +SHAs. + +```dot class="graph" +strict digraph { + fontname="sans-serif"; + rankdir="LR"; + splines=line; + node [style=filled, shape=circle, fontname="sans-serif", fontcolor="white", width=0.5, fixedsize=true]; + edge [color="#374151", fontname="sans-serif"]; + + A [label="A", fillcolor="#6B7280"]; + B [label="B", fillcolor="#6B7280"]; + C [label="C", fillcolor="#347D39", style="filled,dashed"]; + D [label="D", fillcolor="#347D39", style="filled,dashed"]; + Cp [label="C'", fillcolor="#D97706"]; + Dp [label="D'", fillcolor="#D97706"]; + + main [label="main", shape=plaintext, fontname="sans-serif", fontcolor="#D97706", fontsize=10]; + + A -> B -> Cp [label="replayed with\nnew SHAs", fontsize=9, fontcolor="#D97706"]; + Cp -> Dp; + A -> C [style=dashed, color="#9CA3AF"]; + C -> D [style=dashed, color="#9CA3AF"]; + main -> Dp [style=dashed, color="#D97706", arrowhead=none]; + + { rank=same; Dp; main; } +} +``` + +- **History:** linear — no merge commits, individual commits are preserved + +- **Merge commits:** no + +- **SHAs preserved:** no — commits are recreated with new SHAs, so the PR + branch ref won't match the base branch + +- **Use case:** teams that want linear history with individual commits visible, + but don't need SHA preservation + +## Fast-Forward + +```yaml +merge_queue: + max_parallel_checks: 1 + +queue_rules: + - name: default + merge_method: fast-forward + batch_size: 1 +``` + +Moves the base branch ref directly to the PR's head commit using the Git API. +No merge commit is created and no commits are recreated — the original SHAs +from the PR branch end up on the base branch. + +```dot class="graph" +strict digraph { + fontname="sans-serif"; + rankdir="LR"; + splines=line; + node [style=filled, shape=circle, fontname="sans-serif", fontcolor="white", width=0.5, fixedsize=true]; + edge [color="#374151", fontname="sans-serif"]; + + A [label="A", fillcolor="#6B7280"]; + B [label="B", fillcolor="#6B7280"]; + C [label="C", fillcolor="#347D39"]; + D [label="D", fillcolor="#347D39"]; + + main [label="main", shape=plaintext, fontname="sans-serif", fontcolor="#347D39", fontsize=10]; + + A -> B -> C [label="same SHAs\non base branch", fontsize=9, fontcolor="#347D39"]; + C -> D; + main -> D [style=dashed, color="#347D39", arrowhead=none]; + + { rank=same; D; main; } +} +``` + +- **History:** linear — commits sit directly on the base branch + +- **Merge commits:** no + +- **SHAs preserved:** yes — the exact same commit SHAs from the PR appear on + the base branch + +- **Use case:** teams and OSS projects that care about commit identity and want + `git log --oneline` to be perfectly clean + +### Requirements and Constraints + +Fast-forward merging has specific requirements: + +- **`batch_size` must be `1`** — batching is not supported because fast-forward + can only advance the ref to a single PR's head + +- **Global `merge_queue.max_parallel_checks` must be set to `1`** — speculative + checks (draft PRs) are not supported + +- **Two-step CI is not supported** — for the same reason as above + +- **`update_method` defaults to `rebase`** — PRs must be rebased on top of the + base branch before merging so the fast-forward is possible. Note that if a + rebase update occurs, the commit SHAs on the PR will change — what + fast-forward preserves are the SHAs of the PR branch at merge time + +:::caution + Fast-forward requires Mergify to push directly to the base branch without + going through a pull request merge. If GitHub branch protections are enabled, + you must allow Mergify to **bypass the required pull requests** setting. + + Mergify bypass required pull requests +::: + +### Complete Example + +A typical fast-forward configuration for a repository that wants a strictly +linear history with preserved commit SHAs: + +```yaml +merge_queue: + max_parallel_checks: 1 + +queue_rules: + - name: default + merge_method: fast-forward + batch_size: 1 + merge_conditions: + - check-success = ci +``` + +## Combining Merge and Update Methods + +The `update_method` option controls how Mergify updates PR branches when they +fall behind the base branch. Combining `merge_method` with `update_method` +gives you additional control over your history shape. + +### Semi-Linear History (Rebase + Merge Commit) + +```yaml +queue_rules: + - name: default + merge_method: merge + update_method: rebase +``` + +PRs are rebased on top of the base branch before being merged with a merge +commit. This produces a history where individual commits are linear, but each +PR is wrapped in a merge commit that marks the PR boundary. + +- **Use case:** teams that want linear commits but also want merge commits as + PR boundary markers in `git log --graph` + +### Linear History via Rebase Update + +```yaml +queue_rules: + - name: default + merge_method: rebase + update_method: rebase +``` + +PRs are rebased to stay current, then rebased again at merge time. This +produces a fully linear history with no merge commits, though SHAs will differ +from the original PR branch. + +## Choosing the Right Strategy + +| I want... | `merge` | `squash` | `rebase` | `fast-forward` | +|---|:---:|:---:|:---:|:---:| +| Linear history | | ✔ | ✔ | ✔ | +| Preserved commit SHAs | ✔ | | | ✔ | +| One commit per PR | | ✔ | | | +| Individual PR commits visible | ✔ | | ✔ | ✔ | +| See PR boundaries in `git log` | ✔ | | | | +| [Batches](/merge-queue/batches) and [parallel checks](/merge-queue/parallel-checks) | ✔ | ✔ | ✔ | | diff --git a/src/content/docs/merge-queue/rules.mdx b/src/content/docs/merge-queue/rules.mdx index 1988181a95..3eaf47a39f 100644 --- a/src/content/docs/merge-queue/rules.mdx +++ b/src/content/docs/merge-queue/rules.mdx @@ -18,7 +18,7 @@ With queue rules, you can: - Define multiple queues for different categories of PRs (e.g. dependencies vs. features). -- Control merge methods (merge, squash, rebase) per queue. +- Control merge methods (merge, squash, rebase, fast-forward) per queue. - Adjust batching behavior (batch size, max wait time). @@ -58,6 +58,23 @@ Here are all the available fields you can configure for a queue rule: +## Merge Method + +The `merge_method` option controls how pull requests are merged into the base +branch. Accepted values are `merge`, `rebase`, `squash`, and `fast-forward`. + +Each method produces a different git history shape. See the +[Merge Strategies](/merge-queue/merge-strategies) guide for a detailed +comparison, including configuration examples and trade-offs. + +:::note + The `fast-forward` method preserves original commit SHAs on the base branch + but requires `batch_size: 1` on the queue rule and + `max_parallel_checks: 1` as a global `merge_queue` setting. See + [Merge Strategies: Fast-Forward](/merge-queue/merge-strategies#fast-forward) + for requirements. +::: + ## Queue vs. Merge Conditions - **`queue_conditions`** → Requirements for a PR to be *accepted into the diff --git a/src/content/navItems.tsx b/src/content/navItems.tsx index c62dc3202a..6317488c7a 100644 --- a/src/content/navItems.tsx +++ b/src/content/navItems.tsx @@ -108,6 +108,11 @@ const navItems: NavItem[] = [ { title: 'Overview', path: '/merge-queue', icon: 'fa6-regular:lightbulb' }, { title: 'Setup', path: '/merge-queue/setup', icon: 'fa6-solid:gear' }, { title: 'Queue Rules', path: '/merge-queue/rules', icon: 'bi:stack' }, + { + title: 'Merge Strategies', + path: '/merge-queue/merge-strategies', + icon: 'octicon:git-merge-16', + }, { title: 'Lifecycle', path: '/merge-queue/lifecycle', icon: 'tabler:refresh' }, { title: 'Priority', path: '/merge-queue/priority', icon: 'fa6-solid:traffic-light' }, { title: 'Pause', path: '/merge-queue/pause', icon: 'fa6-regular:circle-pause' },