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.

Test the merge result before landing it

e5478085e5b53a2892d0649d6f07c6f0a5653378 · Keenan Nemetz · 11d ago · parent db5e4382

Test the merge result before landing it

A protected branch could receive a non-fast-forward landing whose tree was
never built: the gate asked for checks on the request head, and the merge
commit was made at landing time. A pushed merge with the right parents but
extra content was accepted too.

Publish the merge commit under refs/vole/candidates/, test it as the base
branch, and make the gate require its results. Approvals name the head and
the base branch, so a moved base retests without a new approval. A merger
who clicks while that run is still going waits for it instead of clicking
again. A pushed merge commit now has to carry exactly the tree a clean merge
would produce.

Closes: 01M61QQ56TESQ1HTMB1Z5C3733

server/ssh/merge.go +35−1

@@ -58,7 +58,41 @@ func (s *Server) serveChangeMerge(ch xssh.Channel, argv []string, user, remote s
58 58 Role: a.EffectiveRole(user, s.cfg.Identity.IsAdmin(user)),
59 59 Runs: func() (*runs.Store, error) { return s.cfg.Runs, nil },
60 60 }
61 res, err := landing.Land(gate, id, user, time.Now().UTC())
61 now := time.Now().UTC()
62 // Ask the same plan the page does. If the combined result is still building, a
63 // CLI merge becomes the same "merge when ready" wait rather than an error the
64 // caller has to come back and retry.
65 plan, err := landing.Prepare(gate, id, user, now)
66 if err != nil {
67 fmt.Fprintf(ch, "vole: %v\n", err)
68 return 1
69 }
70 if len(plan.Conflicts) > 0 {
71 fmt.Fprintf(ch, "vole: %s does not merge into %s: %s conflict. Resolve it on the branch and push again\n",
72 format.SHA(plan.Head), plan.Base, strings.Join(plan.Conflicts, ", "))
73 return 1
74 }
75 if plan.Blocked != "" {
76 fmt.Fprintf(ch, "vole: %s\n", plan.Blocked)
77 return 1
78 }
79 if !plan.Ready {
80 if plan.Pending || len(plan.Gaps) == 0 {
81 if s.cfg.Runs == nil {
82 fmt.Fprint(ch, "vole: this instance has no CI queue, so the combined result cannot be tested\n")
83 return 1
84 }
85 if err := s.cfg.Runs.ArmAutoMerge(name, id, user, now); err != nil {
86 fmt.Fprintf(ch, "vole: cannot arm merge: %v\n", err)
87 return 1
88 }
89 fmt.Fprintf(ch, "merging %s into %s when the combined result passes\n", id, plan.Base)
90 return 0
91 }
92 fmt.Fprintf(ch, "vole: the combined result failed:\n %s\n", strings.Join(plan.Gaps, "\n "))
93 return 1
94 }
95 res, err := landing.Land(gate, id, user, now)
62 96 if err != nil {
63 97 fmt.Fprintf(ch, "vole: %v\n", err)
64 98 return 1