vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
Phase 3: a request says whether it would actually land
3c5d11faadd2f1ca4788e0f7f41a77a11840517e · Keenan Nemetz · 17d ago · parent a63475ce
Phase 3: a request says whether it would actually land A diff answers "what changed" and nothing else. A branch can read perfectly line by line and still refuse to merge, and until now nobody found that out until they ran `git merge` locally — after an approval had been given. The request page now asks `git merge-tree --write-tree --name-only <base-tip> <head>`, which merges in memory without a worktree or an index, and says either "this branch no longer merges into main cleanly: code.txt — rebase or merge main before landing it" or "merges into main cleanly". `vole request show` prints the same line from a clone. Three rules, each because the obvious version is wrong: - Surfaced, not gated. A gate would only ever be right at the instant it ran: the base can move after an approval, and the roadmap asked for detection before the push rather than a second way to refuse one. - Silence when the question cannot be asked. merge-tree exits 1 both for a conflict and for a revision it cannot resolve, so the exit status alone is not an answer; the answer is believed only when the output starts with a tree id, which is what a real merge prints first. A base branch that is gone, or a head that was never pushed, therefore produces no claim at all — a page that says "clean" about a commit it could not load is worse than one that says nothing. - The clone says what it does not know. `vole request show` reports "cannot check: <sha> is not in this clone (git fetch origin)" rather than reading a head it does not have as mergeable. The same view now states the requirement, "0 of 1 required approval(s)", and counts the approvals that currently apply — the number used to live in the reviewer's head. Verified: TestMergeConflictsNamesThePaths and TestMergeConflictsIsEmptyWhenItWould Land build two branches that touch the same line and one that touches another file; TestMergeConflictsReportsAnUnaskableQuestion pins that an unknown revision is an error rather than "no conflicts" (that is the bug the tree-id check exists for); TestParseMergeTree covers clean, empty, one path, two paths and the message section; TestRequestPageSaysWhenItWouldConflict drives a real conflict through the binary — main moves the same line after the feature branch was opened — checks the page, the "0 of 1" line, a clean branch, the CLI's conflict line, the CLI's "cannot check" for a head that is not in a fresh clone, and a request whose head was never pushed, which must say nothing. scripts/ci.sh green (gofmt, vet, build, markdown links, go test -race ./...).
internal/web/templates.go +10
| @@ -56,6 +56,7 @@ select{background:var(--card);color:var(--fg);border:1px solid var(--line);borde | |||
| 56 | 56 | .diff tr.add td.ln{color:#3fb950} | |
| 57 | 57 | .diff tr.del{background:rgba(248,81,73,.10)} | |
| 58 | 58 | .diff tr.del td.ln{color:var(--bad)} | |
| 59 | .conflict{color:var(--bad)} | ||
| 59 | 60 | .diff tr.hunk td{color:var(--muted);background:var(--card);padding:.25rem .5rem;font-size:.78rem} | |
| 60 | 61 | .diff .binary{padding:.7rem .8rem;color:var(--muted);font-size:.85rem} | |
| 61 | 62 | </style> | |
| @@ -399,6 +400,9 @@ commit to land, plus <code>approvals/<account-id>.md</code> from each appr | |||
| 399 | 400 | {{if .Description}}<div class="markdown">{{.Description}}</div>{{end}} | |
| 400 | 401 | ||
| 401 | 402 | <h2>Approvals</h2> | |
| 403 | {{if .Required}}<p class="muted">{{.Current}} of {{.Required}} required approval(s).</p> | ||
| 404 | {{else}}<p class="muted">This repository requires no approvals: only an admin may push.</p> | ||
| 405 | {{end}} | ||
| 402 | 406 | {{if .Approvals}} | |
| 403 | 407 | <table> | |
| 404 | 408 | <tr><th>Approver</th><th>Head approved</th><th>Base</th><th>Status</th><th>When</th></tr> | |
| @@ -432,6 +436,12 @@ commit to land, plus <code>approvals/<account-id>.md</code> from each appr | |||
| 432 | 436 | {{end}} | |
| 433 | 437 | ||
| 434 | 438 | <h2>What would land</h2> | |
| 439 | {{if .MergeChecked}}{{if .Conflicts}} | ||
| 440 | <p class="conflict">This branch no longer merges into {{.Request.Base}} cleanly: | ||
| 441 | {{range .Conflicts}}<code>{{.}}</code> {{end}}— rebase or merge {{.Request.Base}} before landing it.</p> | ||
| 442 | {{else}} | ||
| 443 | <p class="muted">Merges into {{.Request.Base}} cleanly.</p> | ||
| 444 | {{end}}{{end}} | ||
| 435 | 445 | {{if .AlreadyMerged}} | |
| 436 | 446 | <p class="muted">Already merged into {{.Request.Base}}.</p> | |
| 437 | 447 | {{else if .Files}} | |