View as Markdown

Merge Strategies

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.

MethodHistoryCommits on base branchSHAs preservedQueue parallelism
mergeNon-linearOriginal commits + merge commitYesFull
squashLinear1 new commit per PRNoFull
rebaseLinearRecreated copies of each commitNoFull
fast-forwardLinearOriginal commits moved to baseYesSerial only
merge-batchNon-linearOriginal commits + 1 merge commit per PR + 1 per batchYesFull
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. When you leave merge_method unset, Mergify uses the first method your repository allows, so a repository that disallows merge commits falls back to squash or rebase instead.

A A B B A->B C C B->C M M B->M D D C->D D->M _branch0 main _branch0->M A A B B A->B C C B->C M M B->M D D C->D D->M _branch0 main _branch0->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

queue_rules:
- name: default
merge_method: squash

Squashes all PR commits into a single commit on the base branch.

A A B B A->B S S B->S C C B->C D D C->D D->S  squashed    into one commit   _branch0 main _branch0->S A A B B A->B S S B->S C C B->C D D C->D D->S  squashed    into one commit   _branch0 main _branch0->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
queue_rules:
- name: default
merge_method: rebase

Replays each PR commit on top of the base branch, creating new commits with new SHAs.

A A B B A->B C C A->C Cp C' B->Cp  replayed with    new SHAs   D D C->D Dp D' Cp->Dp _branch0 main _branch0->Dp A A B B A->B C C A->C Cp C' B->Cp  replayed with    new SHAs   D D C->D Dp D' Cp->Dp _branch0 main _branch0->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

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.

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.

A A B B A->B C C B->C  same SHAs    on base branch   D D C->D _branch0 main _branch0->D A A B B A->B C C B->C  same SHAs    on base branch   D D C->D _branch0 main _branch0->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

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. Each of them is written as the pull request’s own merge commit, as described under Merge Batch.

  • 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

  • commit_message_format has no effect when checking in place: fast-forward preserves the original commits, so custom commit messages are not applicable. In batch-PR mode, it sets the message of the commit that merges each PR into the batch branch

  • Parallel and isolated modes are not supported: both merge batches independently of each other, which fast-forward cannot do. Mergify rejects a queue rule that combines them

  • max_checks_retries is not supported with parallel checks: a retry checks the batch again on a new batch PR, so the batches already checked on top of the failed one can no longer be fast-forwarded once it merges. Mergify rejects a configuration that sets both max_checks_retries and merge_queue.max_parallel_checks above 1 on a fast-forward queue

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

Inplace mode, with strictly linear history and preserved commit SHAs:

merge_queue:
max_parallel_checks: 1
queue_rules:
- name: default
merge_method: fast-forward
batch_size: 1
queue_conditions:
- check-success = ci

The CI check sits in queue_conditions rather than merge_conditions on purpose: a queue with separate merge_conditions is a two-step CI queue, and Mergify runs its checks on a batch pull request instead of in place. See In-place checks (no batch PRs) for the full list of conditions a queue has to meet to check in place.

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

queue_rules:
- name: default
merge_method: fast-forward
batch_size: 5
merge_conditions:
- check-success = ci
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 merge commit that lands on the base branch says what the batch brought in. Its title names the pull requests, then gives the first one’s title and counts the other pull requests that come with it (+2 more). Its body has one line per pull request, with that pull request’s title and author, in the order they were merged. For a batch of #42, #43 and #44, it reads:

Merge #42, #43, #44: Fix the login redirect (+2 more)
- #42 Fix the login redirect (octocat)
- #43 Retry failed uploads (hubot)
- #44 Raise the cache size (monalisa)

In a batch of more than five pull requests, the title names the first five and adds how many it leaves out: a batch of #42 to #48 starts with Merge #42, #43, #44, #45, #46 +2: and ends with (+6 more). To keep the title within 100 characters, Mergify cuts the first pull request’s title at a word and ends it with ….

Inside the batch branch, each pull request is merged by a commit of its own, which lands on the base branch with the batch and is the pull request’s merge commit on GitHub. Mergify writes its message as if that pull request were merged on its own:

  • the queue rule’s commit_message_format sets the title and body unless they are inherit, and appends the trailers

  • whatever it leaves to the repository (all of it, when the rule has no commit_message_format) follows your repository’s merge commit settings on GitHub. With the GitHub defaults, the commit for pull request #42 reads:

    Merge pull request #42 from octo-org/fix-login
    
    Fix the login redirect
    

commit_message_template is not applied to these commits. Because CI runs on the batch branch, a [skip ci]-style marker or a skip-checks: true trailer coming from a pull request is put in parentheses, so it does not skip the batch’s CI.

A A B B A->B C1 C1 B->C1 C2 C2 B->C2 D1 D1 B->D1  batch PR   M M B->M C1->D1  merges #42   D2 D2 C2->D2  merges #43   D1->D2 D2->M  merge commit   _branch0 main _branch0->M A A B B A->B C1 C1 B->C1 C2 C2 B->C2 D1 D1 B->D1  batch PR   M M B->M C1->D1  merges #42   D2 D2 C2->D2  merges #43   D1->D2 D2->M  merge commit   _branch0 main _branch0->M
  • History: non-linear; a single merge commit per batch appears on the base branch

  • Merge commits: yes; one per batch on the base branch, plus one per PR inside the batch branch

  • SHAs preserved: yes; Mergify builds the batch branch by merging each embarked pull request’s head into it, so that head commit becomes the second parent of the commit that merges the pull request instead of a copy. Only the merge commits are new

  • 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

By default, if anything other than Mergify pushes a commit to the branch of a batch PR, 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. A queue rule still setting queue_branch_merge_method: fast-forward should set merge_method: fast-forward instead.

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: if it is unset, the configuration is rejected too, because the repository default that merge_method resolves to is never merge-batch or fast-forward.

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

An accepted edit also restarts the batches behind the edited one, whenever 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

Section titled 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)

Section titled Semi-Linear History (Rebase + Merge Commit)
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

Section titled Linear History via Rebase Update
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.

I want…mergesquashrebasefast-forwardmerge-batch
Linear history✔✔✔
Preserved commit SHAs✔✔✔
One commit per PR✔
One merge commit per batch on the base branch✔
Individual PR commits visible✔✔✔✔
See PR boundaries in git log✔✔
Batches and parallel checks✔✔✔✔
Single deployment per batch✔✔

Was this page helpful?