This is gh-notify: A thin ui veneer on top of Magit/Forge porcelain for juggling large amounts of GitHub notifications at speed.
It provides a more efficient interface to the Magit/Forge notification database suited for rapid searching/narrow/filter based workflows. It also improves on Magit/Forge’s default notification fetching behavior by introducing support for incremental notification fetching, which is a must for interactive notification queue workflow iterations.
Note: as of version 2.0.0 gh-notify relies on forge’s improved and builtin incremental update fetching as well as its simplified read/unread status management.
This code should be plug and play if you already have Magit/Forge set up and have fetched notifications for your GitHub account at least once. If not, please see: https://magit.vc/manual/forge.html to get started.
- M-x forge-pull-notifications RET (slow)
- M-x gh-notify RET (fast)
- M-x describe-mode RET (docs)
You only have to do bootstrap the Forge database in the slow way once, and will be able to interact with your notifications through gh-notify from that point on which, hopefully, will be a much snappier experience :)
gh-notify requires the latest versions of Magit/Forge to be installed from melpa as per April 10, 2024. It interacts with lowlevel Forge APIs quite liberally and is sensitive to core API changes in the Forge project. If things break for you on versions installed beyond this date, please file an issue.
If you are on much older versions of Magit/Forge, please use version 0.1.0 of gh-notify.
Incremental notification fetching is integrated in a Forge interoperable manner and should not conflict with your normal Forge use. It will incrementally update the Forge database and retain state across sessions.
If, for whatever reason, you do want a fully clean notification slate, as opposed to the incremental/iterative experience provided by gh-notify, you can use: M-x forge-pull-notifications RET to restore a clean Forge slate.
Forge will mark things as read with GitHub.com, but since we do not do full refreshes of all notifications, but only get new notifications since the last update, these state updates are not available in gh-notify after a forge-visit completes.
As a workaround, gh-notify toggles the unread flag for the notification object in the Forge database locally to keep unread/read states synced.
Contrary to popular belief, you do not need local clones of Forge repos just to interact with issues and pull requests. Most all of that data is populated via the GitHub.com rest and GraphQL API and you can edit/comment/etc. issues and even review pull requests with e.g. github-review just fine. In fact, that is my personal use case as well.
This is my github-review config:
;; github-review
(use-package github-review
:quelpa
:after forge
:bind (("C-x r" . github-review-forge-pr-at-point)
:map diff-mode-map ("C-c s" . my/github-review-kill-suggestion))
:config
(defun my/github-review-kill-suggestion ()
;; kill a region of diff+ as a review suggestion template
(interactive)
(setq deactivate-mark t)
(let ((s-region
(buffer-substring-no-properties
(region-beginning)
(region-end))))
(kill-new
(format "# ```suggestion\n%s\n# ```\n"
(replace-regexp-in-string "^\\+" "# " s-region))))))
When opening a Forge PR from gh-notify, you can use M-x github-review-forge-pr-at-point RET and everything will work just fine, even without a local clone of the target repo.
The only caveat is that magit still expects to be operating in a git repository when constructing Forge topic buffers. When a repo does not exist locally, magit errors out when the default-directory is not a git repo.
As a workaround, gh-notify creates an empty git repository in
~/.gh-notify-smokescreen
and binds that path to default-directory for any
forge-visit interactions to ensure we can interact with non-local topics.
This is highly experimental code, but I do use it in my dayjob at GitHub which involves juggling hundreds of notifications across large sets of repos on a daily basis. Everything at GitHub happens through GitHub so it is imperative to me to have an effective and iterative workflow that lets me rapidly sort/narrow/filter and otherwise juggle notifications effectively. Preferably without having to step outside of Emacs.
Having said that, if anything breaks in weird ways, or in corner cases that I may not be aware of, please file an issue. I’ll get to it when I see the notification ;)
All of the awesome high-speed filtering is based on code written by Xristos <xristos@sdf.org>
He is an absolute monster when it comes to anything involving parentheses and remains an inspiration in the software engineering field.
I would also like to acknowledge Jonas Bernoulli for his amazing work on the Magit/Forge project.
Copyright (C) 2021 bas@anti.computer 2020 xristos@sdf.org All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This project includes code modified from: Magit/Forge (https://github.com/magit/forge) Copyright (C) 2018-2021 Jonas Bernoulli Magit/Forge modifications are subject to the following license terms: Forge is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Forge is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Forge. If not, see http://www.gnu.org/licenses. This project includes code modified from: chrome.el (https://github.com/anticomputer/chrome.el) Copyright (C) 2020 xristos@sdf.org 2020 bas@anti.computer More specifically it repurposes the text filtering and rendering engine developed by Xristos <xristos@sdf.org> for chrome.el. All his original author credits and licensing terms apply.