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/web/templates/requests.html +12−1

@@ -173,9 +173,13 @@
173 173 <span class="state {{.Request.State}}">{{.Request.State}}</span>
174 174 · <span class="sha">#{{short .Request.ID}}</span>
175 175 · by {{who .Request.Author}} {{since .Request.Created.Unix}}
176 {{if eq .Request.State "merged"}}
177 · merged into <code>{{.Request.Base}}</code> at <span class="sha">{{short .Request.MergedHead}}</span>
178 by {{who .Request.MergedBy}} {{since .Request.MergedAt.Unix}}
179 {{end}}
176 180 </p>
177 181 </div>
178 {{if or .CanApprove .CanClose .ContributorCI}}
182 {{if or .CanApprove .CanClose .CanMerge .ContributorCI}}
179 183 <div class="page-head-actions">
180 184 {{with .ContributorCI}}{{if .CanAllow}}
181 185 <form method="post" action="/r/{{$.Name}}/ci/allow">
@@ -191,6 +195,12 @@
191 195 <button class="success" type="submit">{{icon "check"}}Approve</button>
192 196 </form>
193 197 {{end}}
198 {{if .CanMerge}}
199 <form method="post" action="/r/{{.Name}}/requests/{{.Request.ID}}/merge">
200 <input type="hidden" name="csrf" value="{{.CSRF}}">
201 <button class="success" type="submit">{{icon "git-merge"}}Merge</button>
202 </form>
203 {{end}}
194 204 {{if .CanClose}}
195 205 <form method="post" action="/r/{{.Name}}/requests/{{.Request.ID}}/state">
196 206 <input type="hidden" name="csrf" value="{{.CSRF}}">
@@ -200,6 +210,7 @@
200 210 {{end}}
201 211 </div>
202 212 {{end}}
213 {{if .MergeBlocked}}<p class="muted small">{{.MergeBlocked}}</p>{{end}}
203 214 </div>
204 215 {{end}}
205 216