Choosing between the ort and recursive strategies Jump to heading

Git replaced its default merge strategy in version 2.33, and most people noticed only that large merges got faster. The two strategies solve the same problem β€” a three-way merge with possibly several merge bases β€” and they agree on the vast majority of inputs. Where they differ is rename detection, directory handling and performance, and those differences show up exactly when a merge is already difficult. This recipe explains when the distinction matters, within merge strategies and gitattributes.

When to use this approach Jump to heading

  • A merge conflicted and you suspect the strategy is involved.
  • Someone has proposed pinning the strategy in team configuration.
  • Large merges are slow and you want to know whether the strategy is the cause.
  • A directory was renamed on one side and files were added to it on the other.
  • If your merges are small and mostly clean, the default is correct and this page is background reading.

Step 1 β€” Establish which strategy you are using Jump to heading

git --version                       # ort is the default from 2.33
git config --get pull.twohead       # a value here overrides the default
git config --get merge.default      # rarely set; the same effect
# Confirm on an actual merge, with verbosity turned up
git -c merge.verbosity=5 merge --no-commit --no-ff other-branch 2>&1 | head -8
git merge --abort
# Verification: no configuration is pinning the old strategy
git config --show-origin --get-regexp 'merge\.|pull\.twohead' || echo "using the default"
Where the two strategies actually differBoth implement the same three-way merge model. The newer one detects renames more reliably, handles directory renames that the older one missed, and is dramatically faster on large trees because it does not write intermediate states to the working directory.recursive (pre-2.33)ort (current default)file rename detectiongoodbetter, more consistentdirectory renamesoften missedhandledspeed on large treeswrites to the worktreein memoryresult on ordinary mergesidenticalidenticalthe last row is why the change went almost unnoticed

Step 2 β€” Reproduce the merge with each strategy Jump to heading

The only reliable way to know whether the strategy matters for a specific merge is to run both.

git switch -c probe/ort main
git merge -s ort --no-commit --no-ff feature 2>&1 | tail -5
git diff --name-only --diff-filter=U | tee /tmp/ort-conflicts.txt | wc -l
git merge --abort && git switch main && git branch -D probe/ort
git switch -c probe/recursive main
git merge -s recursive --no-commit --no-ff feature 2>&1 | tail -5
git diff --name-only --diff-filter=U | tee /tmp/rec-conflicts.txt | wc -l
git merge --abort && git switch main && git branch -D probe/recursive
# Verification: do they disagree at all?
diff /tmp/ort-conflicts.txt /tmp/rec-conflicts.txt && echo "identical conflict sets"

If the conflict sets are identical β€” which they usually are β€” the strategy is not your problem and the conflict is genuine.

Step 3 β€” Understand the directory-rename case Jump to heading

This is the difference most likely to affect you, and it is the one that produces a merge result people describe as β€œGit lost my file”.

# On one branch: a directory is renamed
git switch -c rename main
git mv src/util src/helpers && git commit -m 'refactor: rename util to helpers'

# On the other: a new file is added to the old directory
git switch -c addition main
printf 'export const clamp = (n) => n;\n' > src/util/clamp.ts
git add src/util/clamp.ts && git commit -m 'feat: add clamp'
# ort follows the directory rename; recursive leaves the new file behind
git switch rename && git merge -s ort --no-commit addition
git status --short | grep clamp
git merge --abort
# Verification: where did the file land under each strategy?
git merge -s recursive --no-commit addition && git status --short | grep clamp; git merge --abort
The directory-rename case, side by sideOne branch renames a directory while the other adds a file inside the old path. The newer strategy follows the rename and places the new file in the renamed directory; the older one leaves it in a directory that no longer exists on the merged side.the merge has to decide where clamp.ts belongsmainBrename dirBRadd fileBAmergedMort puts A inside the renamed directory; recursive leaves it in the old one

Step 4 β€” Decide whether to pin anything Jump to heading

The short answer is no, and the cases for the long answer are narrow.

# If you must pin for a specific merge β€” never globally
git merge -s recursive other-branch

# Pinning globally, for reference. Almost always a mistake.
# git config --global pull.twohead recursive
# Verification: check that nothing has pinned it accidentally
git config --global --get pull.twohead && echo "PINNED β€” is this deliberate?" || echo "default in use"

SAFETY WARNING β€” pinning the old strategy in shared configuration means everyone gets worse rename handling and slower merges to preserve behaviour that almost never differs. If a specific merge genuinely needs it, pass the flag for that merge and say why in the commit message. A pinned strategy in a team configuration file outlives the reason for it by years.

Step 5 β€” Attack the real cause instead Jump to heading

When a merge is painful, the strategy is rarely the lever that helps. The three that do are attributes, conflict style and branch lifetime.

# Conflict style: see the common ancestor rather than guessing
git config --global merge.conflictStyle zdiff3
# Attributes: stop conflicting on files with a deterministic resolution
git check-attr merge -- package-lock.json CHANGELOG.md
# Branch lifetime: how far has this branch diverged?
git rev-list --left-right --count main...feature
What actually reduces conflict painChanging the merge strategy affects a small number of edge cases. Enabling the three-way conflict style, routing generated files to drivers, and shortening branch lifetime each remove whole categories of conflict.conflicts removed per hundred, by interventionshorter branches41attributes and drivers27zdiff3 conflict style0changing the strategy2zdiff3 removes no conflicts β€” it makes the remaining ones resolvable

The third bar is deliberately zero: conflict style does not prevent conflicts, it makes the ones you get decidable by showing what the common ancestor said. That is a different kind of win and usually the highest-value single setting available β€” see 3-way merge fundamentals.

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Is there any reason to use recursive today? Jump to heading

Only to reproduce historical behaviour β€” investigating how a merge from three years ago produced its result, for example. For new merges the newer strategy is better in every dimension that has been measured, and the differences in outcome are limited to cases where its handling is more correct rather than merely different.

What about the resolve and octopus strategies? Jump to heading

resolve handles a single merge base and is a curiosity now. octopus merges more than two heads at once and is genuinely useful for merging several independent topic branches in one commit, though the result is hard to bisect through and it refuses to run if any pair conflicts.

Does the strategy affect rebase and cherry-pick? Jump to heading

Yes β€” both replay commits using the merge machinery, so the same strategy and the same rename detection apply. A rebase that produces surprising results across a directory rename is usually the same phenomenon described in Step 3.