---
title: "Git Merge Strategies Cheatsheet"
slug: "git-merge-strategies"
category: "cheatsheets"
tags: ["cheatsheets", "git", "merge", "rebase", "version-control", "branching"]
status: "stable"
last_updated: 2026-05-14
summary: "Git merge strategies compared: fast-forward, no-ff, squash, rebase, ours/theirs, recursive. When each produces the right history and when it destroys it."
related:
  [
    "[[coding/git]]",
    "[[tooling/github]]",
    "[[cheatsheets/git-commands]]",
    "[[cheatsheets/git-rebase]]",
    "[[cheatsheets/git-aliases]]",
    "[[tooling/claude-code-workflow]]",
    "[[cheatsheets/index|Cheatsheets]]",
  ]
---

## Overview

Git merges in three conceptually different ways: it records a merge commit (merge), replays commits on a new base (rebase), or collapses a branch into one commit (squash). Choose the strategy before merging; changing it after rewrites shared history. This card covers the invocations, the resulting graph shape, and the tradeoffs. For day-to-day command reference see [[cheatsheets/git-commands]]; for interactive rebase recipes see [[cheatsheets/git-rebase]].

## Fast-forward and no-ff

Fast-forward (default when possible) moves the branch pointer without creating a merge commit. `--no-ff` always creates a merge commit even when a fast-forward is possible.

| Command | Graph result | Use when |
|---|---|---|
| `git merge feature` | Linear; no merge commit if possible | CI/CD pipelines where linear history is required |
| `git merge --ff-only feature` | Fails if FF not possible; no commit | Enforcing "rebase before merge" policy |
| `git merge --no-ff feature` | Always creates a merge commit | Preserving the fact that a feature branch existed |
| `git merge --no-ff -m "msg" feature` | Merge commit with custom message | When the merge commit message should document intent |

Fast-forward loses the branch topology: once merged, there is no marker that those commits came from a feature branch. Use `--no-ff` when the branch boundary matters for audit or rollback.

## Squash merge

Squash collapses all commits from the feature branch into a single staged diff, which you commit manually. The source branch is not recorded as a parent.

| Command | Effect |
|---|---|
| `git merge --squash feature` | Stages all changes; no commit yet |
| `git commit -m "feat: add login"` | Creates one commit on current branch |
| `git branch -D feature` | Delete the branch; squash does not track it |

Squash produces clean, bisectable history on main at the cost of losing per-commit granularity from the branch. GitHub's "Squash and merge" button does this automatically.

After a squash merge, `git branch -d feature` fails because git does not know the branch was merged. Use `-D` to force-delete.

## Rebase

Rebase replays commits from the current branch on top of a target, rewriting their hashes. The result is a linear history as if the branch was started from the current tip of the target.

| Command | Effect |
|---|---|
| `git rebase main` | Replay current branch on top of `main` |
| `git rebase --onto new-base old-base` | Transplant a range of commits to a new base |
| `git pull --rebase` | Fetch then replay local commits on remote tip |
| `git rebase -i HEAD~n` | Interactive: squash, reorder, drop, edit commits |
| `git rebase --continue` | After resolving a conflict, continue |
| `git rebase --abort` | Abandon and restore original state |

Never rebase commits already pushed to a shared branch. Every commit hash changes; teammates diverge.

## Conflict resolution: ours and theirs

During a conflicted merge or rebase, `ours` and `theirs` refer to opposite sides depending on the command.

| Context | `ours` | `theirs` |
|---|---|---|
| `git merge feature` | Current branch (HEAD) | feature branch |
| `git rebase main` | main (the new base) | current branch being replayed |
| `git cherry-pick <ref>` | Current branch (HEAD) | the cherry-picked commit |

| Command | Effect |
|---|---|
| `git checkout --ours <file>` | Take the "ours" version of a file |
| `git checkout --theirs <file>` | Take the "theirs" version of a file |
| `git merge -X ours` | Auto-resolve all conflicts in favor of current branch |
| `git merge -X theirs` | Auto-resolve all conflicts in favor of incoming branch |
| `git merge -s ours` | Ignore all changes from the other branch entirely |

`-X ours` and `-s ours` look similar but differ: `-X` is a conflict resolution hint; `-s ours` discards the other branch completely.

## Recursive and octopus strategies

These are the `-s` (strategy) options used by git under the hood.

| Strategy | Invocation | Use case |
|---|---|---|
| `recursive` | Default for two-way merge | Standard branch merges; handles renames |
| `resolve` | `git merge -s resolve` | Older two-way; less rename-aware |
| `octopus` | Default for 3+ branches | Merging many branches at once; no conflicts allowed |
| `ours` | `git merge -s ours` | Keep current tree entirely; record the merge fact |
| `subtree` | `git merge -s subtree` | Merging into a subdirectory |

`recursive` is the default since git 0.99.9 for two-branch merges. You rarely need to specify it explicitly; it is documented here because the option appears in error messages and config.

## Common gotchas

- Rebase inside a rebase produces confusing conflicts. `git rebase --abort` and start over from a clean merge-base.
- Squash merges leave the original branch undeleted; always delete with `-D` after squash.
- `--ff-only` fails loudly rather than silently creating a merge commit; add it to your branch protection or global config.
- `git merge -s ours` discards all incoming changes silently. Use it only to close a branch without taking its content.
- After a rebase, `git push --force-with-lease` is safer than `--force`; it aborts if the remote moved since your fetch.
- Merge commits in `git bisect` are skipped automatically; squash commits are bisectable, which is why `bisect` works better on squash-merged history.

## Related

- [[coding/git]]
- [[tooling/github]]
- [[cheatsheets/git-commands]]
- [[cheatsheets/git-rebase]]
- [[cheatsheets/git-aliases]]
- [[tooling/claude-code-workflow]]
- [[cheatsheets/index|Cheatsheets]]
