Emacs, scripting and anything text oriented.

View GitHub Pull Requests in Magit

Kaushal Modi

How 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:

  1. Open the local repo’s .git/config file.
  2. Find the [remote "origin"] section
  3. 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!

Figure 1: Viewing PR references from denote package’s GitHub repo

Figure 1: Viewing PR references from denote package’s GitHub repo

.. 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 remote-name
      (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)
Code Snippet 1: Function to auto-add GitHub PR references to the repo's .git/config

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.

References #


Versions used: emacs 28.1.50
This is Day 33 of #100DaysToOffload.
Webmentions
Comment by Anonymous on Thu Jun 30, 2022 08:54 EDT

Nice post! I think it’s also worth mentioning for exactly this use case the forge package (https://magit.vc/manual/forge.html) from Magit’s maintainer. It lists open and closed PRs and issues in multiple forges (including both Github and Gitlab). You can see them, respond, edit, checkout and create new ones if you set up the OAuth token.

One of the nicer things is that it adds an option on the checkout transient to checkout a PR locally, and you can then push to that PR any extra changes you may need.