Git Merge Strategies — The 'Wrong' Strategy Caused Silent Data Loss
A recursive merge with the wrong rename-detection setting silently dropped 40 renamed files.
20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.
- ✓Git 2.30+, basic understanding of branching and merging, familiarity with command line, access to a Git repository for testing
Git picks the merge strategy automatically. git merge -s recursive -X patience uses patience diff for cleaner renames. git merge -s ours discards the other branch entirely. git merge -X theirs auto-resolves all conflicts in favor of the incoming branch.
Imagine two people editing the same document. Merge strategies are the RULES for what happens when they both changed the same paragraph. 'Ours' says keep my version. 'Theirs' says keep theirs. 'Recursive' (the default) intelligently merges both. 'Ort' is like recursive but faster and better at detecting when a paragraph was renamed vs deleted and recreated.
Most developers never think about merge strategies. Git's default 'ort' strategy handles 99% of merges automatically. But when it gets the 1% wrong, the results are silent and catastrophic: files that disappear without a conflict, renamed code that's treated as deleted and recreated, and merge commits that look clean but contain wrong code. The incident that triggered this guide: a team lost 40 renamed files because the default strategy didn't detect the renames. This guide covers when to override the default, the strategy options Git provides, and how to prevent silent merge corruption.
Why Merge Strategies Matter in Production
In production Git workflows, merge strategy selection directly impacts code stability, history clarity, and rollback safety. A naive git merge can produce conflicts that break builds or introduce subtle bugs. Git offers multiple strategies—recursive, ours, theirs, and octopus—each designed for specific scenarios. Understanding when to use each prevents common failures like unintended overwrites, merge commit pollution, or broken multi-branch integrations. This article dissects each strategy with production-grade examples, focusing on real failure modes and best practices.
Recursive Strategy: The Workhorse
The recursive strategy is Git's default for non-fast-forward merges. It performs a three-way merge using the common ancestor and two branch tips. When conflicts occur, it recursively merges the common ancestors to find a better base. This strategy handles criss-cross merge histories well. In production, recursive is safe for most feature branch merges, but it can produce complex conflict markers when multiple developers modify the same files. Use git merge -s recursive explicitly only when you need to set options like patience or diff-algorithm.
Ours Strategy: Keep Your Version
The ours strategy resolves conflicts by automatically choosing the current branch's version. It does not look at the other branch's changes—it simply ignores them for conflicting files. This is useful when you want to merge a branch but discard its changes entirely, e.g., merging a hotfix branch that was already applied differently. However, it can be dangerous: if the other branch has important changes in non-conflicting files, those are still merged. Use git merge -s ours sparingly and only when you are certain the incoming changes are unwanted.
Theirs Strategy: Accept Incoming Changes
The theirs strategy is the inverse of ours: it automatically resolves conflicts by taking the other branch's version. This is useful when you want to overwrite your branch with upstream changes, e.g., syncing a long-lived feature branch with main. However, it can silently discard your branch's changes. In production, theirs is often used in automated merge scripts where you trust the incoming branch. Be cautious: it can introduce regressions if the other branch has untested changes.
Octopus Strategy: Multi-Branch Merges
The octopus strategy merges more than two branches at once. It is designed for cases where you want to combine several topic branches into a single merge commit. Octopus works best when there are no conflicts—it refuses to proceed if conflicts exist. In production, octopus is rarely used because it creates complex history and is hard to debug. It can be useful for merging multiple hotfixes that are known to be conflict-free. Use git merge -s octopus only when you are certain of no conflicts.
Recursive Options: Patience, Histogram, and More
The recursive strategy supports options that change conflict resolution behavior. patience uses a patience diff algorithm that produces cleaner diffs for code with many repeated lines. histogram is similar but often faster. diff-algorithm=patience can reduce false conflicts in refactored code. In production, these options help when recursive merges produce messy conflicts. Use git merge -s recursive -X patience to enable. However, they are not silver bullets—complex conflicts still require manual resolution.
Subtree Strategy: Merging Subprojects
The subtree strategy is a variant of recursive that merges one repository into a subdirectory of another. It is useful for managing dependencies or shared libraries within a monorepo. Unlike submodules, subtree merges keep the history of the merged project. In production, subtree merges can become complex if the upstream project changes frequently. Use git merge -s subtree with caution—it can lead to large merge commits and history bloat.
Choosing the Right Strategy for Your Workflow
Selecting a merge strategy depends on your team's workflow and history structure. For feature branches, recursive is standard. Use ours or theirs for specific override scenarios. Octopus is for rare multi-branch merges. Subtree is for vendoring. In production, prefer explicit strategy selection over defaults when the default behavior is not ideal. Document your strategy choices in your contributing guidelines. Avoid using ours or theirs in automated merges without review—they can silently introduce bugs.
Common Pitfalls and How to Avoid Them
Common pitfalls include: using ours when you meant theirs, forgetting to abort a failed octopus merge, and relying on recursive options to fix all conflicts. In production, a bad merge can cause silent data corruption. Always verify merge results with git diff and run tests. Use git merge --no-commit to review changes before committing. For automated merges, add a CI step that checks for conflict markers. Avoid merging large branches—break them into smaller, more frequent merges.
Advanced: Custom Merge Drivers
For complex scenarios, you can define custom merge drivers in .gitattributes. These allow you to specify how certain files are merged, e.g., always take one side for generated files. Custom drivers can call external scripts. In production, they are useful for lock files, compiled assets, or configuration files that should never be merged automatically. However, they add complexity and must be maintained. Use sparingly and document thoroughly.
yarn.lock to always keep the main branch version. It prevented merge conflicts but occasionally caused dependency mismatches.Testing Merge Strategies in CI/CD
In CI/CD, merge strategies can be tested by simulating merges in a temporary branch. Use git merge --no-commit to test for conflicts without creating a merge commit. If conflicts occur, the pipeline can fail early. For automated merges, prefer recursive with -X theirs for trusted branches, but always run a full test suite. In production, we use a merge validation step that checks for conflict markers and runs linting before allowing the merge.
Summary: Production Best Practices
In production, prefer recursive for most merges. Use ours or theirs only when you explicitly want to discard or accept changes. Avoid octopus unless you are certain of no conflicts. Document your strategy choices and train your team. Always review merge diffs and run tests. Use custom drivers sparingly. Remember: no strategy replaces good communication and small, frequent merges. The goal is a clean, traceable history that supports quick rollbacks.
40 Renamed Files Silently Dropped by Wrong Merge Strategy
git mv. 2. The main branch had no changes to those files. 3. Git's rename detection threshold (50% similarity by default) was not met for some files because they had significant content changes alongside the rename. 4. Git treated those files as 'deleted from old path, created at new path'. 5. The merge completed with no conflicts — both old and new paths existed in the result. 6. The build system picked up the old paths (which had stale content), not the new paths.git diff --name-status main..merge-result | grep '^A\|^D' showed both deletes and adds. 2. Re-ran the merge with explicit rename detection: git merge -s recursive -X rename-threshold=30 refactor-branch. 3. This time Git detected the renames correctly. 4. Cleaned up the duplicate files: git rm on old paths for the 40 affected files. 5. Set git config merge.renameLimit 10000 globally.- Git's rename detection has a 50% similarity threshold by default.
- Files that change significantly during a rename may fall below this threshold and be treated as delete+create.
- Use
-X rename-threshold=<N>or-X patiencefor merges with extensive renames. - Always verify post-merge
git diff --name-statusagainst expected changes.
ort strategy (Git picks it automatically)git merge -s recursive -X patience for better rename detectiongit merge -s ours — discards all changes from the other branchgit merge -X theirs — risky, use only when you're certaingit merge -s subtree for merging subprojects| File | Command / Code | Purpose |
|---|---|---|
| check-strategy.sh | git config --get merge.ff || echo "No ff config" | Why Merge Strategies Matter in Production |
| recursive-merge.sh | git init recursive-demo | Recursive Strategy |
| ours-merge.sh | git init ours-demo | Ours Strategy |
| theirs-merge.sh | git init theirs-demo | Theirs Strategy |
| octopus-merge.sh | git init octopus-demo | Octopus Strategy |
| recursive-options.sh | git init options-demo | Recursive Options |
| subtree-merge.sh | git init main-repo | Subtree Strategy |
| strategy-decision.sh | echo "Select strategy:" | Choosing the Right Strategy for Your Workflow |
| merge-review.sh | git merge --no-commit --no-ff feature | Common Pitfalls and How to Avoid Them |
| custom-driver.sh | git config merge.ours.driver "keep-ours.sh %O %A %B" | Advanced |
| ci-merge-check.yml | jobs: | Testing Merge Strategies in CI/CD |
| best-practices.sh | echo "1. Choose strategy based on scenario" | Summary |
Key takeaways
-X rename-threshold for heavy renames-X theirs/-X ours for automatic conflict resolution (with caution)Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.
That's Git. Mark it forged?
4 min read · try the examples if you haven't