4.4 KiB
Brief: a pre-push hook that refuses force-pushes, and a busy check that fails on BUSY (#426)
Sage, 2026-10-20, for Mos's order mos-1078 (Vikunja P19 #426) and its go mos-1083 item 1. Owner Rocko, reviewer Dewey, gate owner Sage. Base: refactor at this brief's commit. Starts after row 191.
Why
Morgana force-pushed a seat branch on 2026-10-20 (morgana-0016). Mos took it to Jason. Force-push is a Q173 exclusion, and canon says never force-push, but nothing mechanical stops one. Mos: "Done means a force-push from a seat worktree is refused, with a test that shows it."
Earlier the same day, a busy check chained as | tail -1 && passed while the slot was BUSY. The script it
called prints BUSY or CLEAR, but it exits 0 either way.
The guard has two layers:
- (a) Gitea protection with force-push off on seat branch patterns and on refactor, next and main. ops-01 sets it up, through Mos (mos-1082). That part isn't this row.
- (b) The client hook below. It can be bypassed with
--no-verify, so (a) is the real guard and (b) is the early refusal.
What ships
scripts/git-hooks/pre-push(new). For each ref line git passes on stdin:- It refuses when the remote sha isn't all zeros and isn't an ancestor of the local sha (
git merge-base --is-ancestor). The refusal names the ref and both shas, and the hook exits 1. - A new branch passes. A delete (local sha all zeros) is refused.
- Any failure of the check refuses too, for example an unknown remote object. The hook fetches nothing. If the remote tip isn't present locally, it refuses and says to fetch first.
- It refuses when the remote sha isn't all zeros and isn't an ancestor of the local sha (
- Install, following
scripts/queue-commit.sh --install-hook. Either add a--install-push-hookthere or ship a smallscripts/install-git-hooks.sh, whichever fits the existing code better. Say which in BUILD.md.- The install copies the hook into the common
.git/hooks, which every seat worktree shares. - It refuses when
core.hooksPathis set (the husky trap, 2026-10-09). It refuses when an installed pre-push differs from the repository copy, unless--force-installis given. - It never edits the installed copy in place.
- The install copies the hook into the common
scripts/slot-busy.sh(new), the shared busy check.- It prints one BUSY line per matching process, or CLEAR.
- It exits 0 on CLEAR, 1 on BUSY and 2 on its own error.
- It matches
node --test,gate.sh,mutant,scripts/test-*.shanddocker composecommand lines. It skips its own process and shell snapshots. - It reads
/proc/<pid>/cmdlineand the cwd link only, never file contents under another seat's directory. - Usage in
docs/TOOLS.md:bash scripts/slot-busy.sh > LOG && gate, withset -o pipefailin any script that pipes it. Never| tail -1 &&.
Tests
Add them to scripts/test-land-work.sh or a new scripts/test-git-guard.sh (say which). Run against a scratch
bare remote and a scratch clone with a worktree, never against origin:
- A rewritten branch pushed with
--forcefrom a worktree is refused, and the remote ref is unchanged. - A
+refspecpush is refused. - A fast-forward push passes.
- A new branch passes.
- A delete is refused.
- A remote tip that's missing locally is refused.
- The install refuses with
core.hooksPathset, and refuses a differing installed hook. slot-busy.shexits 1 with a fakenode --testprocess running (a sleeping stand-in with that argv), and 0 without one.slot-busy.sh | tail -1 && falsedoesn't apply here. The test runsbash scripts/slot-busy.sh >/dev/null && echo ran, and asserts it doesn't printranwhile BUSY.
Mutants, each killed:
- drop the ancestor check
- treat an unknown remote object as passing
- let a delete pass
- install over a set
core.hooksPath slot-busy.shexits 0 on BUSY
Boundaries
- Install the hook in the canonical checkout only after landing, as its own step with Sage. Tests use scratch repositories.
- No push to origin from a test. No change to Gitea config. That's ops-01's.
- Don't touch
~/.mosaic,~/.mosaic-devor~/.config/mosaic-dev.
Gate
Dewey approves the manifest. The new or changed scripts/test-*.sh file and every other scripts/test-*.sh
are green on Sage's gate, and the mutants are killed. Done when the hook is installed in the canonical
checkout and a scripted force-push from a seat worktree to a scratch remote is refused there. ops-01's
server proof (a refused force-push, plus a fast-forward push and a new branch that go through) is recorded
in Vikunja #426 by Mos.