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