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.

Let an author move their change request's head from the browser

53ff967be619894b62ca0ca69cd9a9f5f52bce01 · Keenan Nemetz · 13d ago · parent 8e1005eb

Let an author move their change request's head from the browser

A request names the exact commit under review, so a fixup pushed after it
was opened is invisible until the head moves. The only mover was
`vole change update --head`, which writes the metadata branch — a write a
contributor cannot make. The person who owns the branch was the one person
who could not keep the request current.

The request page now offers the author (or a maintainer) a branch picker
that moves an open request's head to that branch's tip. It applies the
same rule the new-request form does: the head must be a branch tip, and a
contributor may only name a branch under their own namespace, which is
what the pre-receive hook would have let them push. Approvals stay in the
tree as history and stop matching, which is what a new head already means.

SetRequestHead now refuses a closed or merged request, so the CLI and the
browser share that rule instead of the web handler owning it alone.

server/web/writes.go +58

@@ -359,6 +359,64 @@ func (w *Web) ChangeRequestState(rw http.ResponseWriter, r *http.Request) {
359 359 http.Redirect(rw, r, "/r/"+m.Name+"/requests/"+id, http.StatusSeeOther)
360 360 }
361 361
362 // UpdateRequestHead points an open change request at the tip of a branch. A request
363 // names a commit, not a branch, so a fixup pushed after it was opened is invisible
364 // until the head moves. The CLI can do that, but only by writing the metadata
365 // branch, which a contributor cannot push; this is the same move on the surface a
366 // contributor can already write to.
367 //
368 // The head must be a branch tip, and a contributor may only name a branch in their
369 // own namespace — the same rule the browser applies when opening a request, so the
370 // control offers nothing the gate would refuse.
371 func (w *Web) UpdateRequestHead(rw http.ResponseWriter, r *http.Request) {
372 ctx, ok := w.beginWrite(rw, r, maxSmallForm)
373 if !ok {
374 return
375 }
376 m := ctx.Repo
377 id := r.PathValue("id")
378 req, err := metadata.GetRequest(m.Git(), id)
379 if err != nil {
380 w.notFound(rw, r)
381 return
382 }
383 if !ctx.canTriage(req.Author) {
384 w.refuse(rw, r, "you do not have permission to move this change request's head")
385 return
386 }
387 if !req.Open() {
388 w.refuse(rw, r, "only an open change request can move its head")
389 return
390 }
391 branch := strings.TrimPrefix(strings.TrimSpace(r.PostFormValue("head")), "refs/heads/")
392 metadataBranch := strings.TrimPrefix(metadata.Ref, "refs/heads/")
393 if branch == "" || branch == metadataBranch || !m.Git().ValidBranch(branch) {
394 http.Error(rw, "choose the branch whose tip should be reviewed", http.StatusBadRequest)
395 return
396 }
397 headRef := "refs/heads/" + branch
398 if contributorOnly(ctx.ACL, ctx.Name) && !access.IsContributorRef(ctx.Name, headRef) {
399 w.refuse(rw, r, "contributors may only propose a branch in their own namespace")
400 return
401 }
402 tip, err := m.Git().RevParse(headRef)
403 if err != nil || tip == "" {
404 http.Error(rw, "that branch does not exist; push it first", http.StatusBadRequest)
405 return
406 }
407 if tip != req.Head {
408 if _, err := metadata.SetRequestHead(m.Git(), id, tip, ctx.Name); err != nil {
409 w.serverError(rw, r, "cannot move the change request's head: "+err.Error())
410 return
411 }
412 w.audit("web.request.head", ctx.Name, m.Name+" "+id+" "+req.Head+"->"+tip)
413 if problems := metadata.ValidateRequests(m.Git(), metadata.Ref); len(problems) > 0 {
414 w.Logger("web: request head %s left the request tree invalid: %v", id, problems)
415 }
416 }
417 http.Redirect(rw, r, "/r/"+m.Name+"/requests/"+id, http.StatusSeeOther)
418 }
419
362 420 // landGate is the landing gate wired to the stores this process already holds. The
363 421 // run queue is the web's own open handle, not a path: the server is long-lived and
364 422 // the gate must not close it.