Files
clide/.githooks/pre-push
T
jpmschweitzerandClaude Opus 4.8 b7e0835b74 pre-push: skip the test suite unless lib/ or pubspec changed (T-348)
The hook ran the full ~2min push-check on every push, even one touching
only docs, changelog, pql data, tests, or assets. Diff the pushed range
(from the hook's stdin) and run the full gate only when lib/ (app/runtime
source) or pubspec.* (deps/version) changed; otherwise run just the
instant decisions + changelog gates.

Other changes ride along with a lib change in practice, the full suite
stays available via `make push-check`, and the release CI runs it forced
on a tagged version. A state we can't classify (unfetched remote sha, new
branch) falls back to the full gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 19:22:12 +02:00

58 lines
2.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# Pre-push gate. Blocks the push if any quality check fails.
#
# Install: `make hooks` (points git core.hooksPath at .githooks/).
# Bypass: never. If this runs slowly, fix the slow test; don't reach
# for --no-verify (git-commit skill forbids it).
#
# Fast path (T-348): run the full ~2min test suite only when the push touches
# lib/ (app + runtime Dart source) or pubspec.* (deps / version). test/,
# assets/, docs, and tooling changes ride along with a lib change in practice,
# and an otherwise-skipped push is covered by the next one that does touch lib.
# The full suite is always available via `make push-check`, and the release CI
# runs it forced on a tagged version. So a lib/pubspec-free push runs just the
# instant decisions + changelog gates. A state we can't classify (unfetched
# remote, new branch) runs the full gate.
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
z40=0000000000000000000000000000000000000000
# Collect every file changed across the commits being pushed. git feeds the
# hook one line per ref on stdin: <local-ref> <local-sha> <remote-ref> <remote-sha>.
changed=""
force_full=0
while read -r _local_ref local_sha _remote_ref remote_sha; do
[[ "$local_sha" == "$z40" ]] && continue # branch deletion — nothing to test
if [[ "$remote_sha" == "$z40" ]]; then
# New remote branch: diff from its merge-base with main, else play it safe.
base="$(git merge-base "$local_sha" origin/main 2>/dev/null || true)"
else
base="$remote_sha"
fi
# If we can't resolve a base locally (e.g. the remote advanced and we haven't
# fetched its objects), we can't classify the diff — run the full gate.
if [[ -z "$base" ]] || ! git cat-file -e "$base^{commit}" 2>/dev/null; then
force_full=1
break
fi
changed+=$'\n'"$(git diff --name-only "$base" "$local_sha")"
done
# Run the full gate when lib/ (app + runtime source) or pubspec.* (deps /
# version) is touched, or when we couldn't classify above.
needs_gate=1
if [[ "$force_full" -eq 0 ]]; then
trigger_files="$(printf '%s\n' "$changed" | grep -E '^(lib/|pubspec\.)' || true)"
[[ -z "$trigger_files" ]] && needs_gate=0
fi
if [[ "$needs_gate" -eq 0 ]]; then
echo "==> pre-push: no lib/ or pubspec change — decisions + changelog gates, skipping tests"
make decisions-validate changelog-gate
else
echo "==> pre-push: make push-check"
make push-check
fi