What are the different merge strategies in Git and how do you choose between fast-forward, no-fast-forward, squash, and recursive merges?
Quick Answer
A fast-forward merge moves the branch pointer when no divergence exists, creating no merge commit. --no-ff forces a merge commit to preserve branch history. Squash merge condenses all feature commits into one. The recursive/ort strategy handles divergent branches with rename detection. Choose based on whether you need traceability, linear history, or audit-friendly boundaries.
Detailed Answer
Think of merge strategies like different ways to incorporate a side road back into a highway. A fast-forward merge means the highway had no new traffic while the side road was being built, so you simply extend the highway pavement to include the new section with no junction needed. A no-fast-forward merge always builds a junction (merge commit) even when it is not strictly necessary, so you can see where the side road joined. A squash merge bulldozes the side road and rebuilds its content as a single new stretch of highway, erasing any record of the construction phases.
A fast-forward merge is possible when the target branch has no new commits since the feature branch diverged. Git simply moves the branch pointer forward to the feature branch tip, producing a linear history with no merge commit. This is the default behavior when possible. The --no-ff flag forces Git to create a merge commit even when fast-forward is possible, which preserves the branching topology and makes it easy to identify feature boundaries in the log. Squash merge (--squash) takes all commits from the feature branch, combines their changes into a single changeset in the staging area, and lets you commit them as one new commit on the target branch. The feature branch commits are not parents of the new commit, so the branch relationship is not recorded in the DAG.
Internally, the ort strategy (default since Git 2.34, replacing recursive) handles true divergent merges where both branches have new commits. It finds the merge base using the commit graph, then performs file-level three-way merges with sophisticated rename detection. When both branches modify the same file differently, it creates conflict markers. Strategy options like -X ours, -X theirs, -X patience, and -X rename-threshold tune the behavior. The octopus strategy handles merging three or more branches simultaneously but aborts on any conflict. The subtree strategy adjusts paths when merging a project that lives in a subdirectory. The choice of merge method is often enforced at the repository level: GitHub's merge button offers merge commit, squash, or rebase options per repository.
In production team workflows, the merge strategy choice depends on your traceability and history readability needs. Teams practicing trunk-based development often enforce squash merges so that main has one commit per feature/PR, keeping the history scannable. The tradeoff is losing individual commit granularity, which can make bisect less precise. Teams that value detailed history use --no-ff merges, where each merge commit acts as a bookmark for 'feature X was integrated here.' Many organizations standardize by disabling certain merge methods in GitHub settings: allowing only squash-and-merge keeps main perfectly linear, while allowing only merge commits preserves full topology. Large open-source projects like the Linux kernel use merge commits extensively because each merge represents a subsystem maintainer accepting a patch series.
A critical gotcha: squash merge does not record the feature branch as a parent, so Git does not know the branch has been merged. Running git branch --merged after a squash merge will not list the feature branch, and you must delete it manually. This can cause confusion if someone tries to merge the same branch again. Another trap: fast-forward merges make it impossible to revert a multi-commit feature as a single unit since there is no merge commit to revert. With --no-ff, you can revert the entire feature with git revert -m 1 <merge-commit>. Also, the recursive strategy can produce different results than ort in edge cases involving criss-cross merges, so be aware of which Git version your team uses.