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.
Make change requests follow branches and allow base retargeting
e050e14a64f6f19c1b1478c4c073d690ef3d3129 · Keenan Nemetz · 13d ago · parent 59464445
Make change requests follow branches and allow base retargeting A request opened from a branch now follows it: post-receive moves the request's head to the new tip after an accepted push, so approvals and checks that named the old tip stop counting. Approvals stay bound to an exact base and commit; the approval writers now require the base and head the approver saw, so a push or retarget cannot silently approve a target nobody reviewed. Merged requests freeze the reviewed head. Add `vole change update --base` and a Change base control for stacked requests. A new base changes the diff and therefore invalidates approvals, which is what retargeting an upper request after its lower branch lands needs. Base names may now contain slashes. Also fix a second-boundary flake in the landing test.
docs/change-requests.md +45−11
| @@ -1,8 +1,9 @@ | |||
| 1 | 1 | # Change requests | |
| 2 | 2 | ||
| 3 | 3 | Review should not depend on a web session or a vendor. In Vole, a change request | |
| 4 | is a normal branch and a normal push. Vole ties each approval to an exact commit, | ||
| 5 | so if the branch changes after approval, the approval no longer applies. | ||
| 4 | is a normal branch and a normal push. A request opened from a branch follows that | ||
| 5 | branch: every push moves the request's head, so an approval always names the exact | ||
| 6 | commit it reviewed and a moved branch starts the review over. | ||
| 6 | 7 | ||
| 7 | 8 | ## Files behind a request | |
| 8 | 9 | ||
| @@ -38,19 +39,29 @@ vole change approve <id> --as <your-name> | |||
| 38 | 39 | git push origin feature:refs/heads/main | |
| 39 | 40 | ``` | |
| 40 | 41 | ||
| 41 | The request records the commit under review in `head:` and the branch it was opened | ||
| 42 | from in `head_ref:`, and the request page, the request list and `vole change show` | ||
| 43 | show both. The branch is a display hint, not part of the review: approvals, checks, | ||
| 44 | the diff and the landing gate all name the commit, and when the branch moves past it | ||
| 45 | the page says so instead of presenting the new tip as reviewed. A request opened | ||
| 46 | from a bare commit id records no branch. | ||
| 42 | The request records the branch it follows in `head_ref:` and the commit currently | ||
| 43 | under review in `head:`. The request page, the request list and `vole change show` | ||
| 44 | show both. After every accepted push, Vole moves `head:` to the new tip of the | ||
| 45 | followed branch, so a fixup does not need a second command. Approvals and checks | ||
| 46 | still name a commit: the approvals that named the old tip stop counting, and the new | ||
| 47 | tip needs its own CI result. An approval is never carried silently to a commit nobody | ||
| 48 | reviewed — the approver's form names the base and head it displayed, and the writer | ||
| 49 | refuses if a push or a retarget moved either one in between. | ||
| 50 | |||
| 51 | A request opened from a bare commit id records no branch and stays pinned to that | ||
| 52 | commit. `vole change update <id> --head <branch>` makes an open request follow a | ||
| 53 | branch (or a different one); `--head <commit>` pins it to one commit. The request | ||
| 54 | page's **Repoint request** control does the same for a branch. Neither is needed | ||
| 55 | after a normal push: the request already follows the branch it was opened from. A | ||
| 56 | branch that is deleted leaves the last tip it saw in `head:` and the page says the | ||
| 57 | branch is gone. | ||
| 47 | 58 | ||
| 48 | 59 | Other commands: | |
| 49 | 60 | ||
| 50 | 61 | ```sh | |
| 51 | 62 | vole change list [--state open|closed] | |
| 52 | 63 | vole change show <id> | |
| 53 | vole change update <id> [--head <rev>] [--closes <issue>]... [--no-closes] | ||
| 64 | vole change update <id> [--base <branch>] [--head <rev>] [--closes <issue>]... [--no-closes] | ||
| 54 | 65 | vole change close <id> | |
| 55 | 66 | vole change reopen <id> | |
| 56 | 67 | ``` | |
| @@ -59,6 +70,24 @@ vole change reopen <id> | |||
| 59 | 70 | needed commits are available locally. If they are not, it says so rather than | |
| 60 | 71 | guessing. | |
| 61 | 72 | ||
| 73 | ## Stacked requests | ||
| 74 | |||
| 75 | A request may target another feature branch instead of `main`. Push the lower | ||
| 76 | branch first, then open the upper request with `--base <lower-branch> --head | ||
| 77 | <upper-branch>`. Its diff is only the upper change, not everything the lower branch | ||
| 78 | adds as well. | ||
| 79 | |||
| 80 | When the lower request lands, retarget the upper one: | ||
| 81 | |||
| 82 | ```sh | ||
| 83 | vole change update <id> --base main | ||
| 84 | ``` | ||
| 85 | |||
| 86 | The request page's **Change base** control does the same. A new base changes the | ||
| 87 | diff, and every approval named the old base, so the upper request needs a fresh | ||
| 88 | review against the new base. Changing the base does not change the branch the | ||
| 89 | request follows; pass `--head` as well only if the upper branch itself moved. | ||
| 90 | |||
| 62 | 91 | ## Linking the issues it closes | |
| 63 | 92 | ||
| 64 | 93 | A change request names the issues it fixes in its own metadata, not in its | |
| @@ -90,11 +119,16 @@ its issues open. | |||
| 90 | 119 | ||
| 91 | 120 | A protected branch only accepts an update if the new tip is the approved commit, | |
| 92 | 121 | or a merge of that commit with the current branch. That is why "merge, then push" | |
| 93 | works, but adding an extra commit after approval does not. | ||
| 122 | works, but adding an extra commit after approval does not. Because a request follows | ||
| 123 | its branch, pushing that extra commit moves the request's head and leaves the old | ||
| 124 | approval naming a commit that is no longer the head. | ||
| 94 | 125 | ||
| 95 | 126 | An approval can also be recorded from the request page in the browser when the | |
| 96 | 127 | signed-in account has maintain access. The approver is the session's account name, not | |
| 97 | a name from the page. | ||
| 128 | a name from the page. The page's Approve form carries the base and head it | ||
| 129 | displayed; if a push moved the branch, or the request was retargeted, before the form | ||
| 130 | was submitted, the approval is refused and the approver sees the new target instead | ||
| 131 | of approving it unseen. | ||
| 98 | 132 | ||
| 99 | 133 | An approval is stored at `approvals/<account-name>.md`. Only the account named in | |
| 100 | 134 | that path can create, change, or remove it. `request.md` records the author's | |