---
title: Conflict Resolution
description: Let Mergify merge the conflicts that have a mechanical answer, such as lockfiles and changelogs, instead of sending them back to the pull request's author.
---

<PreviewNotice />

Two pull requests that each add a dependency or a line to the changelog often
conflict, even though both changes belong in the result: git
merges by line, and both added theirs at the same place. Someone then has to
update the pull request, pick both sides by hand and wait for CI again.

The `conflict_resolvers` key names the files Mergify merges for you. Mergify
merges each one by its structure (a lockfile entry by entry, a Python file by
its imports and functions) and keeps both changes where they do not touch the
same thing. A real conflict, the same line changed two ways or a function both
pull requests rewrote, still goes to a person: the resolver declines it, and it
stays a conflict.

```yaml
conflict_resolvers:
  - auto: true
```

That one entry is enough to start. Where the resolution goes next depends on
how your [queue rule](/merge-queue/rules#merge-method) merges: see [where a resolution
lands](#where-a-resolution-lands).

## Naming the files to resolve

`conflict_resolvers` is a top-level list, declared once for the repository.
Each entry names its files in one of four ways.

### `auto`

`auto: true` resolves every file Mergify has a strategy for, and lets Mergify
pick the strategy file by file: a package manager's manifest and lockfile with
that package manager's strategy, a source file with its language's, a
changelog with `union`. It never picks a strategy that keeps one side's change
only, and a file no strategy suits merges as usual. When Mergify learns a new
file type, an `auto` entry resolves it with no change to your configuration.

Add `directories` to limit it to part of the repository:

```yaml
conflict_resolvers:
  - auto: true
    directories:
      - /frontend
      - /services/api
```

### A language

A `language` entry resolves every file of that language, with the strategy
Mergify picks for it:

```yaml
conflict_resolvers:
  - language: python
  - language: github-actions
```

<ConflictResolverChoices field="language" />

### A package manager

A `package-manager` entry names a package manager the way Dependabot does,
with the `directories` holding its manifest and lockfile. A manifest and its
lockfile have to agree, so Mergify resolves both together: two pull requests
updating two different dependencies merge in both files, while a dependency
both pull requests changed differently still conflicts.

```yaml
conflict_resolvers:
  - package-manager: npm
    directories:
      - /
      - /frontend
```

<ConflictResolverChoices field="package-manager" />

### File patterns and a strategy

A `paths` entry names its files with glob patterns, matched from the
repository root as [scopes file patterns](/merge-queue/scopes/file-patterns)
are, and the `strategy` to merge them with:

```yaml
conflict_resolvers:
  - paths:
      - CHANGELOG.md
    strategy: union
```

`*` does not cross a `/`, so `*.md` matches Markdown files at the root only;
`**/*.md` matches them at any depth. The `directories` of the other entries are
literal paths starting with `/`, not patterns.

`union`, `ours`, `theirs` and `text` are git's names. *Ours* is the side that
is already there: the base branch, plus the pull requests queued ahead when
Mergify builds a batch. *Theirs* is the pull request being merged.

<ConflictResolverStrategies />

### When several entries match a file

The last entry in the list that matches a file wins, whichever way each entry
names it. An `auto` entry is the exception: any other entry matching a file
wins over it, wherever the two sit in the list.

So to keep a file away from `auto`, or from a broader entry above, name it in
an entry with the `text` strategy. `text` merges by line, as git does with no
resolver, so any conflict in those files stays one:

```yaml
conflict_resolvers:
  - auto: true
  - paths:
      - "generated/**"
      - schema.json
    strategy: text
```

When an entry merges a Mergify configuration file, the result must still load
as a configuration, or the file counts as a conflict.

When your configuration [extends another one](/configuration/sharing), the
extended entries come first and yours after them, so an entry of yours wins on
every file both match, unless yours is an `auto` entry and the extended one is
not. Set `conflict_resolvers: []` to ignore the extended
entries.

:::note
  Mergify does not read your repository's `.gitattributes` when it merges. A
  merge attribute declared there, such as `merge=union` or `binary`, changes
  nothing:
  `conflict_resolvers` is the only way to tell Mergify how to merge a file.
:::

## Where a resolution lands

Mergify resolves a conflict where the merge that lands on your base branch is
made, and that depends on the queue rule that takes the pull request:

```dot class="flow" title="Where a conflict gets resolved depends on how the queue rule merges: the batch Mergify builds settles it, a merge commit Mergify pushes into the pull request settles it, or, under a rebase, nothing does and the author resolves it."
digraph {
  node [width=1.6];

  PR [label="Pull request\nconflicts with\nits base branch", class="external"];
  Batch [label="Resolved in\nthe batch", class="merged"];
  Pushed [label="Merge commit\npushed into the\npull request", class="mergify"];
  Author [label="Left to\nthe author", class="muted"];

  PR -> Batch [label="  merge-batch, or\l  fast-forward with\l  a batch pull request\l"];
  PR -> Pushed [label="  merge,\l  squash\l"];
  PR -> Author [label="  rebase\l"];
}
```

```dot class="queue" narrow
digraph {
  node [width=1.2];

  PR [label="Pull request\nconflicts with\nits base branch", class="external"];
  Batch [label="merge-batch, or\nfast-forward with\na batch pull request:\nresolved in the batch", class="merged"];
  Pushed [label="merge, squash:\nmerge commit pushed\ninto the pull request", class="mergify"];
  Author [label="rebase:\nleft to the author", class="muted"];

  PR -> Batch;
  PR -> Pushed;
  PR -> Author;
}
```

### In the merge queue's batch

Under `merge_method: merge-batch`, or `fast-forward` with a [batch pull
request](/merge-queue/merge-strategies#batch-pr-mode), the batch branch
Mergify builds is what lands. Mergify resolves the conflicts while it builds
the batch, and the batch carries them to your base branch. Nothing is pushed
into the pull request.

The queue takes such a pull request even while GitHub reports it as
conflicting (see [conflicts with the base
branch](/merge-queue/lifecycle#conflicts-with-the-base-branch)). A conflict no
entry settles removes it from the queue when the batch is built, as it would
with no resolver.

### Pushed into the pull request

Under `merge_method: merge` or `squash`, GitHub merges the pull request's own
commits, so the resolution has to be in them. When GitHub reports the pull
request as conflicting, Mergify merges the base branch into it, the way it
builds a batch, and when the entries settle every conflict, pushes that merge
commit onto the pull request's branch:

```text
Merge branch 'main' into my-feature
```

It is all or nothing: if one file is still left conflicting, Mergify pushes
nothing, and the pull request is left to its author as before. Mergify tries
again on the next commit to the pull request or to its base branch.

The push comes as soon as the conflict does, without waiting for a review or
for CI. The same applies to a queue rule whose checks run [in
place](/merge-queue/batches#in-place-checks-no-batch-prs) on the pull request,
whatever its `merge_method`, unless it rebases. A queue rule with no
`merge_method` counts as merging or squashing.

Mergify pushes nothing into a pull request:

- that is a draft;

- that is already queued: a push would restart the checks the queue waits on;

- opened by Dependabot or Renovate, which stop updating a pull request once
  someone else pushes to it;

- from a fork whose author does not allow edits by maintainers;

- whose branch is the default branch, or one whose branch protection or a
  ruleset requires a pull request to change it;

- that no queue rule can take, or that could enter queue rules merging
  differently while it matches none of them: Mergify cannot tell yet where
  the merge will be made.

A pushed merge commit is a new commit on the pull request:

- CI runs again on it.

- Where branch protection dismisses stale approvals, GitHub dismisses the
  approvals the pull request had. Where it requires the most recent push to be
  approved, that push is Mergify's, so the pull request needs a fresh approval.

- The author's local branch is behind: they pull before pushing again, with
  `git pull --no-rebase` so the merge commit is kept.

### Under a rebase: no resolution

A queue rule with `merge_method: rebase`, or one whose checks run in place
with `update_method: rebase` (the default of `merge_method: fast-forward`),
gets no resolution. This is by design: a rebase replays the pull
request's commits onto the base branch and drops the merge commit that would
carry the resolution, so the conflict comes back on the first commit replayed.

The author resolves those conflicts as they would without Mergify: rebase the
pull request onto its base branch and fix them by hand. For Mergify to
resolve them, set the queue rule's `merge_method` to `merge`, `squash` or
`merge-batch`, and where its checks run in place, its `update_method` to
`merge`.

If your repository only allows rebase merging and your queue rule sets no
`merge_method`, set `merge_method: rebase`: otherwise Mergify reads the rule as
merging or squashing and pushes a resolution that GitHub's rebase then drops.

## What the pull request shows

### The check run

Each attempt is reported on the pull request by a check run named **Mergify
Conflict Resolution**. Its summary lists each conflicting file, the strategy that
merged it, and for a file left conflicting, why.

| Conclusion | Title | What happened |
| --- | --- | --- |
| In progress | Resolving the conflicts with `main` | Mergify is merging the base branch into the pull request. |
| Success | Conflicts with `main` resolved | Mergify pushed the merge commit. The run is on the merge commit too. |
| Neutral | Conflicts with `main`: Mergify resolves them when merging | The batch resolves every conflict. |
| Neutral | 2 files still conflict with `main` | Some files are left, each with why (below). |
| Neutral | Could not resolve the conflicts with `main`, or the reason | Mergify could not merge or push. |
| Neutral | No conflict with `main` left to resolve | The pull request no longer conflicts. |
| Neutral | Conflicts with `main` no longer resolved for this commit | The pull request moved on first. |

A file is left when:

- no entry covers it;

- its strategy declined, because both sides changed the same thing;

- git hands no strategy that kind of change: a deletion, a rename, a symbolic
  link or a submodule;

- the Mergify configuration file it merged does not load;

- its strategy did not run, because another file conflicts whatever the
  strategies do.

Mergify could not merge or push when, for instance, the branch is one it does
not push to; the summary says why. The pull request moved on when it or its
base branch got a new commit before Mergify could push, or when it was closed
or stopped taking a push (see the list above).

Mergify never concludes the run as a failure: whether the pull request can
merge is still up to your [merge
conditions](/merge-queue/rules#queue-vs-merge-conditions) and GitHub.

### The comment

Where Mergify pushes resolutions into pull requests, it also comments when the
author has something to do:

- **After a push**, a new comment says Mergify resolved the conflicts, names
  the merge commit and the files it merged, and asks the author to pull before
  pushing again.

- **When a file is left**, a comment says which files need the author and why,
  that Mergify pushed nothing, and that it tries again on the next commit. The
  files Mergify could resolve are listed below them.

- **When Mergify could not merge or push**, a comment says why, that Mergify
  pushed nothing, and that it tries again on the next commit.

Mergify keeps one comment of the last two kinds and edits it as attempts come in.
Once the pull request no longer conflicts, it edits that comment down to one
line saying so. A pull request whose conflicts are all in files no entry covers
gets no comment: GitHub already says it conflicts.

Where the batch resolves the conflicts, there is no comment: the author has
nothing to do.

## The dashboard page

The [dashboard](https://dashboard.mergify.com) has a Conflict Resolution page
for each repository, under **Merge Queue**. It has three tabs:

- **Conflicts** lists the files that conflicted while Mergify built batches,
  over a period you choose, and groups them by what your entries do with them,
  under titles such as "Handled by Your Rules" and "A Rule Would Merge These".
  It suggests the entry that would resolve a file,
  `auto` included.

- **Rules** edits `conflict_resolvers` as one ordered list, the last match
  winning. You add an entry by package manager, language, file pattern or
  `auto`, and committing opens a pull request that changes your configuration
  file. An entry the page cannot edit sends you to the [configuration
  editor](/configuration/conditions#use-the-mergify-configuration-editor).

- **Activity** lists the batches where an entry merged a file and the pull
  requests whose conflicts your entries met, newest first, with the latest
  attempt for each. It counts the pull requests by where their resolution
  lands, as in the figure above.
