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.

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,