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 | ||