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~4Git 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 trueNow every git rebase -i does autosquash automatically.
Key Takeaways
--fixupcreates a commit prefixed withfixup!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