Squash git commits with interactive rebase

Originally written on February 19, 2015

The prevailing mantra with git, or any version control system for that matter, is "commit early, commit often." It's important to save your progress just in case you code yourself into a corner and need to back out your changes. But once your feature branch is complete, you will probably want to clean up your git branch's history of any "Oops", "WIP" or other commits that were added as you found your way toward a solution. This is where git's interactive rebase comes in.

Git's interactive rebase is a tool not to be taken lightly. Some people frown on it, as running the command effectively rewrites git's history.* But used on a feature branch, the interactive rebase can make your commits more atomic, cohesive, and clean before submitting a pull request to master.

Rewriting history within the context of the master branch is a Bad Thing™. You should never run git rebase -i on the master branch, only on features that you know you are the only one working on.

One of interactive rebase's features is ability to "squash" commits together. Squashing commits, in simple terms, is pretty much what it sounds like—merging together, or squashing, multiple commits into a single commit. That allows you to commit early and often (with mistakes, as things sometimes go), while still enjoying a clean, respectable git repository when you finally push your changes to master.

As an example, let's say you've made a few small commits, and want to make a larger commit out of them. You can use interactive rebasing to take care of this:

git rebase -i HEAD~4

pick 000001a Add files
pick 000002b Oops! Forgot to add files
pick 000003c Added external library to make changes easier
pick 000004d Large code changes that actually matter

# Commands:
# p, pick = use commit
# e, edit = use commit, but stop for ammending
# s, squash = use commit, but meld into previous commit

This command will tells git that you want to rebase the last four commits from where HEAD is to HEAD~4. From there you're taken to an editor with text in it, and an explanation of what can be done. Change your file to look something like the below:

pick 000001a Add files
sqaush 000002b Oops! Forgot to add files
squash 000003c Added external library to make changes easier
squash 000004d Large code changes that are worthy of a longer commit.

This essentially tells git to combine the all of the commits with the first in the list. Once saved, another editor pops up:

# This is a combination of 4 commits
# The first commit's message is:
Add files

# This is the 2nd commit message:
Oops! Forgot to add files
...

This file shows all of the previous commits, and allows you to modify the message of the squashed commits based on the rest of the commits involved. Edit the message as necessary, and be explicit. Save the file and quit, and your commits will be successfully squashed!

If you run into conflicts during the rebase, they're usually pretty painless. Git is solid at leading you through: it will display the conflicting files, allow you to edit the files, git add, and finally git rebase --continue to resume the squashing process.

If you take a look at your git log, you'll see the history reflects your squash. Pushing to master will display to users only the history you want them to see—the polished end result of your feature branch's commits, and not your mistakes.