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/server/server.go +1
| @@ -74,6 +74,7 @@ func New(cfg *config.Config, binPath string, log *slog.Logger) (*Server, error) | |||
| 74 | 74 | Log: log, | |
| 75 | 75 | HostKeyPath: cfg.SSHHostKey(), | |
| 76 | 76 | MaxBlobBytes: cfg.MaxBlobBytes, | |
| 77 | AuditPath: cfg.AuditLog(), | ||
| 77 | 78 | }) | |
| 78 | 79 | if err != nil { | |
| 79 | 80 | return nil, fmt.Errorf("ssh: %w", err) | |