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.
Make change requests follow branches and allow base retargeting
e050e14a64f6f19c1b1478c4c073d690ef3d3129 · Keenan Nemetz · 13d ago · parent 59464445
Make change requests follow branches and allow base retargeting A request opened from a branch now follows it: post-receive moves the request's head to the new tip after an accepted push, so approvals and checks that named the old tip stop counting. Approvals stay bound to an exact base and commit; the approval writers now require the base and head the approver saw, so a push or retarget cannot silently approve a target nobody reviewed. Merged requests freeze the reviewed head. Add `vole change update --base` and a Change base control for stacked requests. A new base changes the diff and therefore invalidates approvals, which is what retargeting an upper request after its lower branch lands needs. Base names may now contain slashes. Also fix a second-boundary flake in the landing test.
server/web/request_view.go +24−6
| @@ -55,17 +55,26 @@ type requestSidebarView struct { | |||
| 55 | 55 | Current int | |
| 56 | 56 | CanApprove bool | |
| 57 | 57 | CanClose bool | |
| 58 | // HeadRefNote says when the branch the request names no longer points at the | ||
| 59 | // commit under review ("branch moved", "branch deleted"), so the name is read as | ||
| 60 | // provenance rather than as a claim about what is being reviewed. | ||
| 58 | // HeadRefNote says when the branch the request follows no longer points at the | ||
| 59 | // commit under review ("branch moved", "branch deleted"). A push normally moves | ||
| 60 | // the head with the branch, so "branch moved" means that sync did not happen and | ||
| 61 | // the approval control is withheld until the request catches up. | ||
| 61 | 62 | 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 | ||
| 63 | // CanMoveHead is the control that repoints an open request at another branch. | ||
| 64 | // A request opened from a branch follows it automatically, so this is only for | ||
| 65 | // changing which branch it follows (or attaching a request opened from a commit). | ||
| 66 | // It is offered to the author and to maintainers, and only while the request is | ||
| 64 | 67 | // open: a merged request's head is the record of what landed. | |
| 65 | 68 | CanMoveHead bool | |
| 66 | 69 | // HeadBranches are the branches that control may name: every normal branch for | |
| 67 | 70 | // a maintainer, the viewer's own namespace for a contributor. | |
| 68 | 71 | HeadBranches []string | |
| 72 | // CanChangeBase is the control that retargets a stacked request at another base. | ||
| 73 | // A new base changes the diff, so the approvals that named the old base stop | ||
| 74 | // counting. | ||
| 75 | CanChangeBase bool | ||
| 76 | // BaseBranches are the branches that control may name: every normal branch. | ||
| 77 | BaseBranches []string | ||
| 69 | 78 | // CanMerge is the merge control: true only when the gate would accept the | |
| 70 | 79 | // landing now, so the button never offers something the handler would refuse. | |
| 71 | 80 | CanMerge bool | |
| @@ -125,7 +134,6 @@ func (w *Web) requestSidebar(r *http.Request, m *repo.Meta, req *metadata.Reques | |||
| 125 | 134 | } | |
| 126 | 135 | } | |
| 127 | 136 | who := w.accountName(r) | |
| 128 | approvable := req.Open() && canApproveRequest(acl, who, req.Author) | ||
| 129 | 137 | canClose := canTriage(acl, who, req.Author) | |
| 130 | 138 | headRefNote := "" | |
| 131 | 139 | if req.HeadRef != "" { | |
| @@ -136,11 +144,20 @@ func (w *Web) requestSidebar(r *http.Request, m *repo.Meta, req *metadata.Reques | |||
| 136 | 144 | headRefNote = "branch moved" | |
| 137 | 145 | } | |
| 138 | 146 | } | |
| 147 | // A sync that did not happen leaves the previous head in place. Do not offer to | ||
| 148 | // approve it while the followed branch is known to be ahead: the page is showing | ||
| 149 | // an older tip than the branch, and the head control is the way to catch up. | ||
| 150 | approvable := req.Open() && canApproveRequest(acl, who, req.Author) && headRefNote != "branch moved" | ||
| 139 | 151 | canMoveHead := req.Open() && canTriage(acl, who, req.Author) | |
| 140 | 152 | var headChoices []string | |
| 141 | 153 | if canMoveHead { | |
| 142 | 154 | headChoices = headBranches(branchNames(m), acl, who) | |
| 143 | 155 | } | |
| 156 | canChangeBase := req.Open() && canTriage(acl, who, req.Author) | ||
| 157 | var baseChoices []string | ||
| 158 | if canChangeBase { | ||
| 159 | baseChoices = branchNames(m) | ||
| 160 | } | ||
| 144 | 161 | // Preflight the landing rather than guessing from the approval count: the same | |
| 145 | 162 | // gate the handler calls is asked here, so the button and the write agree about | |
| 146 | 163 | // approvals, checks and conflicts. | |
| @@ -174,6 +191,7 @@ func (w *Web) requestSidebar(r *http.Request, m *repo.Meta, req *metadata.Reques | |||
| 174 | 191 | Approvals: approvals, Checks: checks, Required: required, Current: current, | |
| 175 | 192 | CanApprove: approvable, CanClose: canClose, HeadRefNote: headRefNote, | |
| 176 | 193 | CanMoveHead: canMoveHead, HeadBranches: headChoices, | |
| 194 | CanChangeBase: canChangeBase, BaseBranches: baseChoices, | ||
| 177 | 195 | CanMerge: mergeable, MergeBlocked: mergeBlocked, | |
| 178 | 196 | CanDiscuss: w.canDiscuss(r, acl), | |
| 179 | 197 | ContributorCI: contributorCI, | |