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/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software: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.

forge/landing/land.go +7−5

@@ -99,7 +99,7 @@ func Land(g Gate, id, actor string, now time.Time) (*Plan, error) {
99 99 // The base already contains the head, so there is no "before" left to
100 100 // name: plan.OldTip is the moved tip, not the one the landing started
101 101 // from. Marking without a base keeps the record honest.
102 closed, err := marked(g, id, "", plan.Head, actor, now)
102 closed, err := marked(g, id, "", plan.Head, plan.Head, actor, now)
103 103 if err != nil {
104 104 return plan, &ErrLandedUnmarked{NewTip: plan.OldTip, Err: err}
105 105 }
@@ -123,7 +123,7 @@ func Land(g Gate, id, actor string, now time.Time) (*Plan, error) {
123 123 g.Audit(fmt.Sprintf("merge %s by %s (head %s)", g.Ref(plan.Base), actor, format.SHA(plan.NewTip)))
124 124 }
125 125 }
126 closed, err := marked(g, id, plan.OldTip, plan.NewTip, actor, now)
126 closed, err := marked(g, id, plan.OldTip, plan.NewTip, plan.Head, actor, now)
127 127 if err != nil {
128 128 return plan, &ErrLandedUnmarked{NewTip: plan.NewTip, Err: err}
129 129 }
@@ -217,9 +217,11 @@ func Prepare(g Gate, id, actor string, now time.Time) (*Plan, error) {
217 217 // marked writes the merged state and turns a validation problem into an error, so a
218 218 // landing never leaves a request file the metadata reader would refuse. The base tip
219 219 // is what the branch pointed at before it moved: it is the only record of what the
220 // merge changed once the head is in the base.
221 func marked(g Gate, id, baseTip, head, actor string, now time.Time) ([]string, error) {
222 _, closed, err := metadata.MarkMerged(g.Repo, id, baseTip, head, actor, now)
220 // merge changed once the head is in the base. reviewedHead is the commit the
221 // approvals named, which is frozen into head because the followed branch may have
222 // moved after the landing was prepared.
223 func marked(g Gate, id, baseTip, head, reviewedHead, actor string, now time.Time) ([]string, error) {
224 _, closed, err := metadata.MarkMerged(g.Repo, id, baseTip, head, reviewedHead, actor, now)
223 225 if err != nil {
224 226 return nil, err
225 227 }