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>
58 lines
2.3 KiB
Bash
Executable File
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
|