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.

Draw the merge control from the landing plan

097b21105b70f57fbf1c119c87ae7a75b3a44ca6 · Keenan Nemetz · 14d 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/writes.go +10−5

@@ -385,16 +385,21 @@ func (w *Web) MergeRequest(rw http.ResponseWriter, r *http.Request) {
385 385 http.Error(rw, "this change request is not open", http.StatusBadRequest)
386 386 return
387 387 }
388 res, err := landing.Land(w.landGate(m, ctx.ACL, ctx.Name), id, ctx.Name, time.Now().UTC())
388 _, err = landing.Land(w.landGate(m, ctx.ACL, ctx.Name), id, ctx.Name, time.Now().UTC())
389 389 if err != nil {
390 // The gate's refusal is the answer, so it is shown as written: it names the
391 // missing approval, the failing check, or the conflicting path.
392 http.Error(rw, err.Error(), http.StatusConflict)
390 // The refusal is the answer, so it is shown as written: it names the missing
391 // approval, the failing check, or the conflicting path. It is shown *on the
392 // page* rather than as bare text, so the reader keeps the conflict block and
393 // the diff that explain it.
394 if htmxRequest(r) {
395 w.render(rw, "request_sidebar", w.requestSidebar(r, m, req, "", err.Error()), 0)
396 return
397 }
398 w.renderRequestPage(rw, r, err.Error(), http.StatusConflict)
393 399 return
394 400 }
395 401 if problems := metadata.ValidateRequests(m.Git(), metadata.Ref); len(problems) > 0 {
396 402 w.Logger("web: merge left the request tree invalid: %v", problems)
397 403 }
398 404 http.Redirect(rw, r, "/r/"+m.Name+"/requests/"+id, http.StatusSeeOther)
399 _ = res
400 405 }