Today I realised I’d been doing something the hard way for years.

The problem

You make a commit. Then you notice a typo, or a missed console.log, or a linting issue. You make another commit — “fix typo” or “oops” or the classic “remove debug statement”. Your git history now looks like this:

abc1234 Add user authentication flow
def5678 fix typo
901bcde oops forgot to remove log
fed4321 lint

This is noise. Before merging, you’d normally do an interactive rebase (git rebase -i HEAD~4) and manually mark the fixup commits as fixup against their parent. That works. It’s just tedious.

The solution: --fixup

git commit --fixup <commit-sha>

This creates a commit with the message fixup! <original message>, which signals git that this is a fixup for the named commit.

Then:

git rebase -i --autosquash HEAD~4

The --autosquash flag automatically positions and marks all fixup! commits correctly in the rebase editor. You open the editor, everything is already arranged properly, all you do is confirm with :wq.

The full workflow

# You made a commit
git add src/auth.ts
git commit -m "Add user authentication flow"

# You notice a problem
vim src/auth.ts  # fix it

# Commit it as a fixup for the previous commit
git add src/auth.ts
git commit --fixup HEAD  # HEAD if it's the last commit

# When ready to clean up before pushing
git rebase -i --autosquash HEAD~2

You can also use --squash instead of --fixup if you want to keep the commit message of the fixup (useful for adding context).

Set autosquash by default

If you always want --autosquash behaviour, set it globally:

git config --global rebase.autoSquash true

Now git rebase -i always autosquashes — you only need to override explicitly if you don’t want it.

The payoff

Clean, readable commits where each one represents a coherent unit of change. This matters for git log, git bisect, code review, and anyone who has to read the history six months from now (often you).

Small thing. Worth knowing.