Github’s pull requests are a terrific tool for collaborating on open-source projects. I get one or two a week on average for my projects, and I love it. The UI is very clean – you get to see exactly the changes, the full branch if you’d like, even the Travis CI integration is working checking that the branch still passes your tests.
But one thing occasionally bothers me, and that’s pull requests that come loaded with several temporary commits. I often find myself asking contributors to squash those into a single descriptive commit. Not everyone knows how to do this effectively, hence this quick writeup.
I like my Git just like the next guy, and often my own project history is full of small, temporary-stage commits with names like "more features of FOO implemented". But this isn’t acceptable when taking outside contributions. Merging two different thought streams is difficult, and it makes much more sense for each pull request to be a self-contained single commit. If there’s more than one feature coming in, separate pull requests is the way to go.
So I won’t start from the basics – the Github docs are great. I want to explain what to do when faced with a plea to "modify the pull request to have a single commit".
Some folks do it by hastily creating a new branch, porting all changes to it with a patch file and creating a separate pull request. But this is headache both for the contributor and project maintainer. There’s an easier way.
Let’s say you have two commits in your branch:
$ git log --oneline origin/master..new-feature 73bbc09 Hack some more f33b240 Hack hack
What the project maintainer wants is a single commit in the diff between your new-feature branch and the project’s master branch. What we need here is the interactive rebase feature of Git:
$ git rebase -i origin/master
This will open your editor with these contents:
pick f33b240 Hack hack pick 73bbc09 Hack some more # Rebase e54a9a9..73bbc09 onto e54a9a9 # # 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 # ...
Change the pick on the second like to squash (or just s), save the file and exit. You’ll then get another editor with a commit message to edit:
# This is a combination of 2 commits. # The first commit's message is: Hack hack # This is the 2nd commit message: Hack some more # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # ...
This is the time to create a single descriptive commit message for your pull request. Save and exit. The log will now show:
$ git log --oneline origin/master..new-feature e020524 <the first line of your modified commit message>
Single commit – great. All you have to do now is push this branch to your fork. You’ll have to use the --force flag to push here, since the rebase messed the ancestry relationship between what’s currently in the remote and your local branch.
Once you push, the Github pull request will auto-update and will only contain a single commit. Hopefully the target project has some sort of CI integration like Travis, so wait a bit more to see that everything still passes, and you’re good to go.