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.
metadata/request_write.go +44
| @@ -236,6 +236,50 @@ func SetRequestBase(g *git.Repo, id, newBase, actor string) (*Request, error) { | |||
| 236 | 236 | return out, err | |
| 237 | 237 | } | |
| 238 | 238 | ||
| 239 | // SetRequestTarget moves an open request's base and the branch it follows in one | ||
| 240 | // metadata commit. It is the writer behind the request page's single target control: | ||
| 241 | // changing both together avoids a request that briefly points at a base it was never | ||
| 242 | // reviewed against. Approvals name the old base and commit, so either change makes | ||
| 243 | // them stale. | ||
| 244 | func SetRequestTarget(g *git.Repo, id, newBase, newHead, newHeadRef, actor string) (*Request, error) { | ||
| 245 | var out *Request | ||
| 246 | dir := fmt.Sprintf("%s/%s/", RequestsDir, id) | ||
| 247 | target := newHeadRef | ||
| 248 | if target == "" { | ||
| 249 | target = format.SHA(newHead) | ||
| 250 | } | ||
| 251 | _, err := git.CommitFiles(g, Ref, func(files map[string][]byte) error { | ||
| 252 | r, err := loadFrom(files, id) | ||
| 253 | if err != nil { | ||
| 254 | return err | ||
| 255 | } | ||
| 256 | if r.State != "open" { | ||
| 257 | return fmt.Errorf("requests: %s is %s, so its target cannot move", id, r.State) | ||
| 258 | } | ||
| 259 | if r.Base == newBase && r.Head == newHead && r.HeadRef == newHeadRef { | ||
| 260 | out = r | ||
| 261 | return ErrNoChange | ||
| 262 | } | ||
| 263 | r.Base = newBase | ||
| 264 | r.Head = newHead | ||
| 265 | r.HeadRef = newHeadRef | ||
| 266 | if err := validateRequestForWrite(r); err != nil { | ||
| 267 | return err | ||
| 268 | } | ||
| 269 | raw, err := renderRequest(r) | ||
| 270 | if err != nil { | ||
| 271 | return err | ||
| 272 | } | ||
| 273 | files[dir+requestName] = raw | ||
| 274 | out = r | ||
| 275 | return nil | ||
| 276 | }, fmt.Sprintf("request: %s target %s <- %s", id, newBase, target), actor) | ||
| 277 | if errors.Is(err, ErrNoChange) { | ||
| 278 | return out, nil | ||
| 279 | } | ||
| 280 | return out, err | ||
| 281 | } | ||
| 282 | |||
| 239 | 283 | // SyncBranchHead moves every open request that follows branch to head. It is the | |
| 240 | 284 | // server-side half of following a branch: post-receive calls it after the ref has | |
| 241 | 285 | // moved, so the request names the tip a reviewer will see. It changes only head; | |