A Beginner’s Guide to Squashing Commits with Git Rebase
Git is a version control system commonly used by software developers in managing ever-changing codebases. Users “commit” their changes to a local or remote repository along with a short explanatory note. Git’s usefulness lies in part to its quick and lightweight ability to branch new work off an existing project, keeping a breaking change or a new feature siloed and separate until it’s ready to be integrated into the existing project.
It’s common for a development team to delineate work onto branches for an individual developer or a specific feature. When it’s time to integrate those changes into the project’s master branch, teams can become rather fastidious with their project’s commit history, striving to keep updates concise and readable.
In that vein, I’ve found it common practice for teams to squash or condense these long commit message chains into one or a few commits before merging into the master branch. This is useful on a personal level as well. If you’re like me, you err on the safe side and make commits frequently. A side effect of this is an abundance of superfluous commit messages that do nothing more than muddle up a git log. Squashing these commits can make the log more readable and understandable, both for ourselves and others.
To display this, let’s say we have the following 10 commits on a branch titled “home_page.”
* 44d2565 (HEAD -> home_page, master) Create nav border
* 1bd999c Styling user data WIP. Add margin
* a135e04 Implement search input for user display
* 76e5811 Add padding to nav list items
* 97178bd User data display WIP
* c289adc Create div for user data
* 14a64ff Add background color to nav and body
* 83c9bbb Add list items to nav
* 60042c8 Add nav to application
* c0fa78d Initial commit
You’ll see that we have some WIP (Work in Progress) commits along with additional commits relating to the home page portion of our project. These are commits we will likely want to squash to make our git log more concise.
To interactively rebase commits, we can follow the below format and enter our command via the command line:
git rebase -i HEAD~<n>
or
git rebase -i <after-this-commit-sha1>
The ‘ -i ’ flag in the above example indicates that this will be an interactive rebase with the ‘<n>’ designating the number of commits that you wish to rebase.
In the case of our example, we would like to rebase all commits proceeding the initial commit. So we would enter:
git rebase -i HEAD~9
or
git rebase -i 60042c8
These commands will bring up the below interface with options to perform the following:
p, pick = use commit
r, reword = use commit, but edit the commit message
e, edit = use commit, but stop for amending
s, squash = use commit, but meld into previous commit
f, fixup = like “squash”, but discard this commit’s log message
x, exec = run command (the rest of the line) using shell
d, drop = remove commit
0 pick 60042c8 Add nav to application
1 pick 83c9bbb Add list items to nav
2 pick 14a64ff Add background color to nav and body
3 pick c289adc Create div for user data
4 pick 97178bd User data display WIP
5 pick 76e5811 Add padding to nav list items
6 pick a135e04 Implement search input for user display
7 pick 1bd999c Styling user data WIP. Add margin
8 pick 44d2565 Create nav border
9
10 # Rebase c0fa78d..44d2565 onto c0fa78d (9 command(s))
11 #
12 # Commands:
13 # p, pick = use commit
14 # r, reword = use commit, but edit the commit message
15 # e, edit = use commit, but stop for amending
16 # s, squash = use commit, but meld into previous commit
17 # f, fixup = like "squash", but discard this commit's log message
18 # x, exec = run command (the rest of the line) using shell
19 # d, drop = remove commit
20 #
21 # These lines can be re-ordered; they are executed from top to bottom.
22 #
23 # If you remove a line here THAT COMMIT WILL BE LOST.
24 #
25 # However, if you remove everything, the rebase will be aborted.
26 #
27 # Note that empty commits are commented out
We would like to condense the commit messages into one commit that indicates we have added a homepage to our project. For the sake of this example, I will utilize squash
as opposed to fixup
. This will preserve all of our minor commit messages under one final condensed commit.
Brief aside: What if we wanted to delete one or more of the commits in the example above? Instead of pick
, we could’ve used the keyword drop
to remove that commit from our code. Alternatively, we could also just delete the commit line to accomplish the same thing, but I prefer the explicitness of drop
.
After rebasing the branch, our refreshed git log appears as follows.
* ca22552 (HEAD -> home_page) Add home page to website
* c0fa78d Initial commit
Keep in mind, rebasing is not always advised. There are trade-offs surrounding the decision to keep or condense commit messages. The process can be prone to error and your team members may benefit from having those commit messages visible.
There may come a time when that commit message could help you troubleshoot an issue. Additionally, your team members may suffer from that message’s absence when trying to understand your work and how it’s been implemented. This can be especially painful in large projects.
Regarding commit message formatting, I particularly enjoy the article, How to Write a Git Commit Message by Chris Beams.
Additional Thoughts:
A quick Vim tip for rebasing many commits:
- “Ctrl + Shift + V” to enter Visual Block mode.
- Select the text you’d like to change and press “C”
- Type your changes (In the example above, “squash”) and hit “Esc”