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/requests.go +34−19

@@ -33,36 +33,51 @@ type requestFormData struct {
33 33 }
34 34
35 35 func (w *Web) requestForm(r *http.Request, m *repo.Meta) requestFormData {
36 var branches []string
36 branches := branchNames(m)
37 accountID := w.accountName(r)
38 acl, _ := m.ACL()
39 canOpen := acl != nil && accountID != "" && acl.CanOpenRequest(accountID)
40 return requestFormData{
41 Base: m.DefaultBranch, BaseBranches: branches,
42 HeadBranches: headBranches(branches, acl, accountID), CanOpen: canOpen,
43 }
44 }
45
46 // branchNames lists the repository's normal branches, without the metadata branch.
47 // The new-request form and the control that moves a request's head draw their
48 // choices from the same list, so a branch is offered in both or in neither.
49 func branchNames(m *repo.Meta) []string {
37 50 metadataBranch := strings.TrimPrefix(metadata.Ref, "refs/heads/")
51 var out []string
38 52 if refs, err := m.Git().ListRefs("refs/heads/"); err == nil {
39 53 for ref := range refs {
40 54 name := strings.TrimPrefix(ref, "refs/heads/")
41 55 if name == "" || name == metadataBranch {
42 56 continue
43 57 }
44 branches = append(branches, name)
58 out = append(out, name)
45 59 }
46 sort.Strings(branches)
60 sort.Strings(out)
47 61 }
48 baseBranches := branches
49 headBranches := branches
50 accountID := w.accountName(r)
51 acl, _ := m.ACL()
52 canOpen := acl != nil && accountID != "" && acl.CanOpenRequest(accountID)
53 if acl != nil && acl.RoleFor(accountID) == access.Contribute {
54 name := accountID
55 headBranches = nil
56 for _, branch := range branches {
57 if access.IsContributorRef(name, "refs/heads/"+branch) {
58 headBranches = append(headBranches, branch)
59 }
60 }
62 return out
63 }
64
65 // headBranches narrows the choices to the branches an account may propose as a
66 // change request's head: any normal branch for a maintainer, only its own
67 // contrib/ namespace for a contributor. That is the same rule the pre-receive hook
68 // applies to the account's pushes, so a control cannot offer a branch the hook
69 // would refuse.
70 func headBranches(all []string, acl *access.ACL, account string) []string {
71 if acl == nil || account == "" || acl.RoleFor(account) != access.Contribute {
72 return all
61 73 }
62 return requestFormData{
63 Base: m.DefaultBranch, BaseBranches: baseBranches,
64 HeadBranches: headBranches, CanOpen: canOpen,
74 out := make([]string, 0, len(all))
75 for _, branch := range all {
76 if access.IsContributorRef(account, "refs/heads/"+branch) {
77 out = append(out, branch)
78 }
65 79 }
80 return out
66 81 }
67 82
68 83 type requestRow struct {