Have you ever committed code to your Git repository, and then realized that you forgot something or there was an error in your code? Wish there was an easy way to amend your commit with the fix? What about if you've already pushed your code or made several commits?
If you run a strong test suite, and commit before running the whole test, this probably happens regularly (at least it does for me). Usually each fix you make will require a separate commit until the test suite passes. While there is nothing wrong with many small commits, sometimes it is helpful to squash many commits into one complete commit, especially when hooking project management software into your Git commits. This article discusses a technique used to squash multiple commits in branch (even master) into a single commit. Next week we will discuss another approach to this problem where you start in a branch and squash when you merge the branch into master.
You will need Git install on your machine and GitX is also useful for visualizing the Git commit tree.
How to do it...
In the simplest situation, where you have just committed and now need to amend that commit:
git add . git commit --amend
If you have 2 or more previous commits that you want to squash, then read on. The follow steps discuss how to squash multiple commits.
The following command will list the previous commit(s) (change -1 to -N, where N is the number of previous commits to see). However, it is much easier to see commit using a tool like GitX.
git log -1 --sparse
Running the previous command will output something like:
commit a8e3d75acc974a76bc7aafb43b392a69ab1bab57 Author: Matthew Snider
Date: Thu Jan 19 14:05:55 2012 -0800 [#23700817] COMMIT MESSAGE B commit 7c44b35795b03426861963ad8cc9f5bd1898d901 Author: Matthew Snider Date: Thu Jan 19 14:44:01 2012 -0800 [#23700817] COMMIT MESSAGE A
Grab the SHA-1 of your latest commit (we'll call this B, a8e3d75acc974a76bc7aafb43b392a69ab1bab57), and the SHA-1 of the previous commit that you want to squash into (we'll call this A, 7c44b35795b03426861963ad8cc9f5bd1898d901). It doesn't have to be the immediately previous commit, if you want to squash several commits together, but I usually only squash 2 commits together for sanity reasons.
Move the Git back to the lastest commit (B) that we want to squash (detached HEAD, you are in a temp mode now and won't affect master):
Reset the branch pointer to the initial commit (A), but leave the index and working tree intact:
git reset --soft
Amend the commit (A) on the initial tree using the tree from 'B':
git commit --amend
Get the SHA-1 of the current commit (we'll call this commit C):
git rev-parse HEAD
Go back to the original branch (we assumed master for this example, but this can be done just as easily on a branch as well):
git checkout master
Replay all the commits after B onto the new initial commit:
git rebase --onto
Push changes to master:
git push origin master
How it works…
The simple solution simply adjustes the previous commit tree to include your most recent changes that you added. It also gives you an opportunity to update the commit message. If you have not added new changes, then the amend command will simply allow you to change your previous commits message.
If you are changing more than the previous commit, these steps cause Git to jump to a forward commit, then move back to a previous commit, without actually updating the tree. Since the changes from the forward commit are still there, you can amend them onto the previous commit, creating a new updated commit containing the first commit and all changes until the forward commit. Once you push to master it will be as if the there was ever only 1 commit.
This technique was originally found on the Stack Overflow article git-how-to-squash-the-first-two-commits. The author follows similar steps to those above, but instead of remembering the newly created commit SHA, he tags the commit for referencing and deletes the tag at the end. The questioner was trying to squash their first and second commits ever in master, so this technique can be used to cleanup any part of your Git history. Additionally, if you replace
master in the steps above with whatever active branch you are using, then you can use this same techique on your branches.