Issues in the Repo

jamietanna1 pts0 comments

Issues in the Repo | Andrew Nesbitt

GitHub had a rough Monday this week, with git operations, Actions, and the issue tracker all unavailable for several hours. Code being unreachable during a forge outage is annoying but survivable because every contributor already has a full clone. Issues and pull request threads going dark is a different matter, since for most projects those exist only in GitHub’s database and nowhere else. I asked on Mastodon about tools that keep issue and review data inside the repository so it clones and pushes with the code, and got pointed at more projects than I expected, spanning about twenty years of people having a go at this.

The projects sort by where they physically put the data, and each storage location brings its own answers to the same handful of questions: whether a plain git clone fetches it, what happens when two people edit the same issue offline and then both push, whether you can read it without the tool installed, whether it survives being pushed to a bare git host that has no support for the tool, and who counts as the author of an issue when there are no forge accounts. I wrote about git’s extension points last year and several of these tools use the custom-ref mechanism from that post, but here I’m looking at the data model rather than the plumbing.

Files in the working tree

The oldest approach is to make each issue a text file, or a directory of files, checked in next to the source. Bugs Everywhere did this in 2005 for Bazaar and later added git and Mercurial backends, and ditz stored issues as YAML from 2008. More recently GitRoot builds an entire self-hosted forge on the same idea, keeping issues as issues/-.md with front-matter status fields, users and permissions as YAML under .gitroot/, and a small server that renders the lot as a web UI. Matthew Martin pointed at his own ticket directory that reuses keep-a-changelog fragments as lightweight work items with no tooling at all, which is about as minimal as this pattern gets.

In Diomidis Spinellis’s git-issue the .issues/ directory is itself a nested git repository with its own .git, sha-bucketed as issues/ef/1a04f.../description, .../tags, .../assignee and so on. That keeps the outer project’s history untouched, though the outer clone then doesn’t carry the issues at all.

Because the issues are ordinary tracked files, everything git already does works with no extra configuration: git clone fetches them, grep -r searches them, cat reads them, git bundle packs them, any dumb HTTP host or cgit instance serves them, and git log -p .issues/ shows who changed what and when. Concurrent edits to the same issue become a normal three-way text merge. That works cleanly when two people append separate comments to the bottom of a file, and produces a real conflict when they both change the title line, which you resolve the same way you’d resolve a conflict in code. The author of an issue is whoever git blame says created the file, backed by whatever commit signing the project already uses, so identity comes from git’s existing model rather than needing its own.

The drawback is that issue history and code history share one commit graph. On a busy project git log fills with “reword issue #34” interleaved with real changes, git bisect steps through issue-edit commits, and creating a feature branch forks the issue state along with the code, so an issue closed on the branch shows as open again the moment you switch back to main. Most of these tools work around the log noise by making each issue operation its own commit with a conventional prefix that’s easy to --invert-grep away, but the branching problem is unavoidable while mutable metadata is in the same tree as the code it describes.

Orphan branch

An orphan branch is a ref under refs/heads/ whose root commit has no parent, so it shares no history with main and its files never appear in a normal checkout. Scott Chacon’s ticgit from 2008 and the later ticgit-ng fork keep issues on a branch literally called ticgit, with each issue as a directory in that branch’s tree, and GitHub’s old gh-pages convention used the same mechanism for documentation. The very new haxy forge from the xit project puts an event log on refs/heads/haxy/events where each commit has an empty tree and the commit message is a JSON event describing an issue creation, comment, or status change; the server replays the log to build its state.

The tool reads and writes the branch through plumbing or a second worktree, so git log on main stays free of issue noise, merging concurrent edits works the same as for any files on a branch, and the issue author is whoever authored the commit on the orphan branch. Because the ref is under refs/heads/ a default git clone fetches it automatically, which is the one clear advantage this has over the notes and custom-ref approaches below.

$ git ls-tree ticgit<br>040000 tree 9b721cc... 1206206148_add-attachment-to-ticket_138<br>040000 tree 5d14214......

issue issues branch tree commit code

Related Articles