DEV Community

Cover image for Clean Up Messy Git History with Interactive Rebase (squash, fixup, autosquash)
Muiduzzaman Lipu for Refactorly

Posted on

Clean Up Messy Git History with Interactive Rebase (squash, fixup, autosquash)

Your pull request has commits called wip, fix typo and asdf. Before anyone reviews it, let's turn that mess into clean, meaningful commits with git rebase -i.

Every command and hash below came from a real repository I ran this in.

Video version:

Before: a messy branch

$ git log --oneline
2c27ca8 Add login tests
fc5d50d asdf
4ffd51f fix typo
3e5db2d wip
3d66ed5 Add login form
6b96e5f Initial commit
Enter fullscreen mode Exit fullscreen mode

There's one real commit for the login form, three junk commits that are really part of it, and then the tests. We want two clean commits: "Add login form" and "Add login tests".

Step 1: start the interactive rebase

$ git rebase -i HEAD~5
Enter fullscreen mode Exit fullscreen mode

Git opens your editor with one line per commit, oldest at the top:

pick 3d66ed5 Add login form
pick 3e5db2d wip
pick 4ffd51f fix typo
pick fc5d50d asdf
pick 2c27ca8 Add login tests
Enter fullscreen mode Exit fullscreen mode

pick means "keep this commit as it is".

Step 2: change the commands, save and close

pick   3d66ed5 Add login form
squash 3e5db2d wip
squash 4ffd51f fix typo
squash fc5d50d asdf
pick   2c27ca8 Add login tests
Enter fullscreen mode Exit fullscreen mode
Command What it does
squash (s) merge into the commit above, and edit the combined message
fixup (f) merge into the commit above, and discard this message
reword (r) keep the commit, change its message
drop (d) delete the commit
(move a line) reorder commits

Save and close the editor. Git replays the commits and asks you for the final message of the squashed commit.

After: clean history

Successfully rebased and updated refs/heads/main.
$ git log --oneline
65399a2 Add login tests
ae3841d Add login form
6b96e5f Initial commit
Enter fullscreen mode Exit fullscreen mode

There are two clean commits now. Notice that the hashes changed: rebase creates brand-new commits, which is why the safety rules below matter.

Pro tip: --fixup + --autosquash

Found a bug in an older commit? Don't make a "fix typo" commit and clean it up later by hand. Tell Git which commit the fix belongs to:

$ git commit -a --fixup=ae3841d
$ git log --oneline
13275fc fixup! Add login form
65399a2 Add login tests
ae3841d Add login form
Enter fullscreen mode Exit fullscreen mode

Later, rebase with --autosquash. Git moves the fix to the right place and marks it as fixup automatically:

$ git rebase -i --autosquash HEAD~3
pick ae3841d Add login form
fixup 13275fc fixup! Add login form
pick 65399a2 Add login tests
Enter fullscreen mode Exit fullscreen mode

Just save and close. You don't have to edit anything by hand. Set git config --global rebase.autoSquash true to make it the default.

Safety rules

  • Only rebase commits that aren't on a shared branch. Never rewrite main after others have pulled it.
  • Already pushed your own feature branch? Use git push --force-with-lease, not --force. It refuses to overwrite commits someone else pushed in the meantime.
  • Conflict or panic mid-rebase? git rebase --abort puts everything back.
  • Regret it after it's finished? git reflog still has your old commits.

Cheat sheet

  • git rebase -i HEAD~N: edit the last N commits
  • squash / fixup: merge into the commit above
  • reword: change a message ยท drop: delete a commit
  • git commit --fixup=<hash> + git rebase -i --autosquash
  • --force-with-lease, --abort, reflog

Does your team squash commits, or keep them all? ๐Ÿ‘‡


I post one tested coding tip every day on YouTube at Refactorly. The 30-second version of this one is here.

Top comments (0)