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.

Draw the merge control from the landing plan

097b21105b70f57fbf1c119c87ae7a75b3a44ca6 · Keenan Nemetz · 13d ago · parent b14a7819

Draw the merge control from the landing plan

The merge button was drawn from the approval and check gate, which never
looks at the tree, so a request that conflicted with its base still offered
"Merge" while the "What would land" block two sections above said it did
not merge cleanly. Clicking it answered with a bare 409 and one sentence:
the page, the diff and the conflict paths were all replaced by text.

The decision now lives in one place. landing.Prepare works out what a
landing would do — fast-forward, merge commit, already landed, or the
paths that conflict — and refuses the same things Land refuses. Land
prepares and carries out; the page prepares to decide whether to draw the
control; and the refusal path re-renders the request page with the message
beside the conflict block instead of answering with plain text.

A conflict is reported in the plan rather than as an error, because it is
an outcome the page can style: the paths are already listed by "What would
land", so the sidebar adds nothing and the button stays away.

Prepare writes one unreferenced commit object for a non-fast-forward
landing, because the approval matches a commit whose parents are the base
tip and the approved head, and that commit has to exist to be matched.
Nothing visible changes and git collects it.

server/web/request_view.go +21−7

@@ -9,8 +9,10 @@ import (
9 9 "net/http"
10 10 "sort"
11 11 "strings"
12 "time"
12 13
13 14 "github.com/rodent-software/vole/forge/access"
15 "github.com/rodent-software/vole/forge/landing"
14 16 "github.com/rodent-software/vole/forge/repo"
15 17 "github.com/rodent-software/vole/metadata"
16 18 )
@@ -119,12 +121,16 @@ func (w *Web) requestSidebar(r *http.Request, m *repo.Meta, req *metadata.Reques
119 121 var mergeable bool
120 122 var mergeBlocked string
121 123 if req.Open() && canMerge(acl, who) {
122 gate := w.landGate(m, acl, who)
123 if tip, _ := m.Git().RevParse("refs/heads/" + req.Base); tip == "" {
124 mergeBlocked = "The base branch " + req.Base + " does not exist."
125 } else if _, err := gate.Check(req.Base, req.Head, tip); err != nil {
124 // Prepare is what Land runs, so the control and the write agree about
125 // approvals, checks and conflicts. A conflict is already called out with its
126 // paths by the "What would land" block, so it does not repeat a sentence here.
127 plan, err := landing.Prepare(w.landGate(m, acl, who), req.ID, who, time.Now().UTC())
128 switch {
129 case err != nil:
126 130 mergeBlocked = err.Error()
127 } else {
131 case len(plan.Conflicts) > 0:
132 mergeBlocked = "" // the "What would land" block already names the paths
133 default:
128 134 mergeable = true
129 135 }
130 136 }
@@ -207,6 +213,14 @@ func (w *Web) requestThreadFrom(r *http.Request, m *repo.Meta, req *metadata.Req
207 213 }
208 214
209 215 func (w *Web) RequestView(rw http.ResponseWriter, r *http.Request) {
216 w.renderRequestPage(rw, r, "", http.StatusOK)
217 }
218
219 // renderRequestPage is the request page, optionally with a message at the top and a
220 // status of its own. A write that is refused for a reason the page can explain
221 // re-renders here rather than answering with bare text: the reader keeps the review,
222 // the conflict block and the controls, and the message sits beside them.
223 func (w *Web) renderRequestPage(rw http.ResponseWriter, r *http.Request, errMsg string, status int) {
210 224 m, ok := w.open(rw, r)
211 225 if !ok {
212 226 return
@@ -238,7 +252,7 @@ func (w *Web) RequestView(rw http.ResponseWriter, r *http.Request) {
238 252 }
239 253 }
240 254
241 sidebar := w.requestSidebar(r, m, req, m.Name+" · "+req.Title, "")
255 sidebar := w.requestSidebar(r, m, req, m.Name+" · "+req.Title, errMsg)
242 256 w.render(rw, "request", requestPageView{
243 257 requestSidebarView: sidebar,
244 258 Tab: "conversation",
@@ -252,7 +266,7 @@ func (w *Web) RequestView(rw http.ResponseWriter, r *http.Request) {
252 266 Thread: w.requestThreadFrom(r, m, req, commentTargetFromQuery(r), "", comments),
253 267 Conflicts: conflicts,
254 268 MergeChecked: mergeChecked,
255 }, 0)
269 }, status)
256 270 }
257 271
258 272 // requestDiff computes the three-dot patch for a change and answers whether it