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/hook/hook.go +22−1

@@ -587,8 +587,12 @@ func reject(w io.Writer, format string, args ...any) {
587 587 // Nothing here may fail the push. A push that landed is done, and a full disk must not
588 588 // turn it into an error the client reports as a rejected update.
589 589 func PostReceive(cfg Config) int {
590 if cfg.Out == nil {
591 cfg.Out = io.Discard
592 }
590 593 repo := repoNameFromDir(cfg.RepoDir)
591 who := strings.TrimSpace(cfg.Actor)
594 actor := strings.TrimSpace(cfg.Actor)
595 who := actor
592 596 if who == "" {
593 597 who = "unknown"
594 598 }
@@ -601,6 +605,23 @@ func PostReceive(cfg Config) int {
601 605 old, next, ref := fields[0], fields[1], fields[2]
602 606 auditLine(cfg.AuditPath, fmt.Sprintf("push %s %s -> %s by %s in %s",
603 607 ref, shortOrNone(old), shortOrNone(next), who, repo))
608 // A change request opened from a branch follows it: the push that just moved
609 // the ref moves the request's head in the same breath. It runs here, after the
610 // ref update, because the new tip is the event; pre-receive could only guess.
611 // Nothing about this may fail the push, and a sync that does not happen leaves
612 // the previous (already reviewed) head in place rather than approving new work.
613 if strings.HasPrefix(ref, "refs/heads/") && ref != metadata.Ref &&
614 next != "" && next != git.ZeroSHA && actor != "" {
615 branch := strings.TrimPrefix(ref, "refs/heads/")
616 n, err := metadata.SyncBranchHead(git.Open(cfg.RepoDir), branch, next, actor)
617 switch {
618 case err != nil:
619 fmt.Fprintf(cfg.Out, "vole: could not move requests following %s: %v\n", branch, err)
620 case n > 0:
621 auditLine(cfg.AuditPath, fmt.Sprintf("request-follow %s -> %s by %s in %s (%d request(s))",
622 ref, shortOrNone(next), actor, repo, n))
623 }
624 }
604 625 if cfg.SpoolDir == "" {
605 626 continue
606 627 }