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 4 slice 3: checks on the gate, and statuses on the page
35eaeccffdab9d07e13fb7b346060e535697fee7 · Keenan Nemetz · 17d ago · parent b44cbfaf
Phase 4 slice 3: checks on the gate, and statuses on the page A protected branch now requires both an approved change request and passing checks for the commit being landed. Approvals say a person read the change; checks say it works. Neither substitutes for the other. The gate asks about the *approved head*, not about the merge commit somebody may be pushing: CI ran on the head, and a merge commit is a new object nobody built. That also keeps the gate consistent with what approvals already name. Details that decide whether this is usable: - A repository with no workflows is unaffected — nothing to run means nothing to wait for — so existing repositories do not start failing pushes. - A job with no run is refused with an explanation that names the branch filter, because `on: push: branches: [main]` never produces a result for a feature branch. That is the one configuration that can deadlock a merge, and the error says so instead of leaving somebody guessing. - Failed, cancelled, still-running and missing are four different messages; a reviewer should be able to tell which. - `vole acl checks <repo> on|off` turns it off, default on. Statuses are readable: the repository page lists recent runs (job, commit, status, attempts, age) and a change request shows its head's job statuses next to the approvals, so the reviewer sees the same facts the gate uses. Tests cover the three interesting shapes: approved + green lands; approved + red is refused naming the failed job; approved + no run at all is refused with the branch-filter hint. Verified on a live instance: pushing a workflow queues and runs it, the repository page shows `build · .vole/workflows/ci.yaml · success`, and the change request shows `wilbur · current` alongside `build · success`. Phase 4 is now complete except signed run records, which are Phase 7's.
internal/hook/hook.go +17
| @@ -33,6 +33,9 @@ type Config struct { | |||
| 33 | 33 | SystemRepo string | |
| 34 | 34 | // SpoolDir receives one file per accepted ref update, for the CI queue. | |
| 35 | 35 | SpoolDir string | |
| 36 | // RunsDB is the run queue, read here so required checks are enforced at the | ||
| 37 | // same single point as everything else. | ||
| 38 | RunsDB string | ||
| 36 | 39 | // AuditPath receives lines about privileged metadata operations. Empty means | |
| 37 | 40 | // no audit sink, which is only acceptable for tests. | |
| 38 | 41 | AuditPath string | |
| @@ -192,6 +195,20 @@ func PreReceive(cfg Config) int { | |||
| 192 | 195 | c.ref, actor, a.RequiredApprovals(), short(c.new), branch, short(c.new))) | |
| 193 | 196 | continue | |
| 194 | 197 | } | |
| 198 | // Approvals say a person read the change; checks say it works. Both are | ||
| 199 | // required unless the repository turns checks off. | ||
| 200 | if a.RequiresChecks() { | ||
| 201 | gaps, err := checkGaps(g, cfg.RunsDB, repoNameFromDir(cfg.RepoDir), m.Request.Head, branch) | ||
| 202 | if err != nil { | ||
| 203 | problems = append(problems, fmt.Sprintf("%s: cannot verify required checks: %v", c.ref, err)) | ||
| 204 | continue | ||
| 205 | } | ||
| 206 | if len(gaps) > 0 { | ||
| 207 | problems = append(problems, fmt.Sprintf("%s: checks have not passed for %s:\n %s", | ||
| 208 | c.ref, short(m.Request.Head), strings.Join(gaps, "\n "))) | ||
| 209 | continue | ||
| 210 | } | ||
| 211 | } | ||
| 195 | 212 | auditLine(cfg.AuditPath, fmt.Sprintf("merge %s via request %s (head %s) approved by %s", | |
| 196 | 213 | c.ref, m.Request.ID, short(m.Request.Head), strings.Join(m.Approvers, ","))) | |
| 197 | 214 | } | |