Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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/web/web.go +10−2

@@ -21,18 +21,20 @@ import (
21 21 "github.com/rodent-software/vole/internal/identity"
22 22 "github.com/rodent-software/vole/internal/index"
23 23 "github.com/rodent-software/vole/internal/repo"
24 "github.com/rodent-software/vole/internal/runs"
24 25 )
25 26
26 27 type Web struct {
27 28 Repos *repo.Store
28 29 Index *index.Store
29 30 Ident *identity.Store
31 Runs *runs.Store
30 32 Cfg *config.Config
31 33 Tmpl *template.Template
32 34 Logger func(format string, args ...any)
33 35 }
34 36
35 func New(repos *repo.Store, ix *index.Store, ident *identity.Store, cfg *config.Config) (*Web, error) {
37 func New(repos *repo.Store, ix *index.Store, ident *identity.Store, rs *runs.Store, cfg *config.Config) (*Web, error) {
36 38 t, err := template.New("vole").Funcs(template.FuncMap{
37 39 "since": humanSince,
38 40 "short": shortSHA,
@@ -41,7 +43,7 @@ func New(repos *repo.Store, ix *index.Store, ident *identity.Store, cfg *config.
41 43 if err != nil {
42 44 return nil, err
43 45 }
44 w := &Web{Repos: repos, Index: ix, Ident: ident, Cfg: cfg, Tmpl: t}
46 w := &Web{Repos: repos, Index: ix, Ident: ident, Runs: rs, Cfg: cfg, Tmpl: t}
45 47 w.Logger = func(string, ...any) {}
46 48 return w, nil
47 49 }
@@ -132,6 +134,10 @@ func (w *Web) Repo(rw http.ResponseWriter, r *http.Request) {
132 134 if err := w.Index.EnsureIssues(m); err == nil {
133 135 issueCount, _ = w.Index.IssueCount(m.ID)
134 136 }
137 var recentRuns []*runs.Run
138 if w.Runs != nil {
139 recentRuns, _ = w.Runs.Recent(m.Name, 8)
140 }
135 141 branches, _ := g.ListRefs("refs/heads/")
136 142 names := make([]string, 0, len(branches))
137 143 for k := range branches {
@@ -150,6 +156,7 @@ func (w *Web) Repo(rw http.ResponseWriter, r *http.Request) {
150 156 Count int
151 157 Branches []string
152 158 IssueCount int
159 Runs []*runs.Run
153 160 }{
154 161 Page: w.base(m.Name),
155 162 Name: m.Name,
@@ -163,6 +170,7 @@ func (w *Web) Repo(rw http.ResponseWriter, r *http.Request) {
163 170 Count: count,
164 171 Branches: names,
165 172 IssueCount: issueCount,
173 Runs: recentRuns,
166 174 }, 0)
167 175 }
168 176