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.
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 ./...).
README.md +11−2
| @@ -236,11 +236,20 @@ git push origin feature:refs/heads/main | |||
| 236 | 236 | ``` | |
| 237 | 237 | ||
| 238 | 238 | The request page shows what would land — the diff against the merge base, the CI | |
| 239 | checks for the head, and who has approved which commit — so a review does not start | ||
| 240 | with a clone. The push is refused unless the new tip *is* the approved commit, or is a | ||
| 239 | checks for the head, how many approvals are required and who has approved which | ||
| 240 | commit — so a review does not start with a clone. It also answers the one question a | ||
| 241 | diff cannot: whether the branch still **merges** ("this branch no longer merges into | ||
| 242 | main cleanly: `code.txt`"), computed with `git merge-tree` in memory, so a branch that | ||
| 243 | needs a rebase is visible before anybody approves it. That answer is shown, never | ||
| 244 | enforced, and it is left out entirely when the question cannot be asked — a base | ||
| 245 | branch that no longer exists, or a head that was never pushed. The push is refused | ||
| 246 | unless the new tip *is* the approved commit, or is a | ||
| 241 | 247 | merge whose parents are the branch tip and the approved commit. That second shape is why | |
| 242 | 248 | "merge, then push" works while "append a commit and call it approved" does not. | |
| 243 | 249 | ||
| 250 | `vole request show <id>` prints the same `merge:` line from a clone, and says so when | ||
| 251 | the head is not in the clone rather than guessing. | ||
| 252 | |||
| 244 | 253 | ```sh | |
| 245 | 254 | vole acl protect demo refs/heads/main # protect a branch | |
| 246 | 255 | vole acl approvals demo 2 # require two approvals (default 1) | |