You made 3 clean commits, then realized you forgot something in the second one. You don’t want a “oops, forgot this” commit polluting your history. Git has a built-in mechanism for this: fixup commits with autosquash. Here’s how the pieces fit together.

The Problem

A clean Git history is documentation. Each commit should tell a story about why a change was made. But real work is messy—you forget things, catch issues after committing, and end up with noise commits that obscure the narrative. You need a way to retroactively fix a past commit without rewriting history by hand.

What I Learned

The setup

Imagine you’re building a simple website:

# Commit 1: create the HTML
git commit -m "Add index.html page"
 
# Commit 2: add styles
git commit -m "Add styles in style.css"
 
# Commit 3: add interactions
git commit -m "Add script.js with interactions"

Now you realize you forgot to style a button in the CSS (commit 2). You fix it and create a fixup commit:

git add style.css
git commit --fixup <hash-of-commit-2>

This creates a commit with an automatic message:

fixup! Add styles in style.css

How the matching works

This is the critical piece: autosquash matches commits by exact message. The text after fixup! must be identical to the original commit message:

Original commit:  "Add styles in style.css"
Fixup commit:     "fixup! Add styles in style.css"
                   ^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
                   prefix   IDENTICAL to the original

If the message doesn’t match exactly—one different letter, an extra space—autosquash won’t recognize the pair and nothing gets merged.

That’s why --fixup <hash> is the safe approach. Git copies the original message for you:

# DANGEROUS — any typo breaks autosquash
git commit -m "fixup! Add styles in style.css"
 
# SAFE — Git copies the exact message from the referenced commit
git commit --fixup <hash-of-commit-2>

The autosquash magic

Your history now looks like this:

1. Add index.html page
2. Add styles in style.css                ← original commit
3. Add script.js with interactions
4. fixup! Add styles in style.css         ← temporary fix

Run the rebase:

git rebase -i --autosquash HEAD~4

Git compares messages, finds the matching pair, and automatically reorders:

pick  abc1234  Add index.html page
pick  def5678  Add styles in style.css
fixup ghi9999  fixup! Add styles in style.css   ← moved and marked!
pick  jkl0000  Add script.js with interactions

Save and close the editor. Final result—clean history:

1. Add index.html page
2. Add styles in style.css   ← now includes the button fix
3. Add script.js with interactions

The fixup commit disappeared—it was absorbed into commit 2.

Make it the default

To skip passing --autosquash every time:

git config --global rebase.autoSquash true

Now every git rebase -i does autosquash automatically.

Key Takeaways

  • --fixup creates a commit prefixed with fixup! followed by the exact original message
  • Autosquash matches by exact string: if the message after fixup! doesn’t match a commit message character by character, it won’t be squashed
  • Always use --fixup <hash> instead of typing the message manually—let Git handle the matching
  • Clean history is documentation: each commit should explain why, not be cluttered with “oops” fixes

See also: Git, Git Rebase, Code Review