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.
internal/sshserver/sshserver.go +3
| @@ -38,6 +38,8 @@ type Config struct { | |||
| 38 | 38 | Log *slog.Logger | |
| 39 | 39 | HostKeyPath string | |
| 40 | 40 | MaxBlobBytes int64 | |
| 41 | // AuditPath is handed to hooks so privileged operations can be recorded. | ||
| 42 | AuditPath string | ||
| 41 | 43 | } | |
| 42 | 44 | ||
| 43 | 45 | type Server struct { | |
| @@ -208,6 +210,7 @@ func (s *Server) exec(ch ssh.Channel, req *ssh.Request, command, user, fp string | |||
| 208 | 210 | "VOLE_KEY_ID="+fp, | |
| 209 | 211 | "VOLE_REPO="+name, | |
| 210 | 212 | "VOLE_MAX_BLOB="+strconv.FormatInt(s.cfg.MaxBlobBytes, 10), | |
| 213 | "VOLE_AUDIT="+s.cfg.AuditPath, | ||
| 211 | 214 | ) | |
| 212 | 215 | cmd.Stdin = struct{ io.Reader }{ch} | |
| 213 | 216 | cmd.Stdout = struct{ io.Writer }{ch} | |