Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

One metadata branch: refs/heads/vole, linear history, audited rewrites

92b37587e6e8b4e05f95dfc1e555ba36eaaa424d · Keenan Nemetz · 17d ago · parent f9568748

One metadata branch: refs/heads/vole, linear history, audited rewrites

Issues and change requests now share a single branch. Both are human-authored and
low-frequency, and one ref means one push, one fetch and one validation path
instead of per-branch special cases.

  refs/heads/vole
    issues/<ulid>/...
    requests/...        reserved for change requests

Rules, enforced in pre-receive rather than documented and hoped for:

- The tree layout is closed: only issues/ and requests/ may appear, so a typo in
  a directory name is rejected instead of being silently ignored by the web.
- History must be linear. Merge commits on the metadata branch are refused,
  because a merge node says nothing and makes `git log` harder to read than the
  change it describes. Rebasing is what happens instead.
- Force-push requires repo admin and writes a line to the audit log. Rewriting
  discussion history is sometimes necessary (someone pastes a secret) but must
  never be silent, so it is allowed rather than forbidden and recorded.

The CLI no longer fails on a lost race. Because every entity is its own file,
re-applying a change on the new tip cannot conflict, so it re-fetches, redoes the
mutation and retries with jittered backoff. Two things this turned up that a
single-writer test could never have found:

- A lost race has more than one shape. Besides "non-fast-forward" and "fetch
  first", a push can arrive with a stale old-oid ("incorrect old value
  provided") or lose the creation of the branch itself ("reference already
  exists"). All four are retryable; hook rejections are not.
- Retries without jitter keep writers in lockstep: six concurrent writers failed
  five rounds in a row with a deterministic backoff, and converged immediately
  once the sleep was randomised.

Verified by hand: the CLI writes to the vole branch, `git log vole` is a flat
list, and a merge push is refused with both problems reported at once — the merge
node and a malformed issue id in the same tree.

docs/roadmap.md +10−3

@@ -136,9 +136,14 @@ file them, validated at the push boundary. The Phase 0 sharding work is not wast
136 136 Shipped:
137 137
138 138 - **Format**: `issues/<ulid>/meta.json`, `description.md`, and
139 `comments/<ulid>.md`, on `refs/heads/issues`. ULIDs are generated by the writer,
140 so there is no counter to assign and no contention; it is a branch rather than a
141 custom ref so that a plain `git clone` carries the issues.
139 `comments/<ulid>.md`, on `refs/heads/vole`. ULIDs are generated by the writer, so
140 there is no counter to assign. It is a branch rather than a custom ref so that a
141 plain `git clone` carries the issues, and issues share it with change requests
142 because both are human-authored and low-frequency — one push, one fetch, one
143 validation path. Machine-written state (CI runs) must not use this ref.
144 - **Branch rules**, enforced in `pre-receive`: only `issues/` and `requests/` may
145 appear in the tree; history must be linear (no merge commits); force-push
146 requires repo admin and is written to the audit log.
142 147 - **Validation in `pre-receive`**: ULID directory names, required fields, valid
143 148 states and labels, comment frontmatter, and no paths outside the format. Every
144 149 problem is reported in one rejection, with the offending path named.
@@ -322,6 +327,8 @@ Filled in as phases land. Append; don't rewrite.
322 327 | 2026-10-22 | Issues are push-based on `refs/heads/issues`, IDs are client-generated ULIDs | No authenticated web write surface exists, and a branch makes issues travel with a clone |
323 328 | 2026-10-22 | Issue trees validated in `pre-receive` | Reject bad data at the boundary instead of rendering it |
324 329 | 2026-10-22 | CLI follows gitcli(7): `--long`, `-o`, options first, `--`, exit 129 | A tool that speaks git's dialect composes with git and with muscle memory |
330 | 2026-10-22 | Issues and requests share `refs/heads/vole`; linear history, no merges | One ref, one push, atomic cross-cutting edits; entities are separate files so a re-apply never conflicts |
331 | 2026-10-22 | Force-push to metadata is admin-only and audited | Redaction is a real need, but it must never be silent |
325 332
326 333 ## Open questions
327 334