Great work!

XP to next level

BugEater
EN

squash and fixup: Collapsing Noise Commits

Learning Objectives

By the end of this lesson you will be able to:

  • Collapse several work-in-progress commits into one meaningful commit
  • Choose correctly between squash and fixup
  • Explain which commit absorbs which, and in which direction
  • Use git commit --fixup to mark a commit for automatic collapsing

The Branch Everyone Recognises

git log --oneline main..HEAD
7b2e551 fix typo
4c1f8ab actually fix it
9d3e072 fix
1a77c60 add refund boundary tests

Four commits, one idea. The last three are not decisions, they are keystrokes. A reviewer reading them learns nothing, and a future git bisect gets three commits where two of them do not build.

What you want is one commit that says: add refund boundary tests.

Squashing Them

git rebase -i main
pick 1a77c60 add refund boundary tests
squash 9d3e072 fix
squash 4c1f8ab actually fix it
squash 7b2e551 fix typo

The rule in one sentence: squash merges this commit into the line above it.

Above is older. So the three squash lines all fold into 1a77c60, and you end up with one commit containing all four sets of changes.

Save and close. Git combines them and opens an editor with all four messages so you can write the real one:

# This is a combination of 4 commits.
add refund boundary tests
fix
actually fix it
fix typo

Delete the noise, write the message you actually want, save:

test: add boundary cases for zero and negative refunds

Covers the three amounts the API rejects and the two it rounds.

Result:

git log --oneline main..HEAD
d4a91e0 test: add boundary cases for zero and negative refunds

One commit. One idea. Reviewable.

fixup: Same Thing, Without the Message Prompt

fixup does exactly what squash does, except it throws the squashed commit's message away and does not open an editor.

pick 1a77c60 add refund boundary tests
fixup 9d3e072 fix
fixup 4c1f8ab actually fix it
fixup 7b2e551 fix typo

The result is the same single commit, keeping only add refund boundary tests as its message, with no editor step at all.

Which to use:

Use it when
squash The messages contain something worth keeping, or you want to write a new combined message
fixup The messages are fix, wip, oops — pure noise

In practice fixup is the one you reach for most, because most noise commits are named exactly like noise commits.

The Direction Trap

The commit at the top of a squash group must be a pick. Git has to have something to squash into.

squash 1a77c60 add refund boundary tests     ← error: cannot squash the first commit
pick   9d3e072 fix
error: cannot 'squash' without a previous commit

If you meant to keep the newest message rather than the oldest, do not flip the commands — reorder the lines so the commit whose message you want is at the top of the group, then squash the rest into it. This is why interactive rebase lets you reorder at all.

git commit --fixup: Marking as You Go

There is a way to avoid ever writing fixup in the editor. When you are fixing something in a commit that is already on your branch:

git add tests/payments/refund.spec.ts
git commit --fixup 1a77c60

That creates a commit whose message is fixup! add refund boundary tests. It sits at the top of the branch, going nowhere, clearly labelled.

Then, when the branch is ready:

git rebase -i --autosquash main

--autosquash reads those labels, moves each fixup! commit up next to the commit it names, and pre-fills the command as fixup. The todo list arrives already correct — you check it and save.

Make it permanent:

git config --global rebase.autosquash true

Pro Tip: git commit --fixup <sha> while you work, then a single git rebase -i main before the PR, is the whole professional workflow. You never have to remember which commit a fix belonged to, because you recorded it at the moment you knew.

What This Buys You Later

A cleanly squashed branch is not cosmetics. In Module 6, git bisect steps through history one commit at a time, testing each. Commits that do not build are commits bisect has to skip, and a wip commit that only compiles once the next commit lands is exactly that. Squashing them means every commit in main is testable — which is what makes automated bisect possible at all.

Key Takeaways

  • squash and fixup both merge a commit into the line above it — above means older
  • squash combines the messages and opens an editor; fixup discards the message silently
  • The first line of any squash group must be pick
  • To keep a different message, reorder the lines rather than changing the commands
  • git commit --fixup <sha> labels a fix at the moment you make it
  • git rebase -i --autosquash then arranges the todo list for you automatically
  • Squashed, buildable commits are what makes git bisect reliable later

Quiz

In the todo list, which commit does squash merge a line into?

What is the difference between squash and fixup?

Your todo list starts with squash 1a77c60 …. What does Git say?

You fix a bug belonging to commit 1a77c60, which is already on your branch. What is the tidiest way to record it?