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.
metadata/request_write.go +7
| @@ -158,6 +158,10 @@ func GetRequest(g *git.Repo, id string) (*Request, error) { | |||
| 158 | 158 | ||
| 159 | 159 | // SetHead repoints a request at a new commit. The approvals stay in the tree but | |
| 160 | 160 | // stop matching, because they name the commit they reviewed. | |
| 161 | // | ||
| 162 | // Only an open request may move: a merged request's head is the record of what | ||
| 163 | // landed, and a closed one is not going to land at all. Refusing here rather than | ||
| 164 | // in each caller is what keeps the CLI and the browser on the same rule. | ||
| 161 | 165 | func SetRequestHead(g *git.Repo, id, head, actor string) (*Request, error) { | |
| 162 | 166 | var out *Request | |
| 163 | 167 | base := fmt.Sprintf("%s/%s/", RequestsDir, id) | |
| @@ -166,6 +170,9 @@ func SetRequestHead(g *git.Repo, id, head, actor string) (*Request, error) { | |||
| 166 | 170 | if err != nil { | |
| 167 | 171 | return err | |
| 168 | 172 | } | |
| 173 | if r.State != "open" { | ||
| 174 | return fmt.Errorf("requests: %s is %s, so its head cannot move", id, r.State) | ||
| 175 | } | ||
| 169 | 176 | r.Head = head | |
| 170 | 177 | if err := validateRequestForWrite(r); err != nil { | |
| 171 | 178 | return err | |