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/request_view.go +40−26
| @@ -34,6 +34,30 @@ type requestCheck struct { | |||
| 34 | 34 | Exit int | |
| 35 | 35 | } | |
| 36 | 36 | ||
| 37 | // branchOption is one choice in the request target picker. The selected flag is | ||
| 38 | // built here so the template does not have to compare strings, and so a branch that | ||
| 39 | // has gone missing can stay visible as the current value instead of silently | ||
| 40 | // selecting a different branch. | ||
| 41 | type branchOption struct { | ||
| 42 | Name string | ||
| 43 | Selected bool | ||
| 44 | } | ||
| 45 | |||
| 46 | func branchOptions(names []string, selected string) []branchOption { | ||
| 47 | out := make([]branchOption, 0, len(names)+1) | ||
| 48 | found := false | ||
| 49 | for _, name := range names { | ||
| 50 | if name == selected { | ||
| 51 | found = true | ||
| 52 | } | ||
| 53 | out = append(out, branchOption{Name: name, Selected: name == selected}) | ||
| 54 | } | ||
| 55 | if selected != "" && !found { | ||
| 56 | out = append([]branchOption{{Name: selected, Selected: true}}, out...) | ||
| 57 | } | ||
| 58 | return out | ||
| 59 | } | ||
| 60 | |||
| 37 | 61 | // requestCI is the approval a contributor branch needs before its workflow may | |
| 38 | 62 | // run. It belongs on the request because that is where a maintainer reviews the | |
| 39 | 63 | // code the tip contains. | |
| @@ -60,21 +84,15 @@ type requestSidebarView struct { | |||
| 60 | 84 | // the head with the branch, so "branch moved" means that sync did not happen and | |
| 61 | 85 | // the approval control is withheld until the request catches up. | |
| 62 | 86 | HeadRefNote string | |
| 63 | // CanMoveHead is the control that repoints an open request at another branch. | ||
| 64 | // A request opened from a branch follows it automatically, so this is only for | ||
| 65 | // changing which branch it follows (or attaching a request opened from a commit). | ||
| 66 | // It is offered to the author and to maintainers, and only while the request is | ||
| 67 | // open: a merged request's head is the record of what landed. | ||
| 68 | CanMoveHead bool | ||
| 69 | // HeadBranches are the branches that control may name: every normal branch for | ||
| 70 | // a maintainer, the viewer's own namespace for a contributor. | ||
| 71 | HeadBranches []string | ||
| 72 | // CanChangeBase is the control that retargets a stacked request at another base. | ||
| 73 | // A new base changes the diff, so the approvals that named the old base stop | ||
| 74 | // counting. | ||
| 75 | CanChangeBase bool | ||
| 76 | // BaseBranches are the branches that control may name: every normal branch. | ||
| 77 | BaseBranches []string | ||
| 87 | // CanRetarget is the combined base/head control. It is offered to the author and | ||
| 88 | // to maintainers, and only while the request is open: a merged request's target is | ||
| 89 | // the record of what landed. A request opened from a branch follows it already; | ||
| 90 | // this is for changing which branch, or attaching a request opened from a commit. | ||
| 91 | CanRetarget bool | ||
| 92 | // BaseOptions and HeadOptions are the branches the picker may name. HeadOptions | ||
| 93 | // is narrowed to the viewer's own namespace for a contributor. | ||
| 94 | BaseOptions []branchOption | ||
| 95 | HeadOptions []branchOption | ||
| 78 | 96 | // CanMerge is the merge control: true only when the gate would accept the | |
| 79 | 97 | // landing now, so the button never offers something the handler would refuse. | |
| 80 | 98 | CanMerge bool | |
| @@ -148,15 +166,12 @@ func (w *Web) requestSidebar(r *http.Request, m *repo.Meta, req *metadata.Reques | |||
| 148 | 166 | // approve it while the followed branch is known to be ahead: the page is showing | |
| 149 | 167 | // an older tip than the branch, and the head control is the way to catch up. | |
| 150 | 168 | approvable := req.Open() && canApproveRequest(acl, who, req.Author) && headRefNote != "branch moved" | |
| 151 | canMoveHead := req.Open() && canTriage(acl, who, req.Author) | ||
| 152 | var headChoices []string | ||
| 153 | if canMoveHead { | ||
| 154 | headChoices = headBranches(branchNames(m), acl, who) | ||
| 155 | } | ||
| 156 | canChangeBase := req.Open() && canTriage(acl, who, req.Author) | ||
| 157 | var baseChoices []string | ||
| 158 | if canChangeBase { | ||
| 159 | baseChoices = branchNames(m) | ||
| 169 | canRetarget := req.Open() && canTriage(acl, who, req.Author) | ||
| 170 | var baseOptions, headOptions []branchOption | ||
| 171 | if canRetarget { | ||
| 172 | branches := branchNames(m) | ||
| 173 | baseOptions = branchOptions(branches, req.Base) | ||
| 174 | headOptions = branchOptions(headBranches(branches, acl, who), req.HeadRef) | ||
| 160 | 175 | } | |
| 161 | 176 | // Preflight the landing rather than guessing from the approval count: the same | |
| 162 | 177 | // gate the handler calls is asked here, so the button and the write agree about | |
| @@ -190,8 +205,7 @@ func (w *Web) requestSidebar(r *http.Request, m *repo.Meta, req *metadata.Reques | |||
| 190 | 205 | Page: w.page(r, title), Name: m.Name, Request: req, | |
| 191 | 206 | Approvals: approvals, Checks: checks, Required: required, Current: current, | |
| 192 | 207 | CanApprove: approvable, CanClose: canClose, HeadRefNote: headRefNote, | |
| 193 | CanMoveHead: canMoveHead, HeadBranches: headChoices, | ||
| 194 | CanChangeBase: canChangeBase, BaseBranches: baseChoices, | ||
| 208 | CanRetarget: canRetarget, BaseOptions: baseOptions, HeadOptions: headOptions, | ||
| 195 | 209 | CanMerge: mergeable, MergeBlocked: mergeBlocked, | |
| 196 | 210 | CanDiscuss: w.canDiscuss(r, acl), | |
| 197 | 211 | ContributorCI: contributorCI, | |