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.

README.md +15−4

@@ -38,7 +38,7 @@ Working today:
38 38 every key added or revoked. A running instance re-reads the directory every few
39 39 seconds, so `vole user add-key` — or an edit by hand — applies without a restart,
40 40 and revocation takes effect the same way
41 - Issues stored as files in the repository on the `issues` branch, validated at
41 - Issues stored as files in the repository on the `vole` branch, validated at
42 42 push time and rendered read-only on the web
43 43 - Repo create / rename / delete from the CLI
44 44 - A derived SQLite index that can be deleted and rebuilt from the repos
@@ -72,10 +72,10 @@ over HTTP — not a `403` — so the read-only surface never confirms that it ex
72 72
73 73 ## Filing an issue
74 74
75 An issue is a directory on the `issues` branch. No client tooling is required:
75 An issue is a directory on the `vole` branch. No client tooling is required:
76 76
77 77 ```sh
78 git switch --orphan issues
78 git switch --orphan vole
79 79 ID=01M5JR2JK29KKMAA1T9MDNTPBP # any 26-char ULID; unique per issue
80 80 mkdir -p issues/$ID
81 81 cat > issues/$ID/meta.json <<'JSON'
@@ -86,9 +86,20 @@ cat > issues/$ID/meta.json <<'JSON'
86 86 "labels": ["bug"]}
87 87 JSON
88 88 printf 'Steps to reproduce...\n' > issues/$ID/description.md
89 git add -A && git commit -m "issue: docs page blank" && git push origin issues
89 git add -A && git commit -m "issue: docs page blank" && git push origin vole
90 90 ```
91 91
92 That branch holds Vole's metadata and is deliberately narrow:
93
94 - Only `issues/` (and `requests/`, once change requests land) may appear in it;
95 anything else is rejected at push.
96 - History must be linear. Merge commits are refused, so `git log vole` reads as
97 a list of changes rather than a graph.
98 - Force-pushing it requires repo admin and is written to `data/audit.log`, because
99 rewriting history is a redaction tool, not a normal operation.
100 - If someone else pushes first, `vole issue new` re-applies your change on the new
101 tip and retries; you do not have to resolve anything by hand.
102
92 103 The id must be a ULID (26 characters, Crockford base32 — uppercase, no `I`, `L`,
93 104 `O` or `U`). A UUID is not one and will be rejected.
94 105