Thursday, April 9, 2015

Ramblings from Git World - Squashing commits into a single commit for a PR



Often times when we are committing to our development branches off a private fork in github, it is a good idea to commit often so if something catastrophic were to happen, all the work is not lost. However, official github repository owners don't want to see all those commits clouding or sphagettizing (yes, pasta is the meal du jour at the household) the official upstream repository. It is a github best practice then to squash all your commits into a single upstream facing commit. Here are the steps to accomplish that:

Let's assume the upstream remote is labeled as official and the master branch on that remote is called master. Also, let's for the purpose of this excercise, assume that the feature development branch off your fork (fork off the official repository) is called dev_feature.

Once you've made all the commits locally that you need to and pushed it to your dev_feature branch off the origin (your fork of the official repository), and are ready to create a PR:

Run:

git log --oneline official/master..dev_feature

This should show all the commits you have made locally that are ready for a PR.

commit_hash1 First commit for dev_feature.
commit_hash2 Some more changes for dev_feature.
commit_hash3 Even some more changes for dev_feature.
....

Now our goal is squash all the commits into one so that when we merge the PR, the commit history is linear.

In order to do this run:

git rebase -i official/master

A new window will pop up showing all your commits like so:

pick commit_hash1 First commit for dev_feature
pick commit_hash2 Some more changes for dev_feature
pick commit_hash3 Even some more changes for dev_feature

# Rebase commit_hash1..commit_hash3 onto commit_hash1
#
# Commands:
#  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
# ...

Do pick on the first commit and say squash on all others. Save the changes and exit. This will pop a new window and allow you to enter a new commit message. 

# This is a combination of 3 commits.
# The first commit's message is:

First commit for dev_feature

# This is the 2nd commit message:

Some more changes for dev_feature

# This is the 3rd commit message:
Even some more changes for dev_feature

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# ...

Once this is done, if you run;

git log --oneline official/master..dev_feature

it should only show a single commit like so:

new_commit_hash

The only thing left then is to push your changes to the remote dev_feature branch on the remote origin (i.e. your fork off the official). In order to do that do:

git push --force --set-upstream origin dev_feature.

Once the origin fork has your changes, just create the PR, get code review comments, and after builds and regression passes, merge your PR.

Note the same steps hold true even if you've already created the PR off the dev_branch and are squashing your commits just prior to merging the PR upstream. Also, it is a good idea to periodically also push your local commits to the upstream fork on the origin using:

git push --set-upstream origin dev_feature.

The --force flag is required only when you are rewriting the commit history on the dev_feature branch on the remote origin.

That should do it for tonight! :)

Sifar.


p.s. Credit where credit is due.