A Git command to jump from a commit SHA to the PR on GitHub
A carefully constructed commit history can be a goldmine of useful information for helping us understand our codebase. Anyone who has ever wondered “but why!?” and got the answer after running git blame
and getting a well-written commit message will know what I mean.
But sometimes a single commit message isn’t enough and we want to see the wider context for a change. In such cases I find it can be helpful to view the original pull request that introduced it. And whilst it’s possible to jump to a pull request from a commit on the GitHub website, doing so is slow and cumbersome. I wanted a quick way to jump straight to a PR from a commit SHA by running a single command. Something like git pr COMMIT-SHA
.
If that sounds useful to you, read on!
Note: the following information assumes your project uses GitHub flow to merge in changes with explicit merge commits. If you are using an implicit merge strategy or squash merges, then you’re out of luck. The various commands below also assume the project is hosted on GitHub, but it shouldn’t be too difficult to adapt them to work with other sites such as GitLab.
TL;DR
For those that just want the meat and potatoes, running the following commands will furnish your Git configuration with a handful of useful aliases, including the afore-mentioned git pr
, which given a commit SHA should open the PR in your browser. For those on MacOS the commands to create the aliases are:
git config --global alias.merge-commits '!funct() { git log --merges --reverse --oneline --ancestry-path $1..origin | grep "Merge pull request"; }; funct'
git config --global alias.pr-number '!funct() { git merge-commits $1 | head -n1 | sed -n "s/^.*Merge pull request #\\s*\\([0-9]*\\).*$/\\1/p"; }; funct'
git config --global alias.web-url '!funct() { git config remote.origin.url | sed -e"s/git@/https:\/\//" -e"s/\.git$//" | sed -E "s/(\/\/[^:]*):/\1\//"; }; funct'
git config --global alias.pr '!funct() { open "`git web-url`/pull/`git pr-number $1`" ;}; funct'
If you are on Linux you will need to replace open
with xdg-open
in the last command.
For those of you interested in the details, read on…
Step 1: Finding the merge commit
First let’s list all the merge commits that are on the commit graph between origin (i.e. the tip of the main branch) and the commit in question, oldest to newest:
` git log –merges –reverse –oneline –ancestry-path COMMIT-SHA..origin `
Let’s look at an example output for a commit in the Rails codebase:
$ git log --merges --reverse --oneline --ancestry-path a21dd9f2f82a4fb6b70efa816ec153950a74e355..origin
8d597b5435 Merge pull request #39240 from rails/dym-hack
7494f1be33 Merge pull request #39259 from kamipo/fix_type_cast_aggregation_on_association
9fa0f01e6b Merge pull request #39273 from LukasSkywalker/remove-indexer-from-rails-guides
0bed64793e Merge pull request #39274 from kamipo/aggregation_takes_attribute_types
89043b7f7f Merge pull request #39264 from kamipo/fix_type_cast_pluck
1c3e75bf09 Merge pull request #39268 from kamipo/fix_merging_multiple_left_joins
ac65e560db Merge pull request #39269 from kamipo/improve_performance_loaded_association_first
Assuming you are not squash-merging, the commit that appears first (the oldest) will more often than not be the merge commit for the pull request. I say “more often than not” because if the branch in question had another branch merged into it (i.e. someone merged the main branch back in to bring it up-to-date), then those merge commits will also appear, as in this example:
$ git log --merges --reverse --oneline --ancestry-path 638cc381b11d421f30670ea3cf9aa780d710b7bf..origin
232372bfd1 Merge branch 'master' into collection-refactor
fc4ef77d47 Merge pull request #38594 from rails/collection-refactor
931f958695 Merge pull request #38810 from kamipo/restore_compatibility_for_lookup_store
e2cf0b1d78 Merge pull request #38812 from ecbrodie/ecbrodie-patch-validations-docs
83dd0d53d6 Merge pull request #38814 from eugeneius/test_runner_trailing_slash
f41730c95a Merge pull request #38827 from schmijos/patch-1
86eac9b2b4 Merge pull request #38834 from olleolleolle/simpler-workflow-rubocop
To get around this we can pipe the output through grep
so we only see merge commits for pull requests:
` git log –merges –reverse –oneline –ancestry-path COMMIT-SHA..origin | grep “Merge pull request” `
We’ll turn this command into a Git alias so we can run it succinctly as git merge-commits COMMIT-SHA
:
git config --global alias.merge-commits '!funct() { git log --merges --reverse --oneline --ancestry-path $1..origin | grep "Merge pull request"; }; funct'
(Note: if you get an “ambiguous argument” error when you run this command in your repository, it’s probably because your local repo doesn’t have a symbolic reference set for origin/HEAD
. This normally happens when the repo was created by yourself, rather than cloned from the remote. Running git remote set-head origin -a
will set origin/HEAD
and resolve this.)
Step 2: Extracting the pull request number
The next step is to extract the PR number from the commit log we’ve just generated. First we pipe to head
to get the first line and then to sed
to extract the PR number:
` git merge-commits COMMIT-SHA | head -n1 | sed -n “s/^.Merge pull request #\s\([0-9]\).$/\1/p” `
As before, let’s turn that into a Git alias. This one we’ll call git pr-number
:
git config --global alias.pr-number '!funct() { git merge-commits $1 | head -n1 | sed -n "s/^.*Merge pull request #\\s*\\([0-9]*\\).*$/\\1/p"; }; funct'
Step 3: Constructing and opening the URL for the pull request
Now we have the PR number we are almost ready to construct the URL for the pull request. But first we need the web URL for the repository on GitHub. We can construct that from the remote URL configured in the repository, which will either look like git@github.com:rails/rails.git
or git@github.com:rails/rails
or maybe even https://github.com/rails/rails.git
. Here’s a command that will return the web address for the repo:
` git config remote.origin.url | sed -e”s/git@/https:\/\//” -e”s/.git$//” | sed -E “s/(\/\/[^:]*):/\1\//” `
Once again we’ll make this a git web-url
alias for convenience:
git config --global alias.web-url '!funct() { git config remote.origin.url | sed -e"s/git@/https:\/\//" -e"s/\.git$//" | sed -E "s/(\/\/[^:]*):/\1\//"; }; funct'
Finally, armed with our web-url
and pr-number
aliases we are ready to combine them to construct the URL for the pull request and open it in a browser! Here is the final alias git pr
for those of you on MacOS:
git config --global alias.pr '!funct() { open "`git web-url`/pull/`git pr-number $1`" ;}; funct'
Those of you on Linux will need to use xdg-open
instead of open
to open a browser:
git config --global alias.pr '!funct() { xdg-open "`git web-url`/pull/`git pr-number $1`" ;}; funct'
And now we can jump from a commit SHA straight to the PR that introduced it with one simple command.