Git Course 0%

Merge strategies

Intermediate ≈ 9 min

What you will learn

  • What each strategy leaves in main's history
  • The trade-off each one makes, and who pays it
  • Which to choose when the platform lets you choose

After this lesson you can

  • I can read a project's history and say how changes are integrated there

Why this matters

The merge button often offers three options, and the choice is usually the project's rather than yours. Knowing what each does explains why some projects have a tidy list of changes on main and others have a thicket, and it tells you how much your intermediate commits matter.

The same branch, three ways

A branch with three commits, one of which is "fix typo", alongside one unrelated commit on main.

What main's history looks like after each merge strategy The same branch of three commits, integrated three ways, shown as the output of git log --graph --oneline. With a merge commit, main shows a fork and a join: the three branch commits are visible on their own line and a merge commit brings them together. With squash and merge, main shows one new commit containing all three changes and no fork. With rebase and merge, main shows the three commits replayed in a straight line on top, with new hashes and no merge commit. A caption says all three contain the same file changes and differ only in what the history records. Merge commit * 7b783fd Merge branch 'docs/12-windows-install-steps' |\ | * a0e02c8 fix typo | * df2d30f docs: mention PowerShell | * 7ebba71 docs: add Windows section · * 60e7ee5 docs: add a support section every commit kept, and the shape records that a branch existed Squash and merge * 9c5b252 docs: add Windows steps (#12) * 60e7ee5 docs: add a support section to the README * d3b92ec chore: add CI configuration and contributing guide one commit per change, "fix typo" never reaches main Rebase and merge * f361a7a fix typo * 6da6d0c docs: mention PowerShell * d177301 docs: add Windows section * 60e7ee5 docs: add a support section to the README (new hashes, no merge commit) All three leave exactly the same files on main. They differ only in what the history remembers about how they got there.
Merge keeps everything and records the branch; squash produces one commit; rebase replays the commits in a straight line.

Merge commit

Output
*   7b783fd Merge branch 'docs/12-windows-install-steps'
|\
| * a0e02c8 fix typo
| * df2d30f docs: mention PowerShell
| * 7ebba71 docs: add Windows section
* | 60e7ee5 docs: add a support section to the README
|/
* d3b92ec chore: add CI configuration and contributing guide

Everything is preserved, including the fact that a branch existed and when it was merged. The cost is noise: three commits and a merge commit for one change, and main's history becomes hard to skim on a busy project.

Squash and merge

Output
* 9c5b252 docs: add Windows steps to installation guide (#12)
* 60e7ee5 docs: add a support section to the README
* d3b92ec chore: add CI configuration and contributing guide

One commit per merge request. main becomes a readable list of changes, and "fix typo" never reaches it. The cost is that the intermediate history is gone: if the branch contained a deliberate sequence of steps, that sequence is lost, and git bisect gets a coarser granularity.

Rebase and merge

Output
* f361a7a fix typo
* 6da6d0c docs: mention PowerShell
* d177301 docs: add Windows section
* 60e7ee5 docs: add a support section to the README

Every commit is kept, replayed on top of main with new hashes, and there is no merge commit. A straight line, which reads well and hides that a branch existed. The cost is the rewriting: the commits on main are not the commits that were reviewed, and any that were half-finished are now permanently in the main history.

Choosing

Priority Strategy
An honest record of how work happened Merge commit
A readable main, one line per change Squash
A straight history with every commit kept Rebase and merge
A change that genuinely needs its steps preserved Merge commit, or rebase

Squash is the most common default on both platforms, and it is a good default for a team where branches accumulate "wip" and "fix typo" commits, which is most teams.

Fast-forward, and why some projects insist on it

When main has not moved since your branch started, Git can integrate by simply moving the pointer forward: a fast-forward, with no merge commit at all (lesson 6.6). Projects that require a linear history enable "fast-forward only", which means your branch must be rebased onto current main before it can merge. That is what the Update branch or Rebase button on the merge request is for.

Terminal
$ git switch docs/12-windows-install-steps
$ git rebase main
$ git push --force-with-lease

Forcing here is expected and safe: it is your own branch (lesson 12.8).

Reading what a project does

Terminal
$ git log --graph --oneline -20 main
  • Merge commits with |\ shapes: merge commits are used.
  • A straight line with one commit per merge request, each ending in (#12): squash.
  • A straight line with several related commits in sequence and no merge commits: rebase and merge.

The project's settings say so too: Settings → Merge requests on GitLab, Settings → General → Pull Requests on GitHub, where the available buttons are chosen.

How to do it

Locally, the three shapes are three commands:

Terminal
$ git merge --no-ff docs/12-windows-install-steps    # always a merge commit
$ git merge --squash docs/12-windows-install-steps   # stage everything, then commit once
$ git rebase main                                    # replay, then fast-forward merge

In practice you rarely run these on main: the platform's merge button does it. They are useful for understanding what the button will do.

Common mistakes

  • Accepting the default squash message, which is often a list of "wip" commits and lives on main forever.
  • Assuming your intermediate commits will be preserved. On a squashing project they will not, so put the explanation in the merge request description.
  • Rebasing a shared branch to satisfy a linear-history rule (lesson 12.8).
  • Arguing about the strategy on your first week. Every option here is defensible; the team has chosen.
  • Forgetting to tick "delete source branch", whichever strategy is used.

Try it yourself

Goal: produce all three histories from the same starting point.

  1. In your practice repository, create a branch with three commits, one of them called "fix typo", and add one unrelated commit to main.
  2. Copy the whole repository folder twice, so you have three identical copies.
  3. In the first, git merge --no-ff <branch>. In the second, git merge --squash <branch> then commit. In the third, rebase the branch onto main and merge fast-forward.
  4. Run git log --graph --oneline -6 in each and compare.
  5. Decide which of the three you would want to read a year from now.

Expected result: three histories with identical files and visibly different shapes, matching the three outputs in this lesson.

Show solution

Copying the folder in step 2 is the trick that makes this comparison possible, and it is a useful habit in its own right: a repository is just a directory, so a copy is a complete backup that lets you try anything.

Check yourself

1. Which strategy puts exactly one commit on main per merge request?
2. Why does a project that requires a linear history ask you to rebase your branch?
3. What does a merge commit preserve that squashing does not?

Key terms

Merge Squash Rebase Fast-forward