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.

Compact the change request target controls

41d1fee900568b1bff5c7cff3b4c52942f47fabd · Keenan Nemetz · 13d ago · parent e050e14a

Compact the change request target controls

Replace the separate base and head forms and the duplicated labels with one
row: base picker, head picker, update. The pickers preselect the current
targets, and a pinned head appears as the current commit. Collapse the help
text to one line.

Post both targets to one /target handler and write them in one metadata
commit. Either change invalidates approvals; keeping the current value is
allowed even if its branch has gone missing so the other side can still be
repaired.

server/web/writes.go +51−69

@@ -369,15 +369,15 @@ func (w *Web) ChangeRequestState(rw http.ResponseWriter, r *http.Request) {
369 369 http.Redirect(rw, r, "/r/"+m.Name+"/requests/"+id, http.StatusSeeOther)
370 370 }
371 371
372 // UpdateRequestHead repoints an open change request at another branch. The request
373 // already follows the branch it was opened from, so this is only for changing which
374 // branch it follows, or for attaching a request that was opened from a bare commit.
375 // Pushes move the head from then on.
372 // UpdateRequestTarget moves an open request's base and the branch it follows. The
373 // request page posts both together because the two targets are one review surface;
374 // changing either one makes approvals that named the old base and commit stale.
376 375 //
377 // The head must be a branch tip, and a contributor may only name a branch in their
378 // own namespace — the same rule the browser applies when opening a request, so the
379 // control offers nothing the gate would refuse.
380 func (w *Web) UpdateRequestHead(rw http.ResponseWriter, r *http.Request) {
376 // A value that is already the request's target is kept even if its branch has since
377 // been deleted: that lets a reader change only the other side without first having
378 // to repair a missing branch. A value that is changing has to name a branch that
379 // exists, and a contributor may only name a head branch in their own namespace.
380 func (w *Web) UpdateRequestTarget(rw http.ResponseWriter, r *http.Request) {
381 381 ctx, ok := w.beginWrite(rw, r, maxSmallForm)
382 382 if !ok {
383 383 return
@@ -390,84 +390,66 @@ func (w *Web) UpdateRequestHead(rw http.ResponseWriter, r *http.Request) {
390 390 return
391 391 }
392 392 if !ctx.canTriage(req.Author) {
393 w.refuse(rw, r, "you do not have permission to move this change request's head")
393 w.refuse(rw, r, "you do not have permission to change this change request's target")
394 394 return
395 395 }
396 396 if !req.Open() {
397 w.refuse(rw, r, "only an open change request can move its head")
397 w.refuse(rw, r, "only an open change request can change its target")
398 398 return
399 399 }
400 branch := strings.TrimPrefix(strings.TrimSpace(r.PostFormValue("head")), "refs/heads/")
400
401 base := strings.TrimPrefix(strings.TrimSpace(r.PostFormValue("base")), "refs/heads/")
402 head := strings.TrimPrefix(strings.TrimSpace(r.PostFormValue("head")), "refs/heads/")
401 403 metadataBranch := strings.TrimPrefix(metadata.Ref, "refs/heads/")
402 if branch == "" || branch == metadataBranch || !m.Git().ValidBranch(branch) {
403 http.Error(rw, "choose the branch whose tip should be reviewed", http.StatusBadRequest)
404 return
405 }
406 headRef := "refs/heads/" + branch
407 if contributorOnly(ctx.ACL, ctx.Name) && !access.IsContributorRef(ctx.Name, headRef) {
408 w.refuse(rw, r, "contributors may only propose a branch in their own namespace")
404 if base == "" || base == metadataBranch || !m.Git().ValidBranch(base) {
405 http.Error(rw, "choose the branch this change should land on", http.StatusBadRequest)
409 406 return
410 407 }
411 tip, err := m.Git().RevParse(headRef)
412 if err != nil || tip == "" {
413 http.Error(rw, "that branch does not exist; push it first", http.StatusBadRequest)
414 return
408 if base != req.Base {
409 if tip, err := m.Git().RevParse("refs/heads/" + base); err != nil || tip == "" {
410 http.Error(rw, "that base branch does not exist", http.StatusBadRequest)
411 return
412 }
415 413 }
416 if tip != req.Head || branch != req.HeadRef {
417 if _, err := metadata.SetRequestHead(m.Git(), id, tip, branch, ctx.Name); err != nil {
418 w.serverError(rw, r, "cannot move the change request's head: "+err.Error())
414
415 headTip, headRef := req.Head, req.HeadRef
416 switch {
417 case head == "":
418 // The picker showed a pinned commit and the reader did not choose a branch.
419 case head == req.HeadRef:
420 // The current branch: follow it if it still exists, otherwise keep the last
421 // tip. A reader who is only changing the base must not have to repair the
422 // head first.
423 if tip, err := m.Git().RevParse("refs/heads/" + head); err == nil && tip != "" {
424 headTip = tip
425 }
426 default:
427 if head == metadataBranch || !m.Git().ValidBranch(head) {
428 http.Error(rw, "choose the branch whose tip should be reviewed", http.StatusBadRequest)
419 429 return
420 430 }
421 w.audit("web.request.head", ctx.Name, m.Name+" "+id+" "+req.Head+"->"+tip+" following "+branch)
422 if problems := metadata.ValidateRequests(m.Git(), metadata.Ref); len(problems) > 0 {
423 w.Logger("web: request head %s left the request tree invalid: %v", id, problems)
431 ref := "refs/heads/" + head
432 if contributorOnly(ctx.ACL, ctx.Name) && !access.IsContributorRef(ctx.Name, ref) {
433 w.refuse(rw, r, "contributors may only propose a branch in their own namespace")
434 return
424 435 }
436 tip, err := m.Git().RevParse(ref)
437 if err != nil || tip == "" {
438 http.Error(rw, "that head branch does not exist; push it first", http.StatusBadRequest)
439 return
440 }
441 headTip, headRef = tip, head
425 442 }
426 http.Redirect(rw, r, "/r/"+m.Name+"/requests/"+id, http.StatusSeeOther)
427 }
428 443
429 // UpdateRequestBase retargets an open change request at another base branch. This is
430 // how a stacked request is repointed when the branch below it lands, or when it
431 // should land somewhere else. A new base changes the diff, so the approvals that
432 // named the old base stop counting.
433 func (w *Web) UpdateRequestBase(rw http.ResponseWriter, r *http.Request) {
434 ctx, ok := w.beginWrite(rw, r, maxSmallForm)
435 if !ok {
436 return
437 }
438 m := ctx.Repo
439 id := r.PathValue("id")
440 req, err := metadata.GetRequest(m.Git(), id)
441 if err != nil {
442 w.notFound(rw, r)
443 return
444 }
445 if !ctx.canTriage(req.Author) {
446 w.refuse(rw, r, "you do not have permission to change this change request's base")
447 return
448 }
449 if !req.Open() {
450 w.refuse(rw, r, "only an open change request can change its base")
451 return
452 }
453 base := strings.TrimPrefix(strings.TrimSpace(r.PostFormValue("base")), "refs/heads/")
454 metadataBranch := strings.TrimPrefix(metadata.Ref, "refs/heads/")
455 if base == "" || base == metadataBranch || !m.Git().ValidBranch(base) {
456 http.Error(rw, "choose the branch this change should land on", http.StatusBadRequest)
457 return
458 }
459 if tip, err := m.Git().RevParse("refs/heads/" + base); err != nil || tip == "" {
460 http.Error(rw, "that base branch does not exist", http.StatusBadRequest)
461 return
462 }
463 if base != req.Base {
464 if _, err := metadata.SetRequestBase(m.Git(), id, base, ctx.Name); err != nil {
465 w.serverError(rw, r, "cannot change the change request's base: "+err.Error())
444 if base != req.Base || headTip != req.Head || headRef != req.HeadRef {
445 if _, err := metadata.SetRequestTarget(m.Git(), id, base, headTip, headRef, ctx.Name); err != nil {
446 w.serverError(rw, r, "cannot update the change request's target: "+err.Error())
466 447 return
467 448 }
468 w.audit("web.request.base", ctx.Name, m.Name+" "+id+" "+req.Base+"->"+base)
449 w.audit("web.request.target", ctx.Name,
450 m.Name+" "+id+" "+req.Base+"<-"+req.HeadRef+" -> "+base+"<-"+headRef)
469 451 if problems := metadata.ValidateRequests(m.Git(), metadata.Ref); len(problems) > 0 {
470 w.Logger("web: request base %s left the request tree invalid: %v", id, problems)
452 w.Logger("web: request target %s left the request tree invalid: %v", id, problems)
471 453 }
472 454 }
473 455 http.Redirect(rw, r, "/r/"+m.Name+"/requests/"+id, http.StatusSeeOther)