Noah Meyerhans

The weblog

Reviewing GitHub Pull Requests Locally

When reviewing pull requests on GitHub, it’s often useful to have local access to the changes under review. There are a few different documented ways to accomplish this, but none have left me entirely satisfied. So, I came up with something different. Maybe it’ll work for you.

The existing methods are:

  • You can add a new git remote referencing the source of the PR, then fetch it and check out the branch containing the proposed changes. This is OK, but adding a bunch of remotes is kind of annoying.
  • All incoming PRs for a given GitHub repo are visible in the repo’s ref space, but not in a path that gets fetched in the default git configuration. With a bit of configuration in your local clone you can add the pr ref path to the list of fetched paths. This works well, but needs to be configured, and will fetch all pending PRs. This is nice, but I track some repos that get a lot of PRs, most of which don’t need my review. In cases like this, retrieving all the PRs whenever I fetch is kind of annoying.
  • You can fetch a specific pull request by its ID and store it in a given branch using a command like git fetch origin pull/ID/head:BRANCHNAME, where ID is the pull request ID and BRANCHNAME is a new local branch that will hold these changes. This is nice, but annoying to type.
  • There is a git pr command available in the git-extras project that implements the above fetch command with less typing. But that is additional software to install.

So, for my take on this, I have implemented a fairly simple git alias. It can be configured globally, in ~/.gitconfig or equivalent, and supports fetching from multiple remotes. Add the following to the ‘[aliases]’ section of your git config:

1
fpr = !sh -c 'git fetch ${2:-origin} +refs/pull/$1/head:refs/remotes/${2:-origin}/pr/$1 && git checkout pr/$1' -

The mnemonic for “fpr” is “Fetch Pull Request”. Use it to fetch a specific PR to a local branch named pr/ID. It will fetch from the “origin” remote by default, but you can override that. For example, let’s fetch pr #8287 from the OpenWRT packages repository:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ git remote -v
openwrt git@github.com:openwrt/packages.git (fetch)
openwrt git@github.com:openwrt/packages.git (push)
origin  git@github.com:nmeyerhans/packages.git (fetch)
origin  git@github.com:nmeyerhans/packages.git (push)
$ git fpr 8287 openwrt
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 5 (delta 3), reused 3 (delta 3), pack-reused 2
Unpacking objects: 100% (5/5), done.
From github.com:openwrt/packages
 * [new ref]             refs/pull/8287/head -> openwrt/pr/8287
Branch pr/8287 set up to track remote branch pr/8287 from openwrt.
Switched to a new branch 'pr/8287'
$ git status
On branch pr/8287
Your branch is up-to-date with 'openwrt/pr/8287'.
nothing to commit, working tree clean