Command Palette

Search for a command to run...

Hectal
PHASE 4Intermediate ~11 min· topic 5 of 5

Topic 4.5

Merge Commit, Squash, or Rebase: How Pull Requests Land

In one line

Hosting services offer three ways to merge a PR. Merge commit (git merge --no-ff) keeps every commit and adds a merge commit. Squash and merge (git merge --squash) turns the whole PR into one new commit on main. Rebase and merge replays each commit onto main with new hashes and no merge commit. They give the same final code but different history, and different follow-up effects on bisect, revert, and branch cleanup.

0/5 · 0%

Think of it like this

Adding a new chapter to a shared book. You can bind in all the author's drafts with a cover sheet (merge commit), retype it as one clean chapter (squash), or copy each draft page in order at the end of the book (rebase).

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Merge commit
A commit with two parents that joins a branch into another.
--no-ff
Always create a merge commit, even when a fast-forward is possible.
Squash merge
Combine all of a branch's changes into one new commit on the target branch.
Rebase merge
Replay the branch's commits onto the target one by one, without a merge commit.
Linear history
History with no merge commits, a single line of commits.
-X ours / -X theirs
Merge options that resolve conflicting hunks in favour of one side, while still merging the rest.

Step by step

01One PR, three histories

Priya's coupon PR has three commits: 'Add coupon model', 'Validate expiry', and 'Fix typo'. Here is what main looks like after each merge method.

One PR, three historiesdiagram
Rendering diagram…

02The same three, done locally

The buttons on GitHub do roughly what these commands do. Seeing them locally makes the differences concrete.

terminal
$ # 1. merge commit
git switch main && git merge --no-ff feature/coupons -m "Merge PR #142: coupons"
# 2. squash
git switch main && git merge --squash feature/coupons && git commit -m "Add coupons with expiry (#142)"
# 3. rebase then fast-forward
git switch feature/coupons && git rebase main && git switch main && git merge --ff-only feature/coupons
── expected output ──
Merge made by the 'ort' strategy.
src/coupons.js | 41 +++++++++++++++++++++++++++++++++++++++++
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requested
[main 2d3e4f5] Add coupons with expiry (#142)
1 file changed, 41 insertions(+)
Successfully rebased and updated refs/heads/feature/coupons.
Updating 0e9d8c7..8f9a0b1
Fast-forward

03How each one affects later work

The choice matters most when something goes wrong later. A bad squash commit is reverted with a plain git revert <hash>, while a merge commit needs git revert -m 1 <merge> (Topic 7.4). With rebase merging, a revert means reverting several commits. Bisect on squash-merged main finds the PR at fault but not the exact commit inside it.

choosing a defaultwhole filetext
                     merge commit     squash            rebase
history on main      all + merge      1 per PR          all, linear
undo a whole PR      revert -m 1      revert 1 commit   revert N commits
bisect precision     per commit       per PR            per commit
needs clean commits  helpful          no                yes
git branch -d works  yes              no (use -D)       no (use -D)

04Strategy options: prefer one side, carefully

Sometimes a merge has many conflicts where one side is clearly right, for example syncing a generated translations branch. -X theirs resolves only the conflicting hunks in favour of the incoming branch and merges everything else normally. Don't confuse it with -s ours, which ignores the other branch's changes completely.

terminal
$ git merge -X theirs translations/update
── expected output ──
Auto-merging locales/hi.json
Merge made by the 'ort' strategy.
locales/hi.json | 38 +++++++++++++++++++-------------------

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Continuing on a squash-merged branch

Priya's coupon PR is squash-merged. She keeps committing on the same branch and opens a second PR from it.

terminal
$ git switch feature/coupons && git rebase origin/main
── what you'll see ──
CONFLICT (content): Merge conflict in src/coupons.js
error: could not apply 4d5e6f7... Add coupon model
# the new PR also lists the three old commits again

Myth vs fact

Myth

Squash merging loses work.

Fact

All the changes are in the squashed commit. Only the separate intermediate commits are dropped from main (the PR page still shows them).

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    With squash merging, the PR title becomes the commit subject, so enforce good PR titles (for example in Conventional Commits format, Topic 7.2) with a CI check. That way main's history and release notes stay clean without anyone rebasing by hand.

Remember this

  1. 1

    Merge commit: all PR commits plus one merge commit with two parents. History shows exactly how work happened, and the PR can be reverted with one git revert -m 1.

  2. 2

    Squash and merge: one commit per PR, so main reads like a changelog. Messy 'fix typo' commits disappear, but so does the detail of individual commits.

  3. 3

    Rebase and merge: each PR commit replayed on top of main, giving linear history with all commits. Every commit should then build and pass tests.

  4. 4

    All three produce the same files. They differ in history shape, which affects git log, bisect (Topic 6.2), and reverts.

  5. 5

    After squash or rebase merging, the PR's original commits aren't on main, so git branch -d says 'not fully merged'. Delete the branch with -D once you've confirmed the PR merged.

  6. 6

    Pick one default per repository (rulesets can enforce it, Topic 8.4). Many teams use squash for small PRs. Repos with carefully crafted commits often prefer merge or rebase.

Explain it without notes

01

Compare the three PR merge methods.

02

Why does git branch -d refuse after a squash merge?

Practice

01

Merge the same branch three ways in a test repo and compare git log --graph.

Trade-offs

  • ↔

    Merge commits keep the most information but make busier history. Squash is simplest for most teams but hides commit-level detail. Rebase merging gives linear, detailed history but requires disciplined commits.

Done when you can

  • I can explain merge, squash, and rebase merges.

  • I know how each affects revert and bisect.

  • I start a fresh branch after a squash merge.