When I want to check out a GitHub pull request of my colleagues on my local machine, I usually have to open my browser, go to the pull request page on GitHub, copy the branch name, do a git fetch
in my terminal, followed by a git checkout
, pasting the branch name. Annoyed by this context switch between terminal and browser and all that mouse movement, I devised a way to select the pull request from a list in the terminal, with a checkout when I hit Enter:
hub pr list | fzf | egrep -o '[[:digit:]]+' | head -n 1 | xargs hub pr checkout
In the rest of the article I’ll explain the parts of the command line. At the end explain how to create a short command so I don’t have to type the whole thing every time I want to check out a pull request.
Prerequisites / System requirements: I already have the command line programs hub
and fzf
installed on my system.
hub
is a command line tool for interacting with GitHub. hub pr list
uses the GitHub API to download the pull requests for the current repository and print the issue IDs and descriptions of all pull requests.
I don’t want the list of pull requests printed on my screen, although that would already avoid having to switch to my browser window. Instead, I want to select from the list. To achieve that, I use the pipe operator of the shell to send the the output of hub pr list
as the input of the program fzf
.
fzf
is a “fuzzy finder” for the command line. It displays its input text as a selectable list. If I press Enter, it outputs the text of the line that is currently under the selection cursor. I’ve been using fzf
as a tool to search through my shell history when pressing Ctrl-R.
The output of hub pr list
is in the format
#1235 Pull Request Title
#1234
is the numeric issue id, the rest of the line is the title of the pull request.
The final command - hub pr checkout
- needs the numeric issue id and will fail if it receives the whole line I selected. That’s why I use another pipe operator to send the output of fzf
to the command egrep -o '[[:digit:]]+'
, which extracts the numeric issue id with a regular expression that matches a sequence of digits. With the -o
argument, egrep
restricts the output of the command to the matched characters instead of printing the whole line.
If the pull request description contains numbers, like “#12345 Fixed 99 bugs
”, the egrep
command will output each sequence of digits on its own line. For the final command I need to restrict the output to the first matched number, the issue id, so I pipe the output of egrep
to another command: head -n 1
head
reads the first n
lines of input and omits the rest. -n 1
restricts the output to one line, the issue id.
Finally, I use another pipe operator and the xargs
command to convert command line input to a positional command line argument. xargs
appends the issue id to the hub pr checkout
command, e.g. hub pr checkout 1235
. The checkout command fetches the branch of the pull request from GitHub and checks it out as a local branch that tracks the remote branch so I can do git push
without the --set-upstream
argument.
To avoid having to remember and type that 80-character command line I have created a function:
# Pull Request Check Out - check out pull request branches from GitHub
prco() {
hub pr list | # Download list of pull requests
fzf | # Show list as selectable menu
egrep -o '[[:digit:]]+' | # extract sequences of digits
head -n 1 | # drop all but the first sequence (PR id)
xargs hub pr checkout # convert pro ID input to argument & check out
}
If I put this snippet into my .bashrc
, I can type the new command prco
to get to my selection.
I could expand the function with error checking - if the current directory is connected to a GitHub repository, if I really selected something in the fzf
menu - but those errors will have no bad effects: If I’m not in a GitHub repository, the hub
command will print an error on stdout and provide an empty string as an input to fzf
. If I don’t select anything in fzf, it will exit with a non-null exit code, which will cancel all following commands.