The pull request story

Screenshot of a pull request that has been accepted

Using mercurial patch queues and git amend to make better pull requests.

Pull requests have become a bedrock of our team’s development process. However, a large pull request that touches many files and spans multiple commits makes for a difficult diff to review.

Reviewing a pull request is easier if it tells a story, and one great way of doing so is by organizing commits into orthogonal layers and commits that each address one concern. Reviewing the pull request a layer at a time means easier diffs to wrap your head around, and the ability to comment on the individual commits within the pull request.

The mercurial queues (mq) extension for mercurial is a powerful way to manage your work in progress before pushing it to the world. Bryan O’Sullivan’s authoritative introduction to mq in Mercurial: The Definitive Guide is a great reference. The Mozilla Developer Network highlights advanced uses of mq, such as rebasing to resolve patch conflicts.

My pull request story telling begins by thinking about potential layers (for example, front- versus back-end) and considering upfront refactorings that can make the final solution cleaner. It is then a matter of hg qnewing a patch for each refactoring or layer as I go. As I continue to test while developing, bugs in lower layers can be fixed in the most relevant patch by hg qpoping, fixing the bug, then hq pushing.

For example, here’s what my progress in implementing the ability to create a new branch via a pull request looked like:

$ hg qseries
add-boolean-field.patch
logic-to-detect-new-branch-case.patch
toggle-checkbox-on-new-branch-change.patch
show-new-branch-indicator-in-view.patch
compare-view-ajax-url-new-branches.patch
fix-get_compare_url-for-pull-requests.patch

However, fixing lower-layer bugs can sometimes mean subsequent patches in the queue no longer apply cleanly:

$ hg qpush
applying add-boolean-field.patch
patching file apps/compare/forms.py
Hunk #1 FAILED at 0
1 out of 1 hunks FAILED -- saving rejects to file apps/compare/forms.py.rej

We can clean up the failed qpush and take advantage of the fantastic diff-mode for Emacs to repair the patch:

$ hg qpop
$ find . -name '*.rej' | xargs rm
$ emacs .hg/patches/add-boolean-field.patch

Emacs’s diff-mode automatically updates the patch file’s hunk headers (that is, line offsets) when you edit the context surrounding modified lines in a patch. In this case, it’s just a matter of updating the still-fresh-in-my-mind surrounding context that changed.

Finally, using hg qfold can help you combine related patches and hg qrename --edit allows you to craft the commit messages your pull request will use (protip: create an hg qedit alias).

The great news about this kind of workflow is that it’s not just Mercurial martyrs that can benefit: git users have git rebase and git commit --amend at their fingertips to create their own pull request story.

So the next time you’re ready to press the Create pull request button, ask yourself if its story is worth telling.