Simplifying Git Squash and Rebase workflow

March 12, 2020 ~ 4 min read

Git's Squash and Rebase workflow can work with any other git workflow and at the end provides a linear commit history that you can easily look back to. I have created a few shortcuts that will help you with this workflow.

The workflow

You might have heard by now about Git Squash and Rebase workflow. If you are not using this workflow, I will suggest that you give it a try for a few months, and it will feel easier to refer back to older commits. If you are using GUI tools for git, it will be easier to see clear commit history graph. If you are using feature branch workflow or git-flow workflow or any other workflow, this workflow will complement those.

However, over the years I have seen people struggle with this workflow, mainly because they don't know how to rebase and squash commits easily. Rebasing in GUI tools is not something for the faint of heart. If you are using git cli, it is fairly easy but is still something for the power users. Also, if you are a maintainer, and merging multiple PRs, it will take a long time to rebase and merge each PR. I have created a few shell aliases to help with this workflow.

Disclaimer: You will need fzf installed in your system for some of these shortcuts to work. If you haven't heard about or using fzf, it is a command-line fuzzy finder and I would highly recommend checking it out.

Squash Commits

Nobody wants to see the ugly commit messages that doesn't tell anything about the history or detail about the changes. See an example below. There are eight commits just for upgrading the ruby version. If you are testing things out, then its okay to commit multiple times, but you should squash these commits once you are done with it. And what is that "Random Commit"?

null

Squashing commits is really easy. If you have fzf installed, you can use following command to interactively select previous commit to rebase to.

$ git log --pretty=oneline --abbrev-commit | fzf | awk '{print $1}' | xargs git rebase -i

Once you run this command, it will open up your editor where you can change your commits, change pick to fixup or f for all the commits you want to suppress. and voila, you squashed the commits in your branch. Create an alias for this command, and it will be a breeze to squash your commits.

Rebase base branch

For rebasing base branch, developers usually check out the base branch, then do git pull and then checkout the feature or bugfix branch again and then rebase with master. However, you can update your base branch from remote in one command:

$ git fetch origin master:master

You can create following function in your shell's config file and use it to update and rebase branch with just one command:

gfrb() {
    local branch remote
    branch=${1:-master}
    remote=${2:-origin}
    git fetch "$remote" "$branch":"$branch"
    git rebase "$branch"
}

And then use it like:

# Update from remote origin, master branch
$ gfrb
# Update and rebase from remote origin, develop branch
$ gfrb develop
# Update and rebase from heroku origin, develop branch
$ gfrb develop heroku

Squash merge

If you think this workflow is still way too complicated for fewer gains, then give squash merge a try. You can let your developers create commits however they want, but when merging, create a squash merge commit. This will combine all the commits from your branch and create a squash commit. This way, your git blame tools will always show all the changes created in the branch/PR and if you want to look at individual changes, you can refer back the to the PR or branch.

I would still consider squashing your commits and rebasing with master whenever possible to keep even the PR/branch history clean. And you can do squash merge to keep the base branch history clean.

Conclusion

Presentation makes a lot more difference in the usability of revision control systems. Merging base branch, creating a lot of commits for one-line changes, testing things out are just a few ways to make your branches commit history look ugly. After a few unsuccessful attempts of git blame will keep you away from looking history. This transition will either happen consciously or unconsciously, but it will creep up in your workflow. This is bad, because knowing history of changes is always better so that you won't get into an infinite cycle of introducing a bug on a bugfix and undoing the same change for the bugfix to fix the new bug.

Always keep your bugfix/feature branch's commit history clean by using squash commits and rebasing base branch. And also keep your base branch's commit history clean by doing squash merge.