---
title: Merge Strategies
description: Choose how pull requests are merged into your base branch and control the shape of your git history.
---

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-batch` | Non-linear | Batch branch commits + 1 merge commit per batch | No | Full |

## 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.

<GitGraph
  nodes={[
    { id: "A", label: "A", color: "gray" },
    { id: "B", label: "B", color: "gray" },
    { id: "C", label: "C", color: "green" },
    { id: "D", label: "D", color: "green" },
    { id: "M", label: "M", color: "blue" },
  ]}
  edges={[
    { from: "A", to: "B" },
    { from: "B", to: "C" },
    { from: "C", to: "D" },
    { from: "B", to: "M" },
    { from: "D", to: "M" },
  ]}
  branch={{ label: "main", node: "M" }}
/>

- **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.

<GitGraph
  nodes={[
    { id: "A", label: "A", color: "gray" },
    { id: "B", label: "B", color: "gray" },
    { id: "S", label: "S", color: "amber" },
    { id: "C", label: "C", color: "green", ghost: true },
    { id: "D", label: "D", color: "green", ghost: true },
  ]}
  edges={[
    { from: "A", to: "B" },
    { from: "B", to: "S" },
    { from: "B", to: "C", dashed: true },
    { from: "C", to: "D", dashed: true },
    { from: "D", to: "S", dashed: true, label: "squashed\ninto one commit" },
  ]}
  branch={{ label: "main", node: "S" }}
/>

- **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.

<GitGraph
  nodes={[
    { id: "A", label: "A", color: "gray" },
    { id: "B", label: "B", color: "gray" },
    { id: "C", label: "C", color: "green", ghost: true },
    { id: "D", label: "D", color: "green", ghost: true },
    { id: "Cp", label: "C'", color: "amber" },
    { id: "Dp", label: "D'", color: "amber" },
  ]}
  edges={[
    { from: "A", to: "B" },
    { from: "B", to: "Cp", label: "replayed with\nnew SHAs" },
    { from: "Cp", to: "Dp" },
    { from: "A", to: "C", dashed: true },
    { from: "C", to: "D", dashed: true },
  ]}
  branch={{ label: "main", node: "Dp" }}
/>

- **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
queue_rules:
  - name: default
    merge_method: fast-forward
```

Fast-forward merging advances the base branch ref directly to the tested
commit(s) using the Git API instead of creating a merge via GitHub. The exact
behavior depends on whether the queue operates in
[inplace or batch-PR mode](/merge-queue/batches#in-place-checks-no-batch-prs).

### Inplace Mode

The PR is rebased on top of the base branch, CI runs on the PR itself, and
the base branch ref is fast-forwarded to the PR's head commit.

In this mode, `update_method` must be set to `rebase` (the default). 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
  When using `update_bot_account` with fast-forward inplace mode, support for
  fork pull requests is deprecated. See the
  [update method deprecation note](#combining-merge-and-update-methods) for
  details and migration options.
:::

<GitGraph
  nodes={[
    { id: "A", label: "A", color: "gray" },
    { id: "B", label: "B", color: "gray" },
    { id: "C", label: "C", color: "green" },
    { id: "D", label: "D", color: "green" },
  ]}
  edges={[
    { from: "A", to: "B" },
    { from: "B", to: "C", label: "same SHAs\non base branch" },
    { from: "C", to: "D" },
  ]}
  branch={{ label: "main", node: "D" }}
/>

- **History:** strictly linear; commits sit directly on the base branch

- **Merge commits:** none

- **SHAs preserved:** yes; the exact same commit SHAs from the PR appear on
  the base branch

### Batch-PR Mode

Mergify creates a batch PR that combines the queued pull requests, runs CI on
that batch, and then fast-forwards the base branch to the batch PR's head
commit.

Because the batch branch combines multiple PRs, the history **may contain merge
commits** from folding each PR into the batch branch.

- **History:** linear at the base-branch level, but individual merge commits
  from combining PRs may appear

- **Merge commits:** possible (from combining PRs in the batch branch)

- **SHAs preserved:** the final merged result is the exact SHA tested by CI

### Constraints

- **`commit_message_format` has no effect**: fast-forward preserves
  the original commits, so custom commit messages are not applicable

- **[Parallel mode](/merge-queue/queue-modes) is not supported**: fast-forward
  is not compatible with scope-based parallel queues

- **Use case:** teams and OSS projects that care about commit identity and want
  the exact CI-tested code on their base branch

:::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.
  See [GitHub Rulesets Compatibility](/merge-queue/github-rulesets) for
  setup instructions.

  <Image src={requiredPRbypassScreenshot} alt="Mergify bypass required pull requests" />
:::

### Examples

Inplace mode, with strictly linear history and 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
```

Batch-PR mode, combining batching with fast-forward merge:

```yaml
queue_rules:
  - name: default
    merge_method: fast-forward
    batch_size: 5
    merge_conditions:
      - check-success = ci
```

## Merge Batch

```yaml
queue_rules:
  - name: default
    merge_method: merge-batch
    batch_size: 5
    merge_conditions:
      - check-success = ci
```

When `merge_method` is set to `merge-batch`, Mergify merges the batch pull
request it creates for batching directly into the base branch using a merge
commit, instead of merging the original pull requests individually.

This is similar to `fast-forward` in that the batch PR itself is merged, but
uses the GitHub pull request merge API rather than advancing the git ref
directly. The resulting merge commit message identifies which pull requests
were included in the batch (e.g., "Merge queue: merged #42, #43, #44").

<GitGraph
  nodes={[
    { id: "A", label: "A", color: "gray" },
    { id: "B", label: "B", color: "gray" },
    { id: "D1", label: "D1", color: "green" },
    { id: "D2", label: "D2", color: "green" },
    { id: "M", label: "M", color: "blue" },
  ]}
  edges={[
    { from: "A", to: "B" },
    { from: "B", to: "D1", label: "batch PR" },
    { from: "D1", to: "D2" },
    { from: "B", to: "M" },
    { from: "D2", to: "M", label: "merge commit" },
  ]}
  branch={{ label: "main", node: "M" }}
/>

- **History:** non-linear; a single merge commit per batch appears on the base
  branch

- **Merge commits:** yes; one merge commit per batch, not per PR

- **SHAs preserved:** no; the batch merge commit is new, and the original PR
  commit SHAs are not preserved because the batch PR contains recreated commits

- **Requires `batch_size > 1`**: this method is designed for batch merging and
  cannot be used with single PRs

- **Use case:** teams that use batch merging and want a single merge commit per
  batch on the base branch, triggering only one deployment per batch

:::note
  Unlike `fast-forward`, `merge-batch` uses the standard GitHub pull request
  merge API, so it works with branch protection settings that require pull
  request merges. No bypass configuration is needed.
:::

## Editing the Batch Branch

By default, if anything other than Mergify pushes a commit to the branch of a
[batch PR](/merge-queue/batches), Mergify dequeues the pull requests embarked in
it and reports the failure. Setting `allow_queue_branch_edit: true` on a queue
rule tells Mergify to trust the branch instead and keep going.

That option requires `merge_method: merge-batch` or `merge_method: fast-forward`.
Those are the only two methods that merge the batch branch itself, so they are
the only ones that can land a commit pushed to it. With `merge`, `squash` or
`rebase`, each pull request is merged at its own head, the batch branch is never
an input to the merge, and such a commit is dropped without a trace. Mergify
rejects the configuration rather than letting the option look like it is doing
something. Set `merge_method` explicitly: leaving it unset is rejected as well,
because the repository default it resolves to is never `merge-batch` or
`fast-forward`.

```yaml
queue_rules:
  - name: default
    merge_method: merge-batch
    batch_size: 5
    allow_queue_branch_edit: true
```

:::caution
  Under these two merge methods, what lands on the base branch is the batch
  branch as a whole. Enabling this option therefore means merging commits that
  were never reviewed as part of any pull request. That is what the option is
  for: batch-level work such as a conflict resolution or a lockfile regeneration
  that belongs to no single pull request. Make sure only Mergify and your own
  tooling can push to these branches.
:::

An accepted edit also restarts the batches behind the edited one, whenever
[parallel checks](/merge-queue/performance#parallel-checks) put more than one batch in flight. Each
of those batches was composed on top of what the batches ahead were expected to merge, so from the
edit onwards their checks run against a tree that will never exist. Mergify tears them down,
recomposes them on the edited head and starts their checks again; the pull requests stay in the queue
throughout. The edited batch itself keeps going, since its own checks already run on the head that
was pushed. Discarding the running checks behind it is the cost of editing a batch branch that has
work queued after it.

Mergify also does not mark the embarked pull requests as merged one by one here.
GitHub considers a pull request merged only once its head commit is reachable
from the new base branch tip, so a pull request whose head no longer matches what
was embarked is not shown as merged even though its content landed.

## 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.

:::note
  Support for fork pull requests with `update_method: rebase` combined with
  `update_bot_account` is deprecated. If your repository
  receives fork PRs, use `update_method: merge` instead.
:::

### 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` | `merge-batch` |
|---|:---:|:---:|:---:|:---:|:---:|
| Linear history | | ✔ | ✔ | ✔ | |
| Preserved commit SHAs | ✔ | | | ✔ | |
| One commit per PR | | ✔ | | | |
| One commit per batch | | | | | ✔ |
| Individual PR commits visible | ✔ | | ✔ | ✔ | |
| See PR boundaries in `git log` | ✔ | | | | |
| [Batches](/merge-queue/batches) and [parallel checks](/merge-queue/performance#parallel-checks) | ✔ | ✔ | ✔ | | ✔ |
| Single deployment per batch | | | | ✔ | ✔ |
