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.
+
+
+:::
+
+### 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' },