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 git-commands; for interactive rebase recipes see 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.

CommandGraph resultUse when
git merge featureLinear; no merge commit if possibleCI/CD pipelines where linear history is required
git merge --ff-only featureFails if FF not possible; no commitEnforcing “rebase before merge” policy
git merge --no-ff featureAlways creates a merge commitPreserving the fact that a feature branch existed
git merge --no-ff -m "msg" featureMerge commit with custom messageWhen 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.

CommandEffect
git merge --squash featureStages all changes; no commit yet
git commit -m "feat: add login"Creates one commit on current branch
git branch -D featureDelete 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.

CommandEffect
git rebase mainReplay current branch on top of main
git rebase --onto new-base old-baseTransplant a range of commits to a new base
git pull --rebaseFetch then replay local commits on remote tip
git rebase -i HEAD~nInteractive: squash, reorder, drop, edit commits
git rebase --continueAfter resolving a conflict, continue
git rebase --abortAbandon 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.

Contextourstheirs
git merge featureCurrent branch (HEAD)feature branch
git rebase mainmain (the new base)current branch being replayed
git cherry-pick <ref>Current branch (HEAD)the cherry-picked commit
CommandEffect
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 oursAuto-resolve all conflicts in favor of current branch
git merge -X theirsAuto-resolve all conflicts in favor of incoming branch
git merge -s oursIgnore 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.

StrategyInvocationUse case
recursiveDefault for two-way mergeStandard branch merges; handles renames
resolvegit merge -s resolveOlder two-way; less rename-aware
octopusDefault for 3+ branchesMerging many branches at once; no conflicts allowed
oursgit merge -s oursKeep current tree entirely; record the merge fact
subtreegit merge -s subtreeMerging 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.