Skip to main content

max / synckit

5.1 KB · 100 lines History Blame Raw
1 #!/bin/bash
2 # Canonical pre-push gate. Two gates, and they answer different questions:
3 #
4 # internal deps does every in-house `version` requirement in the tree still
5 # resolve against the crate it names? Runs in EVERY repo.
6 # test targets do this repo's test targets build? Runs where there is a
7 # root Cargo.toml to run one command in.
8 #
9 # DO NOT EDIT IN PLACE. The master is _private/infra/bootstrap/githooks/pre-push.
10 #
11 # Bypass for a work-in-progress push: git push --no-verify
12 #
13 # WHY THE FIRST GATE EXISTS. Every cross-repo dependency carries a `version`
14 # alongside its `git` URL, so cargo refuses a sibling it was not written against
15 # instead of compiling something surprising. That requirement is the protection
16 # and it is also the maintenance: bumping a library's minor breaks every consumer
17 # whose requirement excludes it, and CLAUDE.md's rule is that the bump and the
18 # forward fix are one pass. Nothing enforced the rule, so quasi went 0.11 -> 0.14
19 # over two evenings and MNW's server could not resolve at all for a day. The
20 # nightly sweep found it and a red cell in a grid is not the same as being told.
21 #
22 # This gate is that rule, mechanised, at the moment it is broken: the push that
23 # would leave a consumer unable to build is the push that is refused. It reads
24 # the WORKING COPIES in the tree, not the remotes, because `~/Code/.cargo/config.toml`
25 # redirects every one of these dependencies to the working copy -- so a local bump
26 # breaks a consumer's build here whether or not it has been pushed anywhere.
27 #
28 # It also grades this repo's manifests AS THEY EXIST AT THE PUSHED COMMIT, and
29 # fails on either view. Reading the working copy alone means an uncommitted
30 # forward-fix is graded instead of the text git is publishing: on 2026-08-24 that
31 # printed "internal deps coherent" over an mnw-cli requirement that had been
32 # advanced on disk and never committed, and Sando failed to resolve it minutes
33 # later. Both views are kept because they catch different things -- the commit
34 # view cannot see a local bump that breaks every build on this machine.
35 #
36 # `cargo check` and `cargo clippy` both compile only the lib and bin targets, so a
37 # break confined to `tests/` or a `#[cfg(test)]` module is clean under both and
38 # lands unnoticed (goingson's sqlx 0.9 upgrade shipped exactly that way).
39 # `--no-run` builds every test target without running them, which is the cheap
40 # half of the suite and enough to catch a compile break. Tests still run
41 # separately.
42 #
43 # `--workspace` is load-bearing wherever default-members is narrower than the
44 # workspace: goingson's is src-tauri alone, so a bare `cargo test --no-run` would
45 # skip core, db-sqlite, go-mcp and got.
46 set -euo pipefail
47
48 ROOT="$(git rev-parse --show-toplevel)"
49 cd "$ROOT"
50
51 # See the canonical pre-commit: a hook run from an editor or a cron job does not
52 # get the profile's PATH, and a hook that cannot find cargo is worse than none.
53 export PATH="$HOME/.cargo/bin:$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
54
55 # Refs arrive on stdin as "<local ref> <local sha> <remote ref> <remote sha>".
56 # A branch deletion has an all-zero local sha and no tree to push. Read once,
57 # ahead of both gates: stdin is not seekable and a second reader gets nothing.
58 #
59 # The sha is KEPT, not just tested. Gate 1 grades the manifests at that commit as
60 # well as the ones on disk, because they are not always the same text and the
61 # difference is invisible in the good case. Last sha wins on a multi-ref push:
62 # the gate wants a commit whose tree it can read, and grading one of them beats
63 # grading none. A deletion contributes an all-zero sha and is skipped.
64 pushing=0
65 pushed_sha=""
66 while read -r _local_ref local_sha _remote_ref _remote_sha; do
67 case "$local_sha" in
68 *[!0]*) pushing=1; pushed_sha="$local_sha" ;;
69 esac
70 done
71 [ "$pushing" -eq 1 ] || exit 0
72
73 # ── gate 1: internal dependency coherence ──────────────────────────────────
74 #
75 # Deliberately no cargo: this reads manifests and answers in well under a second,
76 # where `cargo metadata` on the server is tens of seconds and fails outright on
77 # exactly the state being detected.
78 CODE_ROOT="${CODE_ROOT:-$HOME/Code}"
79 if [ -d "$CODE_ROOT" ] && command -v python3 >/dev/null 2>&1; then
80 if ! python3 "$ROOT/scripts/githooks/internal-deps.py" "$CODE_ROOT" "$ROOT" "$pushed_sha"; then
81 echo "pre-push: push aborted (use --no-verify to bypass)."
82 exit 1
83 fi
84 fi
85
86 # ── gate 2: test targets build ─────────────────────────────────────────────
87 #
88 # MNW and synckit have no root Cargo.toml by design (standalone crates, no root
89 # workspace), so there is no one command to run and they get gate 1 only.
90 [ -f "$ROOT/Cargo.toml" ] || exit 0
91
92 echo "pre-push: building test targets (cargo test --no-run --workspace)..."
93 if ! cargo test --no-run --workspace; then
94 echo "pre-push: test targets failed to build."
95 echo "pre-push: push aborted (use --no-verify to bypass)."
96 exit 1
97 fi
98
99 echo "pre-push: test targets build clean."
100