# zit 0.3.38
zit 0.3.38 is about the person whose push onto a protected branch is refused.
The refusal now says how to get out, the way out works whatever shape the
push took, and behind a front that allows it, pushing again keeps the work on
a branch of its own instead of being refused again. It also closes five ways
past branch protection.
No artifact, tag namespace, or on-disk repository format changed. The cache
beside a repository gained a column and rebuilds once from its saved records.
## A refusal names the way out
A `zit serve --fronted` that refuses a push onto a protected branch now says
what to do:
```
check-in 9e1c5d97f9be would join protected branch 'trunk'; move it with
`amend 9e1c5d97f9be --branch NAME` and push again
```
That amend works for every way a check-in can land on trunk. Before, it
worked only for a check-in that joined trunk through its parent. A check-in
committed with `--branch trunk`, or one the pusher had already moved onto
trunk with `amend`, was refused again on every later push, and only a fresh
clone got out.
The reason is that the server now asks the same question its own derive
answers. It replays the pushed artifacts over the check-ins they build on,
newest move winning as in Fossil, and reads off where each pushed check-in
ends up. It replays them in both orders the server's log can hold them in and
refuses if either lands on a protected branch.
## Pushing again keeps the work
A front can send `X-Zit-Divert-Login` with the login it authenticated. Then
the first refusal says what the next push will do:
```
check-in 9e1c5d97f9be would join protected branch 'trunk'. Your next push,
including the autosync after your next commit, keeps it on branch
attempt/jan/9e1c5d97f9be; or move it yourself with `amend 9e1c5d97f9be --branch NAME`.
```
and the next push lands there. The server writes the move itself, as a
control artifact recorded as `zit-serve`, in the same commit as the push, so
the protected branch never shows the check-in. It is dated after everything
in the push, so a pusher's clock running ahead cannot keep the check-in on
trunk. The reply says where the work went; stock Fossil prints it as "Server
says: ...", and a `fossil sync` brings the move back in the same exchange. An
`X-Zit-Diverted` response header tells the front, which can open a landing
from the attempt branch.
Nothing lands without the pusher having been told: a check-in is moved only
after a refusal for it, kept for a week in `.zit-cache`. Fossil's
`autosync-tries` retry counts as pushing again; the same command then prints
both the refusal and where the work went.
## The client says what the server said
`zit push` and `zit sync` print the server's messages as `server: ...`.
Before, zit read them and showed nothing. When the server kept a pushed
check-in on an attempt branch, `zit push` pulls the move straight away, so
the local repository agrees with the server before the next commit. `zit
sync` now quotes a refusal in the server's own words, as `zit push` already
did.
## Five ways past protection, closed
Each needed a hand-made artifact or an unusual history; none is what `zit` or
`fossil` writes in ordinary use.
- A pushed amend dated in the past could "move" a check-in off trunk that the
derive kept on trunk.
- A check-in whose parent the server did not hold landed on trunk, the main
branch, until a parent that might never come arrived. It is now refused
until the parent is pushed, when the main branch is protected.
- A check-in branching off trunk without cancelling the inherited `sym-trunk`
made the name `trunk` resolve to it.
- Cancelling the `branch` tag of a held check-in dropped it onto the main
branch.
- Retagging a held trunk check-in onto another branch removed it from trunk.
## Compatibility
- No artifact or wire format changed; Fossil RFC revision 00 remains the
target. The new headers pass only between a front and a `--fronted`
server, and the new pragma is one stock Fossil ignores.
- The repository cache gained per-tag dates. A cache written by an older
release is rebuilt from its saved records on first open.
- A push to a protected branch that builds on a check-in the server does not
have is now refused when the main branch is protected, naming the parent to
push first.
- `fossil push` and `fossil sync` exit 0 even after printing a refusal; a
script driving stock Fossil cannot tell from the exit status. zit's own
client exits non-zero.
## Qualification
`zig fmt --check`, `zig build test`, `e2e`, `smoke` (now 19 scenarios, the
new one a stock Fossil 2.28 pushing onto a protected branch through a front
that diverts), `git-import-e2e`, `git-export-e2e`, and `dst`, plus the
three-runner battery.