Advanced Git Merge Strategies: ort, Options and Merge Drivers
Git can merge in more than one way. The strategy is the algorithm; strategy options tune that
algorithm’s behaviour. Confusing the two is not a pedantic distinction — -s ours and -X ours sound
alike and one of them silently discards an entire branch.
Almost all everyday merging uses the default with no options. This lesson covers when that is not enough, and marks clearly which techniques are niche.
The default strategy: ort
Section titled “The default strategy: ort”For a two-branch merge, current Git uses the ort strategy. It performs the three-way merge described
in Lesson 1: find the merge base, compute what each side changed, combine.
ort also handles the cases that make real merges awkward:
- Multiple merge bases. When branches have diverged and re-converged, there can be more than one
candidate common ancestor.
ortmerges the ancestors themselves to produce a synthetic base, then merges against that. - Renames. It detects files that moved and applies the other side’s changes to the new location.
- Directory renames. If one side moved a directory and the other added a file to the old location, it can place the new file in the moved directory.
- Submodules. It fast-forwards submodule pointers where that is unambiguous.
You will not normally name it. git merge feature uses it.
What happened to recursive?
Section titled “What happened to recursive?”Older documentation, and a great deal of older writing, refers to recursive as the default. That was
correct for many years.
ort replaced it as the default in Git 2.34. In current Git, recursive is a synonym for ort —
selecting it does not get you the old implementation, which has been removed.
Strategies versus strategy options
Section titled “Strategies versus strategy options”This is the distinction that matters most.
| Flag | What it does | |
|---|---|---|
| Strategy | -s <name> | Selects the merge algorithm |
| Strategy option | -X <name> | Passes an option to the algorithm |
-s ours and -X ours are entirely different operations, and the difference is destructive.
-X ours — prefer our side in conflicts
Section titled “-X ours — prefer our side in conflicts”git merge -X ours featureMerges normally. Where a hunk conflicts, take our version rather than stopping. Non-conflicting changes from the other branch are still applied.
-s ours — discard the other branch entirely
Section titled “-s ours — discard the other branch entirely”git merge -s ours featureProduces a merge commit whose tree is exactly our current tree. Every change from the other branch is discarded — not just conflicting ones.
Seeing the difference
Section titled “Seeing the difference”A branch that changes one line (conflicting with ours) and adds a new file (not conflicting at all):
git merge -X ours sideAuto-merging f.txtgrep '^b' f.txt # our version of the conflicting linels side.txt # the new file IS presentb-mainside.txtNow the same merge with the strategy instead:
git merge -s ours sidegrep '^b' f.txtls side.txtb-mainls: cannot access 'side.txt': No such file or directoryThe file is gone. -s ours kept our tree wholesale, so the branch’s non-conflicting addition vanished —
while the history records the branch as merged.
Legitimate uses of -s ours
Section titled “Legitimate uses of -s ours”Recording that a branch is superseded. An abandoned branch whose work was redone differently. Merging
with -s ours marks it integrated so it stops appearing as unmerged, without changing any files.
Blocking a forward-port. In a maintenance workflow, recording that a release branch’s changes should
not propagate to main because main already solves the problem differently.
In both cases the intent is bookkeeping, and it is worth saying so in the commit message.
The other strategies
Section titled “The other strategies”| Strategy | Heads | Purpose |
|---|---|---|
ort | 2 | The default |
recursive | 2 | Synonym for ort in current Git |
resolve | 2 | Simpler 3-way merge; no rename detection; picks one merge base |
octopus | 3+ | Default when merging more than two branches; refuses anything needing manual resolution |
ours | Any | Keep our tree entirely |
subtree | 2 | ort adjusted so one tree is merged as a subtree of the other |
octopus
Section titled “octopus”Merging several branches at once:
git merge feat-a feat-b feat-c -m "Integrate topic branches"Fast-forwarding to: feat-aTrying simple merge with feat-bTrying simple merge with feat-cThe resulting commit has one parent per branch. The critical limitation: octopus refuses to run if any side conflicts. It is for bundling branches you already know merge cleanly, typically into a throwaway integration branch for testing. If it refuses, merge them one at a time.
subtree
Section titled “subtree”When one repository’s content has been embedded under a subdirectory of another, the two trees have
different shapes and a normal merge sees every file as added or deleted. The subtree strategy shifts one
tree to align:
git merge -s subtree --squash vendor/upstreamThis is a specialised workflow. Most teams vendoring dependencies today use submodules, a package manager, or a dedicated tool rather than subtree merges.
resolve
Section titled “resolve”An older, simpler algorithm: one merge base, no rename detection. It is very rarely the right choice. Its
one occasional use is when ort’s handling of a criss-cross history produces a result you do not want and
you deliberately want the simpler treatment.
Strategy options worth knowing
Section titled “Strategy options worth knowing”All of these are -X options passed to ort.
Conflict preference
Section titled “Conflict preference”git merge -X ours feature # our side wins conflicting hunksgit merge -X theirs feature # their side wins conflicting hunksSafer than -s ours and safer than git checkout --ours <file>, because non-conflicting changes from both
sides still land. Still a blunt instrument: review the result.
-X theirs is genuinely useful when re-applying a branch that you know supersedes the current state —
regenerated files, a vendored update.
Whitespace
Section titled “Whitespace”git merge -X ignore-space-change featuregit merge -X ignore-all-space featuregit merge -X ignore-space-at-eol featuregit merge -X ignore-cr-at-eol featureThese prevent conflicts caused purely by reindentation or line-ending differences. ignore-cr-at-eol is
particularly useful on mixed Windows/Unix teams.
Rename detection
Section titled “Rename detection”git merge -X find-renames=50 feature # similarity threshold, percentgit merge -X no-renames feature # disable detection entirelyGit does not record renames. It infers them by comparing content similarity between deleted and added files. The default threshold usually works; lowering it helps when a file was renamed and substantially edited.
On very large merges, Git may skip rename detection for performance and tell you so. Raising the limit restores it:
git config merge.renameLimit 10000Diff algorithm
Section titled “Diff algorithm”git merge -X diff-algorithm=patience featuregit merge -X diff-algorithm=histogram featuregit merge -X diff-algorithm=minimal featuregit merge -X diff-algorithm=myers featureThe algorithm determines how Git decides which lines correspond. patience avoids matching on
low-information lines — a lone } or a blank line — which can otherwise produce a technically minimal but
nonsensical diff, and therefore a conflict in the wrong place.
ort already defaults to histogram, which is patience-like with better performance. patience and
histogram also exist as bare -X options, but those spellings are deprecated in favour of
diff-algorithm=.
Renormalize
Section titled “Renormalize”git merge -X renormalize featureRuns a virtual check-out and check-in of all three merge stages before comparing. This is the fix for merges that conflict on every line because two branches were committed under different line-ending or clean-filter rules. If a merge shows an entire file as changed and the visible text is identical, try this.
Merge drivers and .gitattributes
Section titled “Merge drivers and .gitattributes”For file types where line-based merging is meaningless, you can tell Git how to handle them per path.
Marking files binary
Section titled “Marking files binary”*.png binary*.pdf binary*.xlsx binaryGit will not attempt a textual merge; a conflict becomes “choose one version”, which is the honest outcome.
Always take one side
Section titled “Always take one side”package-lock.json merge=oursCHANGELOG.md merge=unionmerge=union is built in and concatenates both sides’ additions instead of conflicting — appropriate for
append-only files such as changelogs, and inappropriate for anything where order or uniqueness matters.
merge=ours requires defining the driver:
git config merge.ours.driver trueThe driver is a command Git runs; true succeeds without modifying the file, which leaves our version in
place.
Custom drivers
Section titled “Custom drivers”A merge driver is any command Git invokes with the three versions:
git config merge.jsonsort.name "Sorted JSON merge"git config merge.jsonsort.driver "my-json-merge %O %A %B %L %P"Placeholders: %O is the base file, %A is ours, %B is theirs, %L is the conflict marker size, and
%P is the final path. The driver writes the result into %A and exits zero for success or non-zero to
signal a conflict.
Choosing a strategy in practice
Section titled “Choosing a strategy in practice”A decision table for the cases where the default is not obviously right.
| Situation | Reach for | Not |
|---|---|---|
| Ordinary merge | Nothing — the default | — |
| Conflicts are all reindentation | -X ignore-space-change | Resolving fifty hunks by hand |
| Whole file conflicts, text looks identical | -X renormalize | -X ignore-all-space |
| A file was renamed and heavily edited | -X find-renames=40 | -X no-renames |
| Regenerated file; incoming version is authoritative | -X theirs, or merge=ours in .gitattributes | Manual resolution each time |
| Branch superseded; record it as integrated | -s ours, with an explanatory message | -X ours |
| Bundling several clean branches for a test build | -s octopus (the default for 3+) | Sequential merges |
| Binary assets conflicting | Mark binary in .gitattributes | Any textual strategy |
| Changelog conflicts on every merge | merge=union in .gitattributes | -X theirs |
Two habits keep this safe. First, prefer a durable fix over a per-merge flag: .gitattributes and a
formatter address the cause, whereas -X ignore-space-change addresses one merge. Second, whenever you
pass a non-default strategy or option, say so in the merge commit message — the resulting tree gives
no clue that anything unusual happened, and a future reader will otherwise assume a normal merge.
Inspecting what a merge actually did
Section titled “Inspecting what a merge actually did”Strategy options make it more important than usual to check the outcome.
git show --cc HEADThe combined diff shows only regions where the result differs from both parents — hand resolutions and
anything a strategy option decided for you. On a merge performed with -X theirs, this is where you see
which of your changes were dropped.
git diff HEAD^1 HEAD --statgit diff HEAD^2 HEAD --statComparing the merge result against each parent separately answers “what did this merge change, from each
side’s point of view?” A -s ours merge shows an empty diff against the first parent and a large one
against the second — which is the signature of a merge that recorded integration without integrating.
Complex histories
Section titled “Complex histories”Criss-cross merges
Section titled “Criss-cross merges”When two branches have merged from each other and then diverged again, there can be several equally valid
merge bases. ort handles this by merging the candidate bases into a synthetic ancestor and merging
against that — which is where the name’s “recursive” ancestry comes from.
You usually will not notice. When you do, it is because a conflict appears in code neither branch seems to have touched recently, and the explanation is that the synthetic base differs from what you expected.
Inspect the candidates:
git merge-base --all main featureMore than one line means a criss-cross history.
Merges that succeed but are wrong
Section titled “Merges that succeed but are wrong”No strategy protects against semantic conflicts — changes that combine cleanly and behave incorrectly.
Strategy options make this more likely, because -X ours, -X theirs and whitespace-ignoring options
all suppress signals Git would otherwise raise.
If you use them, run the tests. If you use -s ours, say so in the commit message.
Common mistakes
Section titled “Common mistakes”Confusing -s ours with -X ours. The strategy discards the branch entirely; the option resolves
conflicting hunks. This is the most consequential confusion in this lesson.
Believing recursive is a distinct strategy. In current Git it is a synonym for ort.
Using -X theirs to avoid resolving. It suppresses the conflict without addressing why it occurred.
Ignoring whitespace in whitespace-significant languages. Can change behaviour silently.
Defining merge drivers in .gitattributes alone. Drivers need per-clone config.
Using union on files where order matters. Concatenation is only sane for genuinely append-only files.
Reaching for a strategy option before understanding the conflict. Nearly every everyday conflict should be resolved by reading both sides.
Mental Model
Section titled “Mental Model”The strategy is which algorithm runs. The options are arguments to that algorithm.
ortis the algorithm for essentially all real merges.-Xoptions adjust how it treats conflicts, whitespace, renames and diffs.-s oursis not really a merge algorithm at all — it is a way of recording a merge that did not happen.
What You Learned
Section titled “What You Learned”ortis the default strategy for two-branch merges;recursiveis now a synonym for it.-sselects a strategy;-Xpasses options to one. They are not interchangeable.-X oursresolves conflicting hunks in your favour;-s oursdiscards the other branch entirely.octopusmerges several branches but refuses anything requiring manual resolution.- Strategy options cover conflict preference, whitespace, rename detection, diff algorithm and renormalize.
.gitattributesplus merge drivers control merging per path; driver definitions are per-clone config.- Criss-cross histories produce multiple merge bases;
git merge-base --allreveals them. - No strategy protects against semantic conflicts.
Try It Yourself
Section titled “Try It Yourself”The -s ours versus -X ours difference is worth seeing once, in a repository you do not care about.
- Create a repository with a file
f.txtcontaining three lines, and commit. - Create
side. Change line 2, and add a new fileside.txt. Commit both. - On
main, change line 2 differently. Commit. - Merge with the option:
git merge -X ours side. Check line 2 (should be yours) and confirmside.txtexists. - Undo:
git reset --hard HEAD~1. - Merge with the strategy:
git merge -s ours side. Check line 2 and predict whetherside.txtexists. - Run
git branch --merged main. Issidelisted? - Run
git show --stat HEAD. What does the merge commit claim to have changed?
Steps 6 to 8 are the point: the file is gone, yet the history says the branch was merged. That combination is exactly why this strategy needs a deliberate reason and a commit message explaining it.
Next Cluster
Section titled “Next Cluster”Merging preserves both histories. Rebasing rewrites one of them — which is more powerful and carries obligations that merging does not.