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