Always Squash and Rebase your Git Commits

Posted on by in Development

Using git for version control allows for powerful collaboration in tech teams. Like any tool, if misused, it can also cause some serious headaches. After working with a wide variety of team sizes and dynamics, I’ve found that the squash and rebase workflow helps make the collaboration process more efficient and a hell of a lot less painful.

What is the squash rebase workflow?

It’s simple – before you merge a feature branch back into your main branch (often master or develop), your feature branch should be squashed down to a single buildable commit, and then rebased from the up-to-date main branch. Here’s a breakdown.

Pull master branch

git pull origin master

Create bug/feature branch

git checkout -b branchName

Make changes as needed with as many commits that you need to. Make sure the final commit is buildable and all tests pass.

Get the number of commits from the start of your branch. There are a couple of ways to get this. You can simply git log and count your commits, or

git log --graph --decorate --pretty=oneline --abbrev-commit

which will show a graph of your commit log history and may be easier to visualize your commits. Sometimes you will have large enough number of commits that counting can become troublesome. In that case grab the SHA from the last commit that your branch branches from.

Squash to 1 commit.

git rebase -i HEAD~[NUMBER OF COMMITS]

OR

git rebase -i [SHA]

If you have previously pushed your code to a remote branch, you will need to force push.

git push origin branchName --force

Checkout master branch

git checkout master

Pull master branch

git pull origin master

Checkout bug/feature branch

git checkout branchName

Rebase from master

git rebase master

Handle any conflicts and make sure your code builds and all tests pass. Force push branch to remote.

git push origin branchName --force

Checkout, merge, and push into master

git checkout master

git merge branchName

git push origin master

Why should you adopt this workflow?

If you follow this process it guarantees that ALL commits in master build and pass tests. This simple fact makes debugging an issue much easier. You can use git bisect when trying to find the source of a bug. Git bisect becomes almost completely ineffective  if there are broken commits on the master branch; if you jump to a commit that isn’t clean, it’s difficult or impossible to tell if it introduced the bug.

 

The process also cleans up your git log. Have you ever seen a git log graph that looks like this?

This is incredibly hard to discern what is going on, especially when you compare it to what my process makes the history look like:

Because each commit contains all the code for a feature or bug fix, you can easily see the whole unit of work in a commit, and you don’t have to  worry about checking out the correct commit from a branch.

My favorite outcome of this workflow is that it makes handling conflicts from rebasing simple. Since you’ve squashed down to one commit, you only have to deal with those conflicts once, rather than having to work against half-baked code. It reduces the risk of losing code when dealing with the conflicts.

So what are the drawbacks?

Since we’re squashing commits and rebasing, we are literally changing the history for the repository. Some people feel that history should reflect your true history, the good the bad and the ugly. I propose a clean history is more valuable than one that is hard to understand. Not all history is lost; diehards can still see the original history in the ref log. Another small drawback is that we lose some granularity when we squash our commits. If you really want to have multiple commits for a feature, at least squash down so that each commit builds and passes tests. While this workflow can be dangerous if you don’t have an understanding of what you are doing, after minimal education on the matter, this process is extremely safe.

TLDR

When it comes to squashing and rebasing your commits, the pros significantly outweigh the cons. It speeds up your debugging process, cleans up your history, reduces the pain from merge conflicts and makes collaboration in your team much better.