Skip to content

Git Merge Strategies: Choosing the Right Tool for Every Integration Scenario

A practical guide born from years of resolving merges that went sideways because I used the wrong strategy.


The Merge Is Not Just One Operation

Early in my career, I thought git merge was a single, simple operation. Branch A merges into branch B, conflicts get resolved, done. I was wrong. Git offers multiple merge strategies, each optimized for different scenarios. Using the wrong one leads to silent data loss, unnecessary conflicts, or history that nobody can follow.

The Default: Recursive Strategy

When you run git merge feature-branch, git uses the "recursive" strategy (or "ort" in newer versions). It finds the common ancestor of both branches and performs a three-way merge:

# Standard merge — uses recursive/ort by default
git merge feature-branch

# Explicitly specify the strategy
git merge -s recursive feature-branch
git merge -s ort feature-branch  # Git 2.34+, faster

Recursive handles most cases well. It even handles "criss-cross merges" where branches have multiple common ancestors — it merges the ancestors first, then uses that virtual result as the base.

Fast-Forward: When History Is Linear

If the target branch hasn't moved since the feature branched off, git can simply move the pointer forward:

# This happens automatically when possible:
git checkout main
git merge feature-branch  # fast-forward if main hasn't changed

# Force a merge commit even when fast-forward is possible:
git merge --no-ff feature-branch

# Only allow fast-forward (fail if not possible):
git merge --ff-only feature-branch

I use --no-ff in my pull request workflow. Even when fast-forward is possible, the merge commit serves as a record: "this feature was integrated at this point." It groups the feature's commits together in the log and makes reverting the entire feature trivial.

I use --ff-only when pulling from upstream: git pull --ff-only origin main. If it fails, that means my branch has diverged and I need to decide how to handle it explicitly.

Ours Strategy: Declaring Victory Without Fighting

The "ours" strategy resolves every conflict by taking our side. The other branch's changes are discarded. This sounds useless, but it has legitimate uses:

# Merge a branch, keeping all of our content
git merge -s ours deprecated-branch

When I use this: - Marking a branch as "superseded" without losing the merge history - Closing a long-dead feature branch that was reimplemented differently - Retiring an approach while preserving the fact that we considered it

Important: -s ours (strategy) is different from -X ours (strategy option). The strategy discards everything from their side. The option only kicks in for conflicting hunks.

Strategy Options: Fine-Tuning Conflict Resolution

Within the recursive/ort strategy, options control how conflicts are handled:

# On conflict, prefer our changes
git merge -X ours feature-branch

# On conflict, prefer their changes
git merge -X theirs feature-branch

# Ignore whitespace differences when detecting conflicts
git merge -X ignore-space-change feature-branch

-X theirs is my go-to when pulling upstream changes into a branch where I want to accept whatever main has. It still merges cleanly where possible — only actual conflicts default to their version.

Octopus Merge: Integrating Multiple Branches Simultaneously

When merging more than two branches at once, git uses the octopus strategy:

# Merge three branches at once
git merge feature-a feature-b feature-c

Octopus refuses to run if there are conflicts — it only works for clean merges. I use it when integrating multiple independent features that don't overlap:

# Release branch integrating three non-conflicting features
git checkout release/2.4
git merge feature/auth feature/logging feature/metrics

This creates a single merge commit with multiple parents, which is cleaner than three sequential merges.

Subtree Merge: Integrating Another Project

When I need to pull another repository into a subdirectory (an alternative to submodules):

# Add the other project as a remote
git remote add library https://github.com/org/library.git
git fetch library

# Merge it into a subdirectory
git merge -s subtree --allow-unrelated-histories library/main

I've used subtree merges for vendoring dependencies and for combining formerly-separate repos into a monorepo while preserving all history.

My Decision Framework

After years of practice, here's how I choose:

Scenario Strategy Command
Normal feature integration recursive/ort + no-ff git merge --no-ff
Updating my branch from main rebase (not merge) git rebase main
Pulling upstream without divergence fast-forward only git pull --ff-only
Resolving conflicting branch, prefer main ort + theirs option git merge -X theirs
Retiring a dead branch ours strategy git merge -s ours
Combining multiple clean features octopus git merge feat-a feat-b
Integrating external project subtree git merge -s subtree

Merge Conflict Prevention

The best merge strategy is often "avoid conflicts in the first place":

  1. Keep branches short-lived — the longer a branch lives, the more it diverges
  2. Rebase on main frequently — daily if possible, so conflicts are small
  3. Modular architecture — teams working on different modules rarely conflict
  4. Communication — if two people touch the same file, talk before merging

Key Takeaway

git merge is not a single tool — it's a family of strategies, each designed for specific integration patterns. The default works 80% of the time, but knowing when to use --no-ff, -X theirs, -s ours, or octopus is what separates competent git users from experts. Choose deliberately, and your history tells a clear story.


Tags: git, merge, strategies, workflow, conflict-resolution

Reference