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.

Land a change request on the server instead of by pushing

38b3025145d8cee8479733fd4d3b0df443c72607 · Keenan Nemetz · 13d ago · parent 536f08b9

Land a change request on the server instead of by pushing

A change request could only be landed by merging locally and pushing the
base branch: the web page was a review surface with no way to finish, and
nothing recorded that the request had landed. `state: merged` was already
accepted by the request reader and rendered by the activity feed, but
nothing ever wrote it, so a merged request and an abandoned one looked
identical.

The gate that decides a landing lives in the pre-receive hook, and the
hook only sees pushes. A merge performed inside the server moves the ref
without the hook, so a server-side merge either re-runs that rule or
becomes the easiest way to land an unapproved change. It now re-runs it:
the approval and check rule moved to forge/landing, and both the hook and
the new landing operation call the same Check.

Landing is fast-forward when the base is an ancestor of the reviewed head,
so the commit that passed CI is the commit that lands; otherwise a
two-parent merge commit, which is the only other shape the gate accepts.
Conflicts are refused with the conflicting paths, never resolved. The ref
moves by compare-and-swap, so a concurrent push loses cleanly and the
landing retries; the request is marked merged after the ref moves, which
makes a retry after a crash finish the marking instead of failing.

The request now records merged_head, merged_by and merged_at, and the
reader refuses a merged request that names no commit, and refuses those
fields on a request that is not merged. "merged" is no longer settable
through the generic state writer: it has to travel with the commit.

The browser gets a Merge button that preflights the same gate, so it only
appears when the landing would succeed, and the page shows what landed,
who landed it, and when. `vole change merge` follows in its own change:
the CLI has no access to the ACL or the run queue, so it needs a verb on
the SSH channel, and that transport is worth reviewing on its own.

Closes the landing half of #01M5XF4XWHVMMCQEYENA8X32GK.

server/hook/records.go +13−1

@@ -24,6 +24,7 @@ import (
24 24 "github.com/rodent-software/vole/ci/runs"
25 25 "github.com/rodent-software/vole/core/git"
26 26 "github.com/rodent-software/vole/forge/access"
27 "github.com/rodent-software/vole/forge/landing"
27 28 )
28 29
29 30 func recordFindings(g *git.Repo, c change, a *access.ACL, actor, repoName, usersDB, runsDB string) []string {
@@ -37,7 +38,7 @@ func recordFindings(g *git.Repo, c change, a *access.ACL, actor, repoName, users
37 38 if a == nil || !a.CanReport(actor) {
38 39 people := "none named, so only admins"
39 40 if a != nil {
40 people = runnerSummary(a)
41 people = landing.RunnerSummary(a)
41 42 }
42 43 return append(problems, fmt.Sprintf(
43 44 "%s: %s reported a run result but is not a runner for %s (runners: %s). "+
@@ -120,3 +121,14 @@ func describe(run *runs.Run) string {
120 121 }
121 122 return run.Status + " on " + run.LeaseOwner
122 123 }
124
125 // openRuns opens the run queue for one gate check. The pre-receive hook is a
126 // process that answers one push and exits, so the store is opened per check and not
127 // closed: the close would come after the answer, and the process ends there anyway.
128 // The server, which is long-lived, passes its own open handle instead.
129 func openRuns(path string) func() (*runs.Store, error) {
130 if path == "" {
131 return nil
132 }
133 return func() (*runs.Store, error) { return runs.Open(path) }
134 }