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.
Test the merge result before landing it
e5478085e5b53a2892d0649d6f07c6f0a5653378 · Keenan Nemetz · 11d ago · parent db5e4382
Test the merge result before landing it A protected branch could receive a non-fast-forward landing whose tree was never built: the gate asked for checks on the request head, and the merge commit was made at landing time. A pushed merge with the right parents but extra content was accepted too. Publish the merge commit under refs/vole/candidates/, test it as the base branch, and make the gate require its results. Approvals name the head and the base branch, so a moved base retests without a new approval. A merger who clicks while that run is still going waits for it instead of clicking again. A pushed merge commit now has to carry exactly the tree a clean merge would produce. Closes: 01M61QQ56TESQ1HTMB1Z5C3733
forge/access/acl.go +21
| @@ -327,6 +327,27 @@ func ContributorRefName(ref string) (name string, ok bool) { | |||
| 327 | 327 | return rest[:i], true | |
| 328 | 328 | } | |
| 329 | 329 | ||
| 330 | // CandidateRoot is the server-only namespace a combined result is published under. | ||
| 331 | // It lives here rather than in forge/landing so the runner can read the trust of a | ||
| 332 | // ref without importing the landing rules. | ||
| 333 | const CandidateRoot = "refs/vole/candidates/" | ||
| 334 | |||
| 335 | // TrustNamespace names the cache namespace a run's ref belongs to. A contributor | ||
| 336 | // branch and a combined result derived from one are both untrusted code, and a | ||
| 337 | // cache is shared mutable state: they may not seed the cache a protected branch | ||
| 338 | // builds into. Everything else is direct. | ||
| 339 | func TrustNamespace(ref string) string { | ||
| 340 | if name, ok := ContributorRefName(ref); ok { | ||
| 341 | return "contrib-" + name | ||
| 342 | } | ||
| 343 | if rest, ok := strings.CutPrefix(ref, CandidateRoot+"contrib/"); ok { | ||
| 344 | if i := strings.IndexByte(rest, '/'); i > 0 { | ||
| 345 | return "contrib-" + rest[:i] | ||
| 346 | } | ||
| 347 | } | ||
| 348 | return "direct" | ||
| 349 | } | ||
| 350 | |||
| 330 | 351 | // CanDiscuss covers filing issues and commenting. The Contribute role may do | |
| 331 | 352 | // this without being able to move a shared branch. | |
| 332 | 353 | func (a *ACL) CanDiscuss(user string) bool { return a.RoleFor(user).AtLeast(Contribute) } | |