Git, CI/CD & GitOpsIX · MergingMerging
Merge strategies — recursive, resolve, octopus, ours, subtree
What you'll learn
- List the five built-in merge strategies and the topology each is suited to
- Explain why recursive is the default for two-parent merges and where it differs from resolve
- Use --strategy-option to apply patience, diff-algorithm, or whitespace-tolerance options
- Recognise when octopus is appropriate (many-branch release merges) and when it refuses (conflict)
- Choose between ours, subtree, and recursive for the common infrastructure-repository cases
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
The merge strategy is the algorithm that turns three tree
inputs into a merged tree. Git ships with five built-in
strategies, each suited to a different topology and a different
operational intent. Choosing a strategy is rarely required —
recursive is the right default for almost every two-parent
merge — but the operational cases where the other strategies
matter (octopus for release branches, subtree for sub-projects,
ours for intentional subsumption) are common enough in
infrastructure repositories that the right strategy needs to
be a deliberate choice, not a forgotten flag.
The five strategies
The five strategies and their operational purpose:
# 1. recursive — default for two-parent merges; handles criss-cross
git merge feature/iam-rotation
# Merge made by the 'recursive' strategy.
# 2. resolve — historical default; does not handle criss-cross
git merge --strategy=resolve feature/iam-rotation
# Merge made by the 'resolve' strategy.
# 3. octopus — many-branch merge; refuses if any path conflicts
git merge --strategy=octopus branch1 branch2 branch3
# Merge made by the 'octopus' strategy.
# 4. ours — declare current branch supersedes other; discard other side's changes
git merge --strategy=ours feature/deprecated
# Merge made by the 'ours' strategy.
# 5. subtree — merge a project that lives at a different root path
git merge --strategy=subtree --prefix=vendor/$NAME $VENDOR_BRANCH
# Merge made by the 'subtree' strategy.
The strategy can be set per-command with --strategy (or the
short form -s), or configured at the repository level with
merge.$NAME.driver for custom strategies, or at the path
level with .gitattributes for path-specific drivers.
flowchart LR
A["git merge"] --> B{Strategy?}
B -->|"default (recursive)"| C["two-parent merge\nrecursive"]
B -->|"--strategy=octopus"| D["many-branch merge\noctopus"]
B -->|"--strategy=ours"| E["supersede branch\nours"]
B -->|"--strategy=subtree"| F["sub-project merge\nsubtree"]
B -->|"--strategy=resolve"| G["legacy two-parent\nresolve"]
The decision tree is shallow: most merges use the default; octopus and ours are explicit choices; subtree is an integration pattern (vendor branches); resolve is a legacy choice that rarely wins.
Recursive — the default
recursive is the strategy that handles every two-parent
merge case correctly, including the criss-cross case where
multiple merge bases exist. It is described in detail in
IX-02; the operational summary is:
# Default: no flag needed
git merge feature/iam-rotation
# Merge made by the 'recursive' strategy.
# Explicit, for clarity in scripts
git merge --strategy=recursive feature/iam-rotation
# Merge made by the 'recursive' strategy.
The strategy finds all merge bases, recursively merges them into a virtual base, and then runs the per-path three-way merge against the virtual base. This is what makes recursive safe for repositories with criss-crossed history — release branches that have been merged into each other over the lifecycle of a release.
The --strategy-option flag modifies the strategy’s
behaviour. The most commonly useful options for infrastructure
code:
# patience: prefer fewer-but-larger diff hunks; better for
# files that have been heavily refactored
git merge --strategy-option=patience feature/iam-rotation
# diff-algorithm=histogram: variant of patience; faster
git merge --strategy-option=diff-algorithm=histogram feature/iam-rotation
# ignore-all-space: tolerate whitespace-only differences;
# useful when one side has been auto-formatted
git merge --strategy-option=ignore-all-space feature/iam-rotation
# renormalize: re-detect CRLF/LF on each blob; useful when
# branches have different line-ending settings
git merge --strategy=recursive --strategy-option=renormalize feature/x
The patience option is particularly valuable for YAML
files (Kubernetes manifests, Ansible playbooks, GitHub
Actions workflows) where reformatting can produce long
chains of small diffs that confuse the default Myers
diff algorithm. Switching to patience often produces a
smaller, more meaningful conflict set.
Resolve — the legacy strategy
resolve was the default strategy before Git learned the
recursive strategy in version 1.5.6 (2007). It performs the
three-way merge using a single merge base, choosing one
arbitrarily if multiple bases exist. For a simple forked
graph (one merge base), resolve and recursive produce
identical results. For a criss-crossed graph (multiple
bases), resolve can pick a non-representative base and
miss conflicts that recursive would catch.
git merge --strategy=resolve feature/iam-rotation
# Merge made by the 'resolve' strategy.
There is essentially no operational reason to choose resolve
over recursive in modern Git. The only argument for
resolve is “I want the older, simpler behaviour and my
graph is guaranteed not to criss-cross”. That guarantee is
hard to make for any repository with release branches. The
default is right; reach for resolve only when debugging
a difference between recursive and resolve behaviour, which
is itself a sign that the history has unexpected structure.
Octopus — many-branch merges
octopus is the strategy for merging more than two branches
in a single commit. It applies the three-way merge
sequentially across all branches and refuses to proceed if
any path conflicts:
git merge --strategy=octopus branch1 branch2 branch3
# Merge made by the 'octopus' strategy.
# terraform/a.tf | 2 +-
# terraform/b.tf | 4 ++--
# terraform/c.tf | 6 +++---
The resulting merge commit has more than two parents (one per branch being merged in, plus the original HEAD). The graph encodes the topology of a many-branch convergence in a single commit.
The cases where octopus is the right choice:
- Release merges. A release branch landing several feature branches at once. The octopus commit is the unit of release, and the audit trail “what landed in release/2026-q3?” is a single commit with N parents.
- Bulk dep updates. Several renovate/dependabot PRs landing together. An octopus merge produces one commit for the bulk update rather than N fast-forwards.
- Stable topic bundles. A stable topic branch (e.g.
stable/lts) that is supposed to receive a curated set of fixes; the octopus commit records the curation.
The cases where octopus is the wrong choice:
- The branches touch the same paths. Octopus refuses to proceed if any path conflicts. The refusal is the intended behaviour — octopus is for clean, non-overlapping convergences. If your branches overlap, run them as separate merges.
- Audit by branch lifecycle is required. An octopus
commit has N parents, and
git log --first-parentonly shows the octopus commit itself; the individual feature branches are reachable but not on the first-parent chain. For “show me what landed”,--first-parentis not enough; the engineer must walk all parents.
Ours — supersede without taking
ours is the strategy that declares the current branch
supersedes the merged-in branch without taking any of the
merged-in branch’s changes. The result is a merge commit
whose tree is identical to the current branch’s tree, with
the merged-in branch’s tip as the second parent.
git merge --strategy=ours feature/deprecated
# Merge made by the 'ours' strategy.
# (no file changes)
The commit is structurally a merge commit — it has two parents — but the tree matches the current branch. The operational purpose: record “we acknowledged this branch and chose not to take its changes” in the graph without discarding the branch.
The cases where ours is the right choice:
- Deprecation. A branch whose work has been
superseded by another approach should be merged with
oursto record the supersession in the graph. The branch tip is reachable from the merge commit, so the history is preserved; the working tree of the trunk does not change. - Policy gates. A branch that failed a policy check
(security review, license check) but that the team
wants to record as “considered, rejected”. The
oursmerge is the record of the consideration. - Topic branch cleanups. A batch of stale feature branches that the team wants to mark as “absorbed” or “abandoned” without losing their history.
The warning: ours is dangerous if the engineer’s intent
is “merge in the other branch’s changes” — the strategy
discards the other branch’s work. Double-check the flag
and the intent before using it.
Subtree — sub-project integration
subtree is the strategy for merging a project that lives
at a different root path. The canonical case is a vendor
branch: a third-party project maintained in its own branch
that the team wants to integrate at a sub-directory of the
main repository.
# Initial merge: bring the vendor project into a sub-directory
git merge --strategy=subtree --prefix=vendor/lib $VENDOR_BRANCH
# Merge made by the 'subtree' strategy.
# vendor/lib/file.c | 100 +++++++++++++++
# 1 file changed, 100 insertions(+)
# Subsequent merges: bring in upstream changes to the same path
git merge --strategy=subtree --prefix=vendor/lib $VENDOR_BRANCH
# Merge made by the 'subtree' strategy.
The strategy works by detecting the prefix relationship between the merged-in branch’s root and the target path, and adjusting the three-way merge accordingly. The first merge establishes the prefix; subsequent merges use the same prefix to align the trees.
The cases where subtree is the right choice:
- Vendor branches. The team tracks a third-party
project (a vendored library, an upstream Terraform
module) in its own branch and periodically merges it
into the main repository at a sub-directory.
subtreeis the cleanest way to do this without thegit submodulemachinery. - Sub-project integration. A repository that has graduated from being a sub-directory of a larger project to being its own project, where the team wants to keep an integration path available for historical merges.
- Split repositories. A repository that was split out of a larger repository, where the team wants to pull specific paths from the larger repository into the split.
Production discipline
Three rules for merge strategies in a production-grade workflow:
- Use the default unless you have a specific reason not to. The recursive strategy is the right choice for every two-parent merge in a modern Git workflow. Override only for octopus (many-branch), ours (supersede), or subtree (vendor).
- Configure YAML-friendly options at the repo level.
merge.conflictStyle = diff3andmerge.algorithm = patiencein.git/configmake every YAML merge produce better conflict markers and better diff hunks without per-command flags. - Document octopus merges in the commit message. An
octopus commit’s parents are the branches it absorbed;
the commit message should name them. The default
message from
git merge --strategy=octopusdoes this, but a custom-mshould also list the branches.
Cross-course references
- Linux for Production Sysadmins - Parts XII (RepoSecurity)
covers package-source integration;
subtreeis the Git-level analogue of integrating upstream sources at a non-root path. - Ansible for Production Sysadmins - Part XXXVII (RepoArch) covers role collection; octopus merges are the natural fit for “absorb N role updates into a release branch”.
- Terraform for Production Sysadmins - Parts IX-XII (State)
cover Terraform module sources;
subtreeis the Git-level analogue of vendoring a Terraform module at a sub-path.
Quiz
Knowledge check · 4 questions
Q1. Which merge strategy is the right choice for declaring that a branch has been superseded without taking any of its changes into the current branch?
Q2. The `octopus` strategy refuses to proceed if any path in the merged-in branches conflicts.
Q3. Name the `git merge` flag that applies a strategy option to the recursive strategy, and give one example of a useful option for YAML-heavy repositories.
Q4. Recommend a merge strategy for a release-branching workflow that absorbs three feature branches at once.
Your team maintains a `release/2026-q3` branch. Three feature branches — `feature/iam-rotation`, `feature/network-fixes`, `feature/observability-tweaks` — have been reviewed and are ready to land together. The branches touch disjoint paths (no overlapping files), and the team wants a single commit on `release/2026-q3` that records the absorption of all three.
Passing score: 75%. Answers are checked in this browser.