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.
Let an author move their change request's head from the browser
53ff967be619894b62ca0ca69cd9a9f5f52bce01 · Keenan Nemetz · 13d ago · parent 8e1005eb
Let an author move their change request's head from the browser A request names the exact commit under review, so a fixup pushed after it was opened is invisible until the head moves. The only mover was `vole change update --head`, which writes the metadata branch — a write a contributor cannot make. The person who owns the branch was the one person who could not keep the request current. The request page now offers the author (or a maintainer) a branch picker that moves an open request's head to that branch's tip. It applies the same rule the new-request form does: the head must be a branch tip, and a contributor may only name a branch under their own namespace, which is what the pre-receive hook would have let them push. Approvals stay in the tree as history and stop matching, which is what a new head already means. SetRequestHead now refuses a closed or merged request, so the CLI and the browser share that rule instead of the web handler owning it alone.
server/web/request_view.go +13
| @@ -59,6 +59,13 @@ type requestSidebarView struct { | |||
| 59 | 59 | // commit under review ("branch moved", "branch deleted"), so the name is read as | |
| 60 | 60 | // provenance rather than as a claim about what is being reviewed. | |
| 61 | 61 | HeadRefNote string | |
| 62 | // CanMoveHead is the control that points an open request at a newer commit. It | ||
| 63 | // is offered to the author and to maintainers, and only while the request is | ||
| 64 | // open: a merged request's head is the record of what landed. | ||
| 65 | CanMoveHead bool | ||
| 66 | // HeadBranches are the branches that control may name: every normal branch for | ||
| 67 | // a maintainer, the viewer's own namespace for a contributor. | ||
| 68 | HeadBranches []string | ||
| 62 | 69 | // CanMerge is the merge control: true only when the gate would accept the | |
| 63 | 70 | // landing now, so the button never offers something the handler would refuse. | |
| 64 | 71 | CanMerge bool | |
| @@ -129,6 +136,11 @@ func (w *Web) requestSidebar(r *http.Request, m *repo.Meta, req *metadata.Reques | |||
| 129 | 136 | headRefNote = "branch moved" | |
| 130 | 137 | } | |
| 131 | 138 | } | |
| 139 | canMoveHead := req.Open() && canTriage(acl, who, req.Author) | ||
| 140 | var headChoices []string | ||
| 141 | if canMoveHead { | ||
| 142 | headChoices = headBranches(branchNames(m), acl, who) | ||
| 143 | } | ||
| 132 | 144 | // Preflight the landing rather than guessing from the approval count: the same | |
| 133 | 145 | // gate the handler calls is asked here, so the button and the write agree about | |
| 134 | 146 | // approvals, checks and conflicts. | |
| @@ -161,6 +173,7 @@ func (w *Web) requestSidebar(r *http.Request, m *repo.Meta, req *metadata.Reques | |||
| 161 | 173 | Page: w.page(r, title), Name: m.Name, Request: req, | |
| 162 | 174 | Approvals: approvals, Checks: checks, Required: required, Current: current, | |
| 163 | 175 | CanApprove: approvable, CanClose: canClose, HeadRefNote: headRefNote, | |
| 176 | CanMoveHead: canMoveHead, HeadBranches: headChoices, | ||
| 164 | 177 | CanMerge: mergeable, MergeBlocked: mergeBlocked, | |
| 165 | 178 | CanDiscuss: w.canDiscuss(r, acl), | |
| 166 | 179 | ContributorCI: contributorCI, | |