There is a flag to the
git merge command:
--squash which at first looks as if it does the same thing as a rebase, but this isn’t exactly so. This post attempts to explain the difference.
Consider the following repository structure:
There is a master branch and two feature branches feat1 and feat2. The feat2 branch will introduce a merge conflict, which is why it is highlighted in red.
Say you want to bring the two features into the master branch as a single change. There are, as you would expect with Git, multiple ways of doing this.
One way to do that is by merging the two branches into the master. From the master branch you can just
git merge feat1 feat2 (yes, you can merge multiple branches in one step) You will get an error because of the merge conflict, which you should know how to fix by now, and this will give you this structure:
That’s fine if you want to be able to show that the merges have happened, but sometimes, that isn’t the case and you want to be able to show a clean history and a single change. To do that, you can use the rebase command. First by rebasing feat1 onto feat2
git checkout feat1; git rebase feat2
And then you can rebase this branch onto the master with
git rebase --interactive, where you can squash the three individual commits to a single commit, and resolve the merge conflict introduced by feat2:
And now, switch to the master branch and rebase it onto the feat1 branch (or merge it, the result will be the same as it will resolve as a fast-forward merge). This will give you a single commit with a clean history and all the merged changes from feat1 and feat2:
git merge —squash
Now to the topic of this post; using the
--squash flag with
git merge. What this does is similar to an interactive rebase where you squash all of the commits, except it doesn’t check in the change, it just updates the index (and by extension, the working directory). So, starting from the original repository structure we can get the same result by first
git merge --squash feat1 feat2 and fixing the merge conflict. This won’t affect the the checked in state of the master branch, but your index will have all the changes:
What’s the advantage of this? Well, you can get a clean history by just commiting the changes:
But, even more interestingly, you can now back out the changes from the index (which leaves them in the working directory) and add back selective changes using
git add --patch and create an entirely different set of commits.
Which should you use?
For most cases, merging would be the choice of most people. It’s simple enough, and if there aren’t many micro commits the history will be coherent. In this example it only takes three steps (1. merge 2. fix conflicts 3. commit).
If you are the sort that makes many micro commits (like me) and you then rebase them into larger chunks before merging the changes to another branch, then the rebasing method is the one you are already using, even if you squash all the changes into a single commit. I prefer this method, because you have greater flexibility with what changes you make. I also find it easier to handle merge conflicts this way. But it does take longer (1. rebase feat1 onto feat2 2. Interactive rebase 3. Amend the commit messages 4. fix conflicts 5. commit 6. rebase master onto feat1).
But, if you’re aware of what conflicts might occur and how to resolve them, or if you want to use interactive patching, then using
git merge --squash is a good choice; and it might be faster (1. merge 2. fix conflicts 3a. commit or 3b/3c … patch add the changes)