Git Merge Strategies: Recursive, Octopus & Custom Drivers
Git supports multiple merge strategies that handle different branch topology scenarios. The recursive strategy handles the vast majority of cases, but octopus, ours, and subtree strategies serve specialized needs. Understanding these strategies and their options helps you resolve conflicts efficiently, maintain a clean project history, and choose the right approach for your branching model. According to Git’s own documentation, the recursive strategy handles over 95% of all merges in practice, but knowing the remaining strategies is essential for maintaining complex repository structures.
Recursive Strategy
The recursive strategy is Git’s default for merging two branches. It works by finding a common ancestor and applying changes from both branches in a three-way merge. When there are multiple common ancestors (a “criss-cross merge” scenario), Git recurses to find a virtual ancestor by merging the common ancestors first — hence the name “recursive.”
git merge feature-branch
# Uses recursive strategy by defaultRecursive Strategy Options
The recursive strategy accepts several options that change conflict resolution behavior:
Ours — automatically favors the current branch’s version when conflicts occur:
git merge -Xours feature-branchThis is useful when you want to merge a branch’s structural changes but keep your version of conflicting content. For example, when merging a configuration branch that updated directory structure but should not overwrite environment-specific settings. Note that unlike the -s ours strategy, -Xours still applies non-conflicting changes from the other branch.
Theirs — favors the incoming branch’s version:
git merge -Xtheirs feature-branchUse caution with -Xtheirs. It silently overwrites your changes with the incoming branch’s version on conflicting files. This is appropriate when you are merging a vendor branch or accepting a well-reviewed feature branch where conflicts are known to be resolved in the feature branch’s favor. Never use -Xtheirs without validating the resulting diff.
Patience — uses a different diff algorithm that produces cleaner results for code with many function declarations:
git merge -Xpatience feature-branchThe patience algorithm finds the longest common subsequence with more context, which typically produces more accurate diffs for code with nested functions and braces. It is slower than the default (minimal) diff algorithm but produces fewer spurious conflicts. The patience algorithm is particularly effective when merging branches with significant refactoring.
Renormalize — applies the current .gitattributes rules before diffing, fixing whitespace and line-ending conflicts:
git merge -Xrenormalize feature-branchThis is essential when merging branches that were created before .gitattributes was configured, or when .gitattributes text encoding rules differ between branches. The renormalize option prevents false conflicts from line-ending mismatches between Windows and Unix developers.
Octopus Merge
The octopus strategy merges more than two heads simultaneously. Git automatically detects three or more branch arguments and switches to the octopus strategy:
git merge feature-a feature-b feature-cOctopus merges fail if there are any conflicts — Git does not support resolving conflicts in octopus merges. This makes the octopus strategy suitable only for integrating branches that are guaranteed not to conflict (e.g., topic branches that modify independent files or have already been conflict-resolved).
When to Use Octopus
The octopus strategy is useful for:
- Merging multiple small, independent topic branches into a release branch
- Synchronizing a long-lived feature branch with several other completed features
- Creating a single merge commit that documents the integration of multiple workstreams
If any branch has conflicts, fall back to sequential merges: git merge feature-a && git merge feature-b && git merge feature-c. Sequentially merging leaves a cleaner audit trail when conflicts must be resolved.
Ours Strategy
The -s ours merge strategy completely ignores changes from the other branches. The result records a merge commit, but the tree is identical to the current branch:
git merge -s ours feature-branchThis is useful when:
- You want to record that a branch was integrated but discard its content (e.g., the feature branch’s commits were already cherry-picked)
- You are declaring a branch as “superseded” without incorporating its changes
- You need to create a merge commit to satisfy a workflow requirement without actually merging content
Do not confuse -s ours (the strategy) with -Xours (the recursive option). -Xours only resolves conflicts by favoring the current branch; non-conflicting changes from the other branch are still applied. -s ours discards all changes from the other branch entirely.
Subtree Strategy
The subtree strategy is designed for merging external projects into a subdirectory:
git merge -s subtree external-project/mainGit automatically detects the common subtree prefix and adjusts paths accordingly. This is useful for maintaining forked dependencies in a monorepo, where an external library is kept in a subdirectory and periodically merged with upstream changes.
Subtree Workflow Example
# Add an external project as a remote
git remote add -f jquery https://github.com/jquery/jquery.git
# Merge into a subdirectory using subtree strategy
git merge -s subtree --no-commit jquery/mainThe -s subtree option automatically finds the correct paths. If automatic path detection fails, use --subtree-prefix:
git merge -s subtree -Xsubtree=vendor/jquery jquery/mainThe subtree strategy is distinct from the git subtree command. The strategy handles the merge algorithm, while the command provides a higher-level interface for managing subtree-based dependencies.
Custom Merge Drivers
For complex conflict resolution scenarios, custom merge drivers can invoke external tools:
# .gitconfig
[merge "custom"]
name = Custom merge driver
driver = custom-merge %O %A %B %L %PDefine per-path merge drivers in .gitattributes:
*.json merge=custom
*.lock merge=customCustom drivers are used for file formats where the standard three-way merge produces poor results — JSON, XML, lock files, or binary formats with special merge semantics. For example, a custom JSON merge driver could intelligently merge JSON objects by key rather than by line, producing correctly formatted results that line-based merging would break.
Selecting the Right Strategy
| Strategy | Use Case | Conflicts? |
|---|---|---|
| Recursive | Standard two-way merges — 95% of all merges | Handled with options |
| Octopus | Merging multiple non-conflicting branches | Fails if any conflict |
| Ours | Recording branch integration without changes | Ignores all changes |
| Subtree | Merging external repos into subdirectories | Standard handling |
Merge Conflict Resolution Strategies
When merges cannot avoid conflicts, a systematic approach to resolution minimizes errors and maintains code quality.
Understanding Conflict Markers
Git marks conflicts in files with special markers:
<<<<<<< HEAD (current branch)
console.log("Version A");
=======
console.log("Version B");
>>>>>>> feature-branch (incoming changes)The section between <<<<<<< and ======= is the current branch’s version. The section between ======= and >>>>>>> is the incoming branch’s version. Resolving requires choosing one, combining both, or writing new code.
Resolving by Conflict Type
Same line changed — both branches modified the same line differently. Evaluate which change is correct. If both are valid, determine if both changes are needed. This is the most common and often most challenging conflict type.
One deleted, one modified — one branch deleted a block that another modified. If the deletion was intentional (removed feature, refactored out), accept deletion. If the modification is the correct evolution, keep it and ensure the deletion was not a mistake.
Adjacent changes — changes to neighboring lines that do not conflict syntactically. Usually safe to keep both. Verify the combined result compiles and passes tests.
Post-Resolution Validation
After resolving all conflicts:
# Verify the merge builds and passes tests
make build && make test
# Check for accidentally included conflict markers
grep -r "^<<<<<<< \|^=======$\|^>>>>>>> " --include="*.js" --include="*.py" --include="*.ts" .
# Review the merge commit diff to ensure nothing was lost
git diff --cachedA common mistake after conflict resolution is forgetting to remove all conflict markers. The grep command above catches any remaining markers. CI pipelines should also check for conflict markers as a validation step.
LLM-Assisted Conflict Resolution
Modern development tools increasingly use AI to assist with merge conflict resolution. GitHub’s merge conflict editor, GitLab’s Web IDE, and tools like GitClear use machine learning to suggest conflict resolutions based on understanding the intent of both changes. While these tools are helpful, always verify the resolved output compiles and passes tests — automated tools can produce syntactically correct but semantically wrong results.
FAQ
Why does Git sometimes create merge conflicts on files I did not modify?
Conflicts can occur from whitespace differences (tabs vs spaces, line endings), text encoding changes, or .gitattributes renormalization. Use git merge -Xrenormalize to normalize line endings before merging. Configure .gitattributes with * text=auto to prevent these issues in future branches.
How do I prevent octopus merge from failing?
Ensure all branches being merged have no conflicts by testing pairwise merges first. Run git merge --no-commit --no-ff feature-a && git merge --no-commit --no-ff feature-b to check for conflicts without committing. If any pair conflicts, use sequential merges instead of octopus.
Can I change the default merge strategy?
Yes, configure it per repository: git config merge.default subtree. However, Git’s auto-detection of strategies works well in practice. Manually overriding is rarely necessary and can cause unexpected results. The recursive strategy is the default for good reason — it handles the widest range of scenarios.
What merge strategy does git pull use?
git pull uses the recursive strategy by default (merging the fetched branch into the current branch). For rebase-based pull, use git pull --rebase which applies commits on top of the fetched branch instead of merging. You can also configure git config pull.rebase true to make rebase the default.
Should I use merge commits or rebase for feature branches?
Use merge commits when the feature branch has been shared with other developers (preserving collaboration history). Use rebase when the branch is local-only and you want a linear history. The squash merge option compresses all feature commits into one, providing clean history at the cost of losing granular commit context. There is no universally correct answer — it depends on your team’s workflow preferences.
Conclusion
Git’s merge strategies provide flexibility for any workflow. The recursive strategy handles most cases with options for conflict resolution preferences. Octopus merges integrate multiple branches simultaneously but require conflict-free branches. The ours strategy records integration without changes. Subtree merges manage external dependencies in subdirectories. Understanding these strategies expands your Git toolkit for specialized merge scenarios.
For related Git topics, see Git collaboration workflows and Git branching strategies.