View as Markdown

Conflict Resolution

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.


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.

conflict_resolvers:
- auto: true

That one entry is enough to start. Where the resolution goes next depends on how your queue rule merges: see where a resolution lands.

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

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:

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

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

conflict_resolvers:
- language: python
- language: github-actions
LanguageWhat it resolves
typescript

Resolves *.{ts,tsx,mts,cts} with the typescript strategy.

javascript

Resolves *.{js,jsx,mjs,cjs} with the typescript strategy.

rust

Resolves *.rs with the rust strategy.

python

Resolves *.{py,pyi} with the python strategy.

yaml

Resolves *.{yml,yaml} with the yaml strategy.

go

Resolves *.go with the go strategy.

ruby

Resolves *.{rb,rake,gemspec,ru,rbi}, Gemfile and Rakefile with the ruby strategy, db/schema.rb with the rails-schema strategy.

swift

Resolves *.swift with the swift strategy.

terraform

Resolves *.{tf,tofu,tfvars} with the hcl strategy.

haskell

Resolves *.hs with the haskell strategy.

java

Resolves *.java with the java strategy.

csharp

Resolves *.cs with the csharp strategy.

kotlin

Resolves *.{kt,kts} with the kotlin strategy.

toml

Resolves *.toml with the toml strategy.

css

Resolves *.css with the css strategy.

dockerfile

Resolves {Dockerfile,dockerfile,Containerfile,containerfile}, {Dockerfile,Containerfile}.* and *.{Dockerfile,dockerfile,Containerfile,containerfile} with the dockerfile strategy.

xml

Resolves *.{xml,resx,resw} with the xml strategy.

astro

Resolves *.astro with the astro strategy.

markdown

Resolves *.{md,mdx,markdown} with the markdown strategy, {CHANGELOG,Changelog,changelog,CHANGES,Changes,changes}.{md,markdown} with the union strategy.

github-actions

Resolves .github/workflows/*.{yml,yaml} and action.{yml,yaml} with the yaml strategy.

mergify

Resolves .mergify.yml, .mergify.yaml, .mergify/config.yml, .mergify/config.yaml, .github/mergify.yml and .github/mergify.yaml at the repository root, with the yaml strategy.

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.

conflict_resolvers:
- package-manager: npm
directories:
- /
- /frontend
Package managerWhat it resolves
npm

Resolves package.json with the json strategy, pnpm-lock.yaml with the pnpm strategy, yarn.lock with the yarn strategy, package-lock.json and npm-shrinkwrap.json with the npm strategy. It covers npm, pnpm and yarn projects alike. Listing a workspace's root also covers the members declared by its package.json (workspaces) or its pnpm-workspace.yaml (packages) on the base branch, globs and exclusions included, each file read as its own tool reads it.

uv

Resolves pyproject.toml and uv.lock with the uv strategy. Listing a workspace's root also covers the members declared by its pyproject.toml ([tool.uv.workspace]) on the base branch, globs and exclusions included, each file read as its own tool reads it.

cargo

Resolves Cargo.toml and Cargo.lock with the cargo strategy. Listing a workspace's root also covers the members declared by its Cargo.toml ([workspace]) on the base branch, globs and exclusions included, each file read as its own tool reads it.

gomod

Resolves go.mod and go.sum with the gomod strategy. List each module's directory.

sbt

Resolves build.sbt, project/*.sbt, project/*.scala and project/build.properties with the sbt strategy. List the build's root, and each subproject whose own build.sbt two pull requests may both change.

terraform

Resolves .terraform.lock.hcl with the terraform strategy. List each root module's directory. The files declaring its providers are not resolved: a terraform language entry resolves them.

opentofu

Resolves .terraform.lock.hcl with the terraform strategy. List each root module's directory. The files declaring its providers are not resolved: a terraform language entry resolves them.

nix

Resolves flake.lock with the nix strategy. A flake.nix still merges as usual.

mix

Resolves mix.exs and mix.lock with the mix strategy. List the project's root, and in an umbrella each app whose own mix.exs two pull requests may both change.

pip

Resolves *requirements*.{txt,in} and requirements/*.{txt,in} with the pip strategy, Pipfile with the toml strategy, pyproject.toml with the uv strategy. It covers pip-compile, Pipenv and Poetry projects alike, and a Pipfile.lock or a poetry.lock still merges as usual.

docker

Resolves {Dockerfile,dockerfile,Containerfile,containerfile}, {Dockerfile,Containerfile}.* and *.{Dockerfile,dockerfile,Containerfile,containerfile} with the dockerfile strategy.

maven

Resolves pom.xml with the xml strategy. List each module's directory.

nuget

Resolves *.{csproj,fsproj,vbproj}, Directory.{Packages,Build}.props, Directory.Build.targets and packages.config with the xml strategy, packages.lock.json with the nuget strategy. List each project's directory, and the one holding its Directory.* files.

A paths entry names its files with glob patterns, matched from the repository root as scopes file patterns are, and the strategy to merge them with:

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.

StrategyWhat it doesWhat it discardsResolves binary files
union

Keep both sides' lines where they conflict, the batch's first. Suited to append-only files, such as a changelog.

Nothing.

No
ours

Keep the batch's whole file and discard the incoming pull request's change to it, even where the two did not conflict.

Discards the incoming pull request's change to the file, even where the two did not conflict.

Yes
theirs

Take the incoming pull request's whole file and discard the batch's, even where the two did not conflict.

Discards the batch's change to the file, even where the two did not conflict.

Yes
prefer-ours

Merge normally, and keep the batch's side only on the lines that conflict -- its whole file when both sides added the file.

Discards the incoming pull request's side on the lines that conflict.

No
prefer-theirs

Merge normally, and keep the incoming pull request's side only on the lines that conflict -- its whole file when both sides added the file.

Discards the batch's side on the lines that conflict.

No
json

Merge a JSON file by its structure rather than by line, so changes to different keys or array elements merge even on adjacent lines, and neither side's change is discarded. Two different values for the same key, a change to a key the other side deleted, a key both sides added differently, two insertions at the same place in an array, or two changes to the same array element, even to different fields of it, are a conflict even where git's line merge would have been clean. Where git's line merge is clean and gives the same result, that text is used as is; otherwise the incoming pull request's changes are written into the batch's text as it wrote them, the rest keeps the batch's formatting and key order, and a change written in a style the batch's file does not use is a conflict. A file that is not valid JSON is merged by line as usual. Suited to JSON files, such as a package.json.

Nothing.

No
pnpm

Merge a pnpm-lock.yaml (lockfile version 9) entry by entry, so two pull requests updating two different dependencies merge even where their entries sit side by side, including when both moved peers of the same package. The same dependency updated to two versions, a package updated on one side while the other moved its peers, a dependency one side removed and the other updated, or a merged file whose entries refer to a package version it no longer has, are a conflict, even where git's line merge would have been clean. The package.json beside it usually conflicts too: a package-manager: npm entry resolves both. A file that is not a version 9 lockfile is merged by line as usual. Suited to pnpm lockfiles (pnpm-lock.yaml).

Nothing.

No
uv

Merge a uv.lock and the pyproject.toml beside it entry by entry, so two pull requests updating two different dependencies merge in both files even on adjacent lines, and two adding different keys at the same place of a pyproject.toml table, such as two extras, keep both: the batch's first, or in order where the table keeps them sorted; sorted or not, two added after one comment, which would be written twice, or where both removed a key, such as a table both renamed two ways, conflict. A merge git's line merge completes in a pyproject.toml is kept as it is, unless it defines the same key twice or keeps a dependency one pull request removed and the other moved to another table, which is a conflict. A conflict, even where git's line merge would have been clean: the same dependency updated to two versions; a uv.lock package one side updated and the other changed, or one side changed while depending on a package the other side updated, since the lockfile records no version constraint to check the two against; and a merged uv.lock naming a package it no longer has, or holding one nothing uses. Where git's line merge conflicts in a pyproject.toml, a dependency one pull request moved to another table while the other changed or removed it is a conflict too, and a list of requirements (dependencies, an extra, a dependency group, [build-system]'s requires) and a workspace's members keep each in its sorted place, two after one comment conflicting there too; an element of any other list, such as a test path or a Poetry version constraint, and a table of an array of tables, such as an index, other tables splitting it or not, are placed by their neighbours, so one replaced two ways, or two added at one place, conflict. An element of a list other than a workspace's that one pull request replaced and the other removed or replaced differently, such as a requirement swapped for two other packages, conflicts too. A file written in a shape uv does not write, such as a multi-line string, is merged by line as usual. Suited to uv lockfiles (uv.lock); pyproject.toml files.

Nothing.

No
rails-schema

Merge a Rails db/schema.rb by line. When two pull requests that each add a migration both change its version line (ActiveRecord::Schema[8.1].define(version: ...)), keep the later version: what rails db:migrate writes once both migrations are applied. Any other conflict in the file, version lines written by two different Rails versions, or a version older than the one the file had before either change, is a conflict as usual. Suited to Rails schema files (db/schema.rb).

Nothing.

No
yarn

Merge a yarn.lock, from yarn 1 or from yarn 2 and later, entry by entry, so two pull requests updating two different dependencies merge even where their entries sit side by side. The same dependency updated to two versions, a dependency one side removed and the other updated, the two sides holding different __metadata, or a merged file referring to a package version it no longer has, or holding one nothing uses, are a conflict, even where git's line merge would have been clean. The package.json beside it usually conflicts too: a package-manager: npm entry resolves both. A file in neither yarn format is merged by line as usual. Suited to yarn lockfiles (yarn.lock).

Nothing.

No
typescript

Merge a TypeScript file (.ts, .tsx, .mts, .cts, .d.ts) or a JavaScript file (.js, .jsx, .mjs, .cjs) by its syntax tree where git's line merge conflicts, so two pull requests adding different imports, functions, members, object keys, tests, cases of a test's it.each([...]) table or JSX elements at the same place keep both: the batch's first, or in the order the file keeps them sorted in. The same declaration, statement or written value changed two ways, a change to something the other side deleted, something one side moved and the other deleted or moved elsewhere, the same name added twice, statements added at the same place (in a function body, or a file's own, such as an exports.name = ... or a require call), declarations added at the same place that run code where they stand (a variable, a field or an object key whose value calls code, a decorated class), tests of a serial suite, table cases that run code or that the test's title numbers by their place (%#) or names alike, two enum members given the same value, or a result that does not parse are a conflict as usual. A merge git completes is left as git wrote it, unless it declares a name twice in one scope with let, const, class or two imports -- the same constant added by both pull requests, one at each end of the file -- or gives an enum member a value neither pull request gave it, where at most one of them changed it (one renumbering a member, the other leaving a later one to follow from it), which is a conflict too, and a file that does not parse, is larger than 2 MiB, or is minified JavaScript (more than 110 characters a line on average) is merged by line as usual. Suited to TypeScript files; JavaScript files.

Nothing.

No
rails-structure

Merge the schema_migrations list Rails writes at the end of a db/structure.sql, so two pull requests that each add a migration merge even though both add a row at the same end of the list: it is written back with the migrations either side added and without those either side removed, sorted as Rails sorts it. The rest of the file is merged by line as usual, so the same table changed by both pull requests is still a conflict, and so are both sides re-sorting the list in opposite orders and a list left with no migration. A file without exactly one such list, as any other SQL file, is merged by line as usual. Suited to Rails structure files (db/structure.sql).

Nothing.

No
rust

Merge a Rust file (.rs) by its syntax tree where git's line merge conflicts, so two pull requests adding different imports, items, methods, fields, enum variants, match arms or derives at the same place keep both: the batch's first, with imports and modules in rustfmt's order and layout. The same item or expression changed two ways, a change to something the other side deleted, the same name added twice, a macro both sides changed, items added at the same place whose order matters (a macro_rules!, a field of a #[repr] struct, a match arm with a guard), a match that would miss an enum variant the other side added, or a result that does not parse are a conflict as usual. A merge git completes is left as git wrote it, unless it defines a name twice in one module, impl, struct or enum, or in two inherent impls of one module written alike -- the same function added by both pull requests, one at each end of the file, not under two different #[cfg]s -- which is a conflict too, and a file that does not parse, or is larger than 2 MiB, is merged by line as usual. Suited to Rust files.

Nothing.

No
python

Merge a Python file (.py, .pyi) by its syntax tree where git's line merge conflicts, so two pull requests adding different imports, functions, tests, methods, string dictionary keys, keyword arguments or, in a test_*.py or *_test.py file, cases of a test's @pytest.mark.parametrize table (literals, with the ids it lists) at the same place keep both: the batch's first, and imports in the order isort and ruff write them, or the order the file already keeps where it plainly follows no sorter. Each keeps its own decorators, docstring, comments and indentation. A function both pull requests changed (but for a test each changed only cases of its table in, and different ones), table cases pytest names by their place (a dictionary, or a tuple that is the value of one argument, given no id) or would name alike, a case both changed, statements or class fields added at the same place, definitions or values whose decorators (other than a test's own marks, fixtures and patches, or, in a test_*.py or *_test.py file, the project's own that run nothing but values), defaults, annotations, operators or subscripts can run code, classes with a base that could register them, imports added to a section the file keeps sorted in an order other than isort's and ruff's own, a change to something the other side deleted, the same name added twice, a name one side removed that the other still uses, a block one side re-indented, or a result that does not parse are a conflict as usual. A merge git completes is left as git wrote it, and a file that does not parse, or is larger than 2 MiB, is merged by line as usual. Suited to Python files.

Nothing.

No
yaml

Merge a YAML file (.yml, .yaml) by its structure where git's line merge conflicts, so two pull requests adding different keys to a mapping keep both, the batch's first or in the order the mapping keeps its keys sorted in, and two adding items at the end of a sequence (a job's steps, a list of hosts) keep both, the batch's first, as do two changing different items of one (the action pinned on each of two neighbouring steps). An item holding a name (a step, a task, a container) is known by it, so two changing different entries of the same one keep both, unless one of them swapped entries the other kept for new ones (a task's module for a block). Each side's text is kept as it wrote it, comments included. The same key or item changed two ways, a change to something the other side deleted, the same key or name added twice, items added at the same place anywhere but the end of a sequence, an item both append among different ones, a document added, removed or replaced, an anchor one side changed while the other changed what refers to it, or a result that does not read back as both sides' changes are a conflict as usual. A merge git completes is left as git wrote it, unless it holds a key twice in one mapping -- the same key added by both pull requests, one at each end -- which is a conflict too, and a file that does not parse, is larger than 2 MiB, has more than 32,767 lines (16,383 with Windows line endings) or a longer line, or uses a list or an alias as a key is merged by line as usual. In Mergify's own configuration file, two rules or entries added at the same place, its end included, are a conflict in pull_request_rules, queue_rules, priority_rules and conflict_resolvers, whose order Mergify reads; two merge protections or two conditions added at the same place keep both; and two changing different entries of a rule keep both even where one swapped an entry for another. Suited to YAML files.

Nothing.

No
cargo

Merge a Cargo.lock (lockfile version 3 or 4) and the Cargo.toml beside it entry by entry, so two pull requests updating or adding two different crates merge in both files even on adjacent lines, and two adding a crate to a workspace's members, or different keys at the same place of a Cargo.toml table, keep both: the batch's first, or in order where the list keeps them sorted; sorted or not, two added after one comment, which would be written twice, or two Cargo.toml keys where both removed one, such as a target table both renamed two ways, conflict. A merge git's line merge completes in a Cargo.toml is kept as it is, unless it defines the same key twice, keeps a crate one pull request removed and the other moved to another table, or lists a workspace member twice or one a pull request removed, which is a conflict. A conflict, even where git's line merge would have been clean: the same crate updated to two versions or changed two ways; a Cargo.lock package one side changed while depending on a crate the other side removed or moved to a lower version, since the lockfile records no version constraint to check the two against; the two sides holding different lockfile versions; and a merged Cargo.lock that is not what Cargo writes, naming a package it no longer has, holding one nothing uses, or two versions of a crate Cargo would resolve to one. Where git's line merge conflicts in a Cargo.toml, a crate one pull request moved to another table while the other changed or removed it is a conflict too, and an element of a list other than a workspace's, such as a feature or a crate type, and a table of an array of tables, such as a [[bin]], other tables splitting it or not, are placed by their neighbours, so one replaced two ways, or two added at one place, conflict, as does one that one pull request replaced and the other removed. A file written in a shape Cargo does not write, such as a multi-line string, or an older lockfile version, is merged by line as usual. Suited to Cargo lockfiles (Cargo.lock); Cargo manifests (Cargo.toml).

Nothing.

No
npm

Merge a package-lock.json or an npm-shrinkwrap.json (lockfile version 2 or 3) entry by entry, so two pull requests updating or adding two different dependencies merge even where their entries sit side by side. A conflict, even where git's line merge would have been clean: the same package changed two ways; a package one side changed and the other removed; the two sides holding different lockfile versions; and a merged file in which a dependency would resolve to a version outside its range that no side resolved it to, or in which npm would mark a package development-only, optional or unused otherwise than the side it comes from does. The package.json beside it usually conflicts too: a package-manager: npm entry resolves both. A version 1 lockfile, or a file laid out otherwise than npm writes it, is merged by line as usual. Suited to npm lockfiles (package-lock.json, npm-shrinkwrap.json).

Nothing.

No
go

Merge a Go file (.go) by its syntax tree where git's line merge conflicts, so two pull requests adding different imports, functions, methods, types, struct fields, interface methods, constants, map keys or rows at the end of a test table at the same place keep both: the batch's first, with imports in gofmt's order and the lines laid out as gofmt aligns them. The same declaration or written value changed two ways, a function or a test table row both pull requests changed, a change to something the other side deleted, the same name declared twice, two constants given the same value, declarations added at the same place whose order matters (an init function, an iota constant, a variable or key whose value calls code, a field of a struct with a padding field), table rows added at the same place anywhere but the end, an import left unused or a package left without its import, or a result that does not parse are a conflict as usual. A merge git completes is left as git wrote it, unless it declares a name twice in one place -- the same type added by both pull requests, one at each end of the file -- or gives a constant a value neither pull request gave it, where at most one of them changed it (one changing an iota expression, the other leaving a later constant to repeat it), which is a conflict too, and a file that does not parse, or is larger than 2 MiB, is merged by line as usual. Suited to Go files.

Nothing.

No
gomod

Merge a go.mod and the go.sum beside it entry by entry, so two pull requests updating or adding two different modules merge in both files even on adjacent lines. A conflict, even where git's line merge would have been clean: the same module updated to two versions or changed two ways, in either file. Neither file records why a module is required, so a module one pull request removed is removed even where the other pull request's change needs it, including on lines git's line merge would have conflicted on, and Go then refuses to build until go mod tidy puts it back. A go.mod with a quoted string or a directive Go does not know is merged by line as usual. Suited to Go module files (go.mod); Go checksum files (go.sum).

Nothing.

No
sbt

Merge an sbt build file (build.sbt, project/*.sbt, project/*.scala, project/build.properties) by line as git does, except that a change rewriting only versions never conflicts with the lines around it: two pull requests bumping dependencies on adjacent lines, or one bumping a dependency beside a line the other adds or removes, merge, each version taken from the pull request that changed it. A version is a double-quoted string that reads as one, or the value of project/build.properties' sbt.version. The same version changed two ways, and lines both pull requests changed otherwise, such as two dependencies added at one place or one appended to a Seq(...) whose last element the other bumped, are a conflict as usual. A merge git completes is left as git wrote it. Suited to sbt build files (build.sbt, project/*.sbt, project/*.scala, project/build.properties).

Nothing.

No
terraform

Merge a .terraform.lock.hcl, from Terraform or OpenTofu, provider by provider, so two pull requests updating or adding two different providers merge even on adjacent lines, and so do two pull requests locking one provider version for two platforms. A conflict, even where git's line merge would have been clean: the same provider changed by both to two versions or under two sets of constraints. The lock file does not record why a provider is required, so a provider one pull request removed is removed even where the other pull request's change needs it, including on lines git's line merge would have conflicted on, and terraform init then puts it back. A lock file either pull request wrote in a layout Terraform does not write, such as with a module block or a list on one line, is merged by line as usual. Suited to Terraform and OpenTofu lock files (.terraform.lock.hcl).

Nothing.

No
nix

Merge a flake.lock (lockfile version 7) input by input, so two pull requests updating or adding two different inputs merge, even where an input one adds makes Nix rename the nodes the other updated. A conflict, even where git's line merge would have been clean: the same input locked two ways, or locked by one side and removed by the other; a merged flake.lock with a follows leading to no input or a node nothing uses; and a merge base not written as Nix writes one under two sides that are. Either side not written as Nix writes one, or in an older lockfile version, is merged by line as usual. The flake.nix beside it is not read. Suited to Nix flake lockfiles (flake.lock).

Nothing.

No
ruby

Merge a Ruby file (.rb, .rake, .gemspec, .ru, .rbi, a Gemfile or a Rakefile) by its syntax tree where git's line merge conflicts, so two pull requests adding different methods, attributes, constants, classes, requires, tests, let helpers, gem specification dependencies or hash keys at the same place keep both: the batch's first, with requires kept in the order the file sorts them. The same definition or value changed two ways, a method or a block of code both pull requests changed, a change to something the other side deleted, the same name defined twice, constants written together changed differently (a version's TINY on one side and PRE on the other), statements and class-body calls added at the same place whose order matters (include, has_many, validates, a hook, a Gemfile's gem), a method one pull request deleted that the other one's change still calls, or a result that does not parse are a conflict as usual. A merge git completes is left as git wrote it, and a file that does not parse, or is larger than 2 MiB, is merged by line as usual. Suited to Ruby files.

Nothing.

No
swift

Merge a Swift file (.swift) by its syntax tree where git's line merge conflicts, so two pull requests adding different imports, types, extensions, methods of different names, protocol requirements, enum cases or properties at the same place keep both: the batch's first, with imports kept sorted where the file sorts them. The same declaration changed two ways, a function both pull requests changed, a change to something the other side deleted, the same name declared twice or two overloads of one name (two initializers among them), declarations added at the same place whose order matters (a statement, a stored property or a global variable whose value runs code, the case of an enum numbered or listed by its order), two cases given the same raw value, a case one pull request added beside a switch over its enum with no default the other wrote, a #if left without its #endif, an import one pull request removed while the other changed the file, or a result that does not parse are a conflict as usual. A merge git completes is left as git wrote it, unless it redeclares something in one file or type -- the same method, with the same signature, added by both pull requests, one at each end of a type or one in each of two of its extensions that add no conformance, not under two different #ifs -- or gives a case a raw value neither pull request gave it, where at most one of them changed it (one renumbering a case, the other leaving a later one to follow from it), which is a conflict too, and a file that does not parse, or is larger than 2 MiB, is merged by line as usual. Suited to Swift files.

Nothing.

No
mix

Merge a mix.lock dependency by dependency, and the deps list of the mix.exs beside it element by element, so two pull requests updating or adding two different packages merge in both files even on adjacent lines. A conflict, even where git's line merge would have been clean: the same package changed two ways; a package one side changed and the other removed; and a merged mix.lock in which a Hex package's dependency is no longer locked at a version its recorded requirement accepts, unless a side holds that same pair. Merged by line as usual: a mix.lock where one side changed a git dependency and the other side changed any package, since the lockfile records nothing of what a git dependency requires; the rest of a mix.exs where both sides changed it; and a deps list not laid out as mix format writes it. Suited to Mix lockfiles (mix.lock); Mix project files (mix.exs).

Nothing.

No
hcl

Merge a Terraform or OpenTofu configuration file (.tf, .tofu, .tfvars) by its syntax tree where git's line merge conflicts, so two pull requests adding different resources, variables, outputs or other blocks at the same place, different attributes to the same block, or different keys to the same map keep both: the batch's first, with the = columns terraform fmt aligns aligned again. The same attribute changed two ways (a version both pull requests bumped, a list both changed), a change to something the other side deleted, the same block or key added twice differently, two blocks of one type added to the same block (a provider may read their order), the same name declared twice in the file, a name one pull request deleted that the other's change refers to, two keys added to an object written on one line, or a result that does not parse are a conflict as usual. A merge git completes is left as git wrote it, and a file that is not a .tf, .tofu or .tfvars one (a Terragrunt or Packer .hcl file), does not parse, or is larger than 1 MiB, is merged by line as usual. Suited to Terraform and OpenTofu configuration files.

Nothing.

No
haskell

Merge a Haskell file (.hs) by its syntax tree where git's line merge conflicts, so two pull requests adding different imports, imported names, LANGUAGE pragmas, functions, types or instances at the same place keep both: the batch's first, or in the order the file keeps them sorted in. A function moves with its type signature, all its clauses and the pragmas naming it, and exports added by both pull requests are kept only at the end of the export list. A function whose code both pull requests changed (its signature on one side and a clause on the other included), the same name added twice, a change to something the other side deleted, a name one side removed that the other still uses, an import of a whole module one pull request removed or narrowed while the other added code reading a name the file does not define or import by name, declarations added at the same place as a Template Haskell splice, exports added at the same place anywhere but the end, an import or export list one pull request reformatted while the other changed it, or a result that does not parse are a conflict as usual. A merge git completes is left as git wrote it, and a file that does not parse, uses the C preprocessor, or is larger than 2 MiB, is merged by line as usual. Suited to Haskell files.

Nothing.

No
java

Merge a Java file (.java) by its syntax tree where git's line merge conflicts, so two pull requests adding different imports, types, methods, fields or tests at the same place keep both: the batch's first, with imports in the order the file keeps them, and a member's annotations and Javadoc moving with it. The same member changed two ways, a method whose signature one pull request changed and whose body the other did, a change to something the other side deleted, the same name declared twice (two overloads of one method or two constructors included), the overloads of one name each pull request changed its own way, a name one pull request imports that the other declares, an empty JDK collection one pull request creates that the other's import or nested class turns into another class, elements added at the same place whose order matters (an enum constant, a statement, an initializer block, a field whose initializer calls code, a field of a class Lombok builds a constructor of, a Spring @Bean method), two imports of one simple name, an import landing among imports of the other kind (static or not), an import one pull request deleted while the other reads its name (a wildcard one included), or a result that does not parse are a conflict as usual. A merge git completes is left as git wrote it, and a file that does not parse, or is larger than 2 MiB, is merged by line as usual. Suited to Java files.

Nothing.

No
csharp

Merge a C# file (.cs) by its syntax tree where git's line merge conflicts, so two pull requests adding different using directives, types, methods, properties, fields or tests at the same place keep both: the batch's first, with using directives in the order the file keeps them, and a member's attributes and XML documentation moving with it. The same member changed two ways, a change to something the other side deleted, the same name declared twice (two overloads of one method or two constructors included), the overloads of one name each pull request changed its own way, a using alias one pull request adds that the other declares as a member, elements added at the same place whose order matters (an enum member, a statement, a field or property whose initializer runs code, a struct's fields, a COM interface's methods), two enum members given the same value, a using directive landing after a declaration or among directives of another kind, a using directive one pull request deleted while the other adds code, a member one pull request deleted or renamed while the other reads its name, or a result that does not parse are a conflict as usual. A merge git completes is left as git wrote it, unless it gives two members of an enum the same value -- the next free one, added by both pull requests, one at each end of the enum -- or gives an enum member a value neither pull request gave it, where at most one of them changed it (one renumbering a member, the other leaving a later one to follow from it), which is a conflict too, and a file that does not parse, or is larger than 2 MiB, is merged by line as usual. Suited to C# files.

Nothing.

No
kotlin

Merge a Kotlin file (.kt) or script (.kts) by its syntax tree where git's line merge conflicts, so two pull requests adding different imports, classes, functions, properties or tests at the same place keep both: the batch's first, with imports in the order the file keeps them, and a declaration's annotations and KDoc moving with it. The same declaration changed two ways, a change to something the other side deleted, the same name declared twice (two overloads of one function or two constructors included), the overloads of one name each pull request changed its own way, a name one pull request imports that the other declares or newly reads without that import, two imports of one simple name, elements added at the same place whose order matters (an enum entry, a statement, a when branch, an init block, a property whose initializer runs code, a property of a @Serializable or @Entity class, a Spring @Bean function), an import one pull request deleted while the other reads its name (one on demand included), a declaration one pull request deleted or renamed while the other reads its name, or a result that does not parse are a conflict as usual. A merge git completes is left as git wrote it, and a file that does not parse, or is larger than 2 MiB, is merged by line as usual. Suited to Kotlin files and scripts.

Nothing.

No
toml

Merge a TOML file by its tables and keys where git's line merge conflicts, so two pull requests adding or changing different keys of the same table, or adding different tables at the same place, keep both: the batch's first, or in order where the file keeps them sorted. The same key or array element changed two ways, a change to something the other side deleted, a key one pull request moved to another table while the other changed or removed it, the same key added twice differently, elements added at the same place of an array (two different bumps of one pinned dependency among them) or of an array of tables, other tables splitting it or not, a multi-line string both changed, a reordering, or a result that defines something twice or writes one comment twice are a conflict as usual. A merge git completes is left as git wrote it, unless it defines the same key or table twice -- one added by each pull request at either end of a table -- or leaves an array element with no comma before the next, which is a conflict too, and a file with an inline table spread over lines, an array element other than an array spread over lines, a table standing apart from the array of tables it belongs to, or a Julia Manifest.toml, is merged by line as usual. Suited to TOML files, such as a rust-toolchain.toml or a Gradle version catalog.

Nothing.

No
css

Merge a CSS stylesheet (.css) by its rules and declarations where git's line merge conflicts, so two pull requests adding different rules at the same place, or different properties to one rule, keep both: the batch's first, with a comment heading a rule moving with it. Both are kept only where their order cannot change what the browser applies. Rules, at-rules or declarations both pull requests added at the same place that set a property in common, or a property and its shorthand (margin and margin-top), are a conflict whatever their selectors, since one element may match both and the one that comes last wins. So is a rule one pull request moved or replaced beside one the other added that sets a property in common, or moved past one the other changed to set a property in common. So are a rule holding an @apply, which sets what the file does not show; @import, @charset, @namespace and @layer rules, and at-rules with no block, added at the same place; two @keyframes, @counter-style, @property, @position-try or @font-palette-values rules of one name, two @font-face rules of one family whose weights, styles, stretches and unicode-ranges all overlap (one left out, or written auto, overlaps every value), or two keyframe steps at one offset, added at the same place; the same declaration changed two ways; a change to something the other side deleted; the same property added twice; a custom property one pull request deleted while the other reads it with var(); and a result that does not parse. A merge git completes is left as git wrote it, and a minified stylesheet (.min.css), a file larger than 2 MiB, or one that does not parse (one holding a named @container rule, or a & after a nested selector, included) is merged by line as usual. Suited to CSS stylesheets.

Nothing.

No
pip

Merge a pip requirements file (a requirements.txt, or the .in pip-compile reads) requirement by requirement where git's line merge conflicts, so two pull requests updating two different dependencies merge even on adjacent lines, and two adding a requirement at the same place keep both: the batch's first, or in their sorted place where the file is sorted, as pip-compile writes it. A requirement's version and hashes go together, its # via lines and the comments above it apart. A merge git's line merge completes is kept as it is. Where it conflicts, still a conflict: the same package, or the options (--index-url, -r and the like), changed two ways, a URL naming a package by #egg= counting as that package; a URL or an editable naming no package beside any change of the other pull request; a package one pull request changed and the other removed; in a file pip-compile wrote, a package one side changed while depending on a package the other side moved, since the file records no version constraint to check the two against, and a merged file whose # via lines name a package it no longer holds. A file pip would not read is merged by line as usual. Suited to pip requirements files (requirements.txt), written by hand or by pip-compile.

Nothing.

No
dockerfile

Merge a Dockerfile or a Containerfile by its instructions where git's line merge conflicts, so two pull requests changing different instructions, or adding different packages to the same RUN -- an apt-get install, apk add, pip install, npm install or install -t list, each package on its own line or not -- keep both: the batch's first, or in order where the list is sorted, as Docker's best practices ask. A stage is known by its AS name, an ARG, ENV or LABEL by the names it sets. Two pull requests adding instructions at the same place (but for ARG, ENV, LABEL or EXPOSE ones reading no variable), the same instruction changed two ways (a FROM image bumped by both, by its tag or its digest), the same package changed two ways, a RUN command other than an install both changed, an option of an install either changed, a package added beside an option that may read it as its value, a command holding a variable or a redirection, or a heredoc, are a conflict as usual. A merge git completes is left as git wrote it, and a file with an escape parser directive, a file that does not parse, or one larger than 2 MiB, is merged by line as usual. Suited to Dockerfiles and Containerfiles.

Nothing.

No
xml

Merge an XML file by its elements and attributes where git's line merge conflicts, so two pull requests changing different elements, or different attributes of one element, keep both, each written as that pull request wrote it, comments and spacing included. In a string table -- a .NET .resx or .resw file, an Android res/values*/ file -- an entry is known by its name, and an Android resource by its type too, so two pull requests adding different entries at the same place keep both, the batch's first. In a Maven pom.xml, a dependency is known by its coordinates, a plugin by its group and artifact, a module, a profile and a property by their name, so two pull requests changing different dependencies keep both, and so do two adding different properties anywhere, or different dependencies, plugins, modules or profiles after a list's last, the batch's first, since Maven reads those in order. In an MSBuild project (.csproj, .fsproj, .vbproj, .props, .targets), a package reference, a central package version and a global package reference are known by the package's id, whatever its case, and its condition, and in a packages.config a package by its id, so two pull requests adding or changing different packages keep both, the batch's first, and two adding one package to an item group, even under two conditions, conflict. Elsewhere, two pull requests adding elements at the same place, whose order a schema or a program may read, or each changing one of two elements of the same name, which are known by their text, the same element, attribute or text changed two ways, a change to something the other side deleted, an entry or attribute added twice differently, or a result holding an element one pull request moved and the other changed or deleted, are a conflict as usual. A merge git completes is left as git wrote it, unless it holds one attribute twice in a tag, or one string table entry, one entry of a Maven list or one package of an item group twice, or keeps a Version on a package reference where the other side moved the project's versions to central package management, which is a conflict too, and a file that does not parse as XML, or one larger than 2 MiB, is merged by line as usual. Suited to XML files, such as a .NET .resx or an Android res/values string table, Maven's pom.xml, and the package references of an MSBuild project or a packages.config.

Nothing.

No
nuget

Merge a packages.lock.json (lockfile version 1 or 2) package by package, so two pull requests updating or adding two different packages merge even where their entries sit side by side. A conflict, even where git's line merge would have been clean: the same package changed two ways for one target framework; a package one side changed and the other removed; the two sides holding different lockfile versions; and a merged file in which a package depends on one the file no longer holds, or holds at a version outside the range it asks for, where neither pull request's file holds it so, as NuGet writes where a nearer reference wins; a target framework one pull request added while the other changed the range the project requests a package at; or a transitive package nothing depends on. The project file beside it usually conflicts too: a package-manager: nuget entry resolves both. A file laid out otherwise than NuGet writes it is merged by line as usual. Suited to NuGet lockfiles (packages.lock.json).

Nothing.

No
astro

Merge an Astro component (.astro) by its syntax tree where git's line merge conflicts: its frontmatter and the text of its scripts as the typescript strategy merges TypeScript, and its markup element by element, so two pull requests adding different imports, attributes to one tag, or elements and {expressions} on lines of their own at the same place keep both, the batch's first. The same attribute, text, expression, style or declaration changed two ways, a change to something the other side deleted, text, an element within a line of text, a spread attribute, a script, a style or a <link> added at the same place, two added elements that differ in one attribute's value only (the same button in two styles), one element both pull requests added spaced their own ways, an element both added beside other markup only one of them added, an element one moved into another that the other changed or deleted, a literal id (id="x", not id={x}) given to more elements than either pull request gives it, a minified script both changed, markup reading a name the merged frontmatter no longer declares (one pull request deleting an import, the other adding markup that uses it), or a result that does not parse are a conflict as usual. A merge git completes is left as git wrote it, unless its frontmatter or a script is one the typescript strategy would refuse, such as a name declared twice, and a file that does not parse, or is larger than 2 MiB, is merged by line as usual. Suited to Astro components (.astro).

Nothing.

No
markdown

Merge a Markdown or MDX file (.md, .mdx, .markdown) by its blocks where git's line merge conflicts, so two pull requests changing different list items, table rows or paragraphs keep both, even side by side, each written as that pull request wrote it. A section is known by its heading, whatever its case, and a table by its header. Front matter merges as YAML, key by key, and an MDX file's import and export statements as JavaScript, so two pull requests importing a component each keep both. Two pull requests adding items to a list or rows to a table that every version keeps sorted, three or more of them, have them sorted in, and link definitions added at one place are all kept, unless both define one label. The same lines of a paragraph or a code block, the same heading, table cell or value reworded two ways, blocks added at the same place anywhere else, whose order a reader reads, a paragraph, note or section added next to one the other pull request changed, which may already say what it says, a block both add at two places, and a change to something the other side deleted are a conflict as usual. A merge git completes is left as git wrote it, and a file that does not parse or is larger than 2 MiB is merged by line as usual. Suited to Markdown and MDX files, such as a documentation page.

Nothing.

No
text

Merge by line, as git does where no resolver applies: an exception to a broader entry before it, or to auto. Any conflict stays a conflict.

Nothing.

No

When several entries match a file

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

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

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:

PR Pull request conflicts with its base branch Batch Resolved in the batch PR->Batch  merge-batch, or  fast-forward with  a batch pull request Pushed Merge commit pushed into the pull request PR->Pushed  merge,  squash Author Left to the author PR->Author  rebase PR Pull request conflicts with its base branch Batch merge-batch, or fast-forward with a batch pull request: resolved in the batch PR->Batch Pushed merge, squash: merge commit pushed into the pull request PR->Pushed Author rebase: left to the author PR->Author
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.

Under merge_method: merge-batch, or fast-forward with a batch pull request, 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). A conflict no entry settles removes it from the queue when the batch is built, as it would with no resolver.

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:

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

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.

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 and GitHub.

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

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

Was this page helpful?