Organizing my git issues in org mode
Org Agenda
Over time, my Org setup reached a point where it felt complete. Notes, todos, meetings, ideas — everything had its place, and everything flowed naturally into my agenda (see my previous blog post). I could even define my own custom agenda states!
The default TODO/DONE cycle works fine for a grocery list, but real work has more nuance. Org makes it easy to define your own workflow states:
1
2
3
4
5
6
7
(setq org-todo-keywords
'((sequence "TODO(t)" "NEXT(n)" "PROJ(p)" "WAITING(w)" "|" "DONE(d)" "CANCELLED(c)")))
(setq org-todo-keyword-faces
'(("TODO" . (:foreground "orange" :weight bold))
("NEXT" . (:foreground "purple" :weight bold))
("PROJ" . (:foreground "deep sky blue" :weight bold))
("WAITING" . (:foreground "red" :weight bold))))
The beauty of this approach is that these keywords are universal: they work everywhere — in the agenda, in tag searches, and in custom views. Define it once, and the whole system respects it.
git Issues
One category of tasks refused to fit neatly into my workflow: issues from Git platforms. Much of my work happens in repositories, where tasks are tracked as issues on GitHub, GitLab, or Gitea. These issues are just as important as anything in my local todo files, but they lived in completely separate systems. That split was frustrating: a clean, unified Org workflow on one side, and dozens of browser tabs on the other.
The first obvious solution was Forge, the Magit extension that integrates Git forges directly into Emacs. And to be fair, Forge is impressive, but it felt too heavy for me. The configuration was more involved than I wanted, and it tried to solve a much bigger problem than the one I had.
What I really wanted was much simpler: I just wanted my issues to show up as Org todos. Instead of bending my workflow around a large tool, I decided to build something small that fits exactly what I need, which became gom - “the Git Org Manager”.
The idea is almost trivial. Instead of talking to APIs directly, gom uses the existing command line tools for each platform. It fetches issues through those tools, transforms them into Org entries, and inserts them into my files.
For GitHub, there is ~gh~. For GitLab, there is ~glab~. For Gitea, there is ~tea~. These tools already handle authentication, configuration, and API quirks.
By building on top of them, i avoided having to reimplement all of that complexity. It becomes a thin layer that simply translates CLI output into Org format, so that each issue becomes a TODO entry, enriched with some metadata.
From there, everything falls back into the standard Org workflow. Issues can be scheduled, tagged, filtered, and viewed in the agenda just like any other task.
I primarily use GitLab, so that backend came first. However, I didn’t want gom to be tied to a single platform. Supporting GitHub and Gitea felt like the natural next step, even if I wouldn’t use them as heavily. Instead of implementing everything manually, I used Gemini and Claude to help generate parts of the GitHub and Gitea integrations.
Setup
The setup is intentionally minimal.
First, authenticate with the CLIs:
1
2
3
4
gh auth login
glab auth login --hostname gitlab.mycompany.com
glab auth login --hostname git.home-lab.io
tea login add
Then clone the repository and add it to your Emacs load path:
1
2
(add-to-list 'load-path "/path/to/gom.el")
(require 'gom)
Next, configure your repositories and output file:
1
2
3
4
5
6
7
(setq gom-repos-directory "~/folder-containing-repos")
(setq gom-issues-file "~/Documents/issues.org")
(setq gom-git-platform-mapping
'(("github.com" . github)
("gitlab.your-domain.de" . gitlab)
("gitea.your-domain.de" . gitea)))
After that you can run ~M-x gom-dashboard~ or ~M-x gom-sync~ to get all your issues as org todo tasks.
This approach deliberately avoids building a full integration layer which was enough for me.
Git issues are no longer something I “check in the browser.” They’re just tasks—sitting next to everything else, showing up in my agenda, and handled the same way.