View GitHub Pull Requests in Magit
— Kaushal ModiHow to view GitHub Pull Request branches locally in the cloned repo, and more importantly, how to do that automatically from within Emacs.
I have a few public projects in git repos, but I don’t get that much traffic in Pull Requests (PR) Gitlab calls these Merge Requests or MRs. . So when I need to add additional commits to a PR, I would just add the PR author’s remote to my local repo, check out their PR branch, add my own commits and then merge that to my project’s main branch.
As these occurrences were few and far apart, I didn’t have a need to view the PR branches directly from within Emacs/Magit. Though, I somehow knew that each GitHub Pull Request’s HEAD got assigned a git reference. But I didn’t need to use that knowledge until today 😃.
Today, when discussing PR # 20 on Prot’s Denote package’s GitHub mirror Prot uses SourceHut as the primary git forge for his Emacs packages. But I am glad that he doesn’t mind the activity in Issues and Pull Requests on the GitHub mirror. , he wrote this comment:
Now I just need to figure out how best to incorporate your changes into the
org-id
branch so I can add the final bits. I am not too familiar with the PR workflow …
.. and that inspired this post today.
Locally creating a branch for a PR #
From the GitHub docs, the git
command to create a local branch for a
PR is this:
git fetch origin pull/ID/head:BRANCHNAME
I have the denote
package cloned from its GitHub mirror. So the
origin remote’s url is https://github.com/protesilaos/denote
.
Make sure that the remote name used in this command is pointing to a GitHub repo, and not a mirror forge like GitLab or SourceHut.
When I ran the below command, I got a new branch pr-20 pointing to the latest commit of that PR:
git fetch origin pull/20/head:pr-20
Awesome!
.. But that wasn’t good enough
.. Now I wanted more
.. I didn’t want to manually create a branch for each PR.
Getting references to all Pull Requests #
Now that I was on that quest of “I want more”, it didn’t take me long to re-discover this 7-year old nugget by Oleh Krehel. Here are the relevant bits from that post:
- Open the local repo’s
.git/config
file. - Find the
[remote "origin"]
section - Modify it by adding this one line with pull refs. This is the
same for all GitHub repositories.
[remote "origin"] url = https://github.com/USER/REPO.git fetch = +refs/heads/*:refs/remotes/origin/* fetch = +refs/pull/*/head:refs/pull/origin/*
With that edit in place, when I did l a (show
the log for all git references), followed by f a
(fetch all the remotes) in the Magit, I could see the references to
the denote
repo’s PRs!
.. But that still wasn’t good enough
.. I didn’t want to manually edit the .git/config
in each repo.
Automatically adding PR refs #
Of course, I wasn’t the first one to think of this!
Another Emacs veteran Artur Malabarba had already had this covered
also around 7 years back. Coincidentally, that post was written as a
response to that same blog post by Oleh where he shared the above
.git/config
tip.
In that post, Artur shares an Emacs Lisp function that uses Magit
functions like magit-get
, magit-get-all
and magit-git-string
to
auto-add the fetch = +refs/pull/*/head:refs/pull/origin/*
line in
the .git/config
. This magic happens after checking that the origin
remote points to a GitHub repo, and if that line doesn’t already
exist.
Here, I am lightly modifying the function shared in that post so that the origin remote name is not hard-coded The reason is that sometimes, I name the original remote as upstream and my fork as fork, and I might have no remote named origin. . Credit for the main logic in this code still goes to Artur.
(defun modi/add-PR-fetch-ref (&optional remote-name)
"If refs/pull is not defined on a GH repo, define it.
If REMOTE-NAME is not specified, it defaults to the `remote' set
for the \"main\" or \"master\" branch."
(let* ((remote-name (or remote-name
(magit-get "branch" "main" "remote")
(magit-get "branch" "master" "remote")))
(remote-url (magit-get "remote" remote-name "url"))
(fetch-refs (and (stringp remote-url)
(string-match "github" remote-url)
(magit-get-all "remote" remote-name "fetch")))
;; https://oremacs.com/2015/03/11/git-tricks/
(fetch-address (format "+refs/pull/*/head:refs/pull/%s/*" remote-name)))
(when fetch-refs
(unless (member fetch-address fetch-refs)
(magit-git-string "config"
"--add"
(format "remote.%s.fetch" remote-name)
fetch-address)))))
(add-hook 'magit-mode-hook #'modi/add-PR-fetch-ref)
Summary #
With the above snippet added to your Emacs config and evaluated, each
time you visit a repo cloned from GitHub in the Magit Status buffer
(M-x magit-status
), the PR refs will get auto-added to that repo’s
.git/config
if needed.
After that, you can easily view the commits from all the PRs by doing l a f a.