mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
ci: add the pre-push gate and stop lying about it in the canon
Section 10.1 promised pre-push as the blocking gate that replaces pull requests. The hook did not exist, so the document promised a check that was not there — worse than saying nothing, because a promise like that gets relied on. Until now process-gate ran only as the catch-up job in CI, which reports after the code is already in dev. The hook skips branch deletions and tags, and for a branch the remote has not seen it measures from the merge-base with dev rather than from the root, or every violation committed before the gate existed would make it impossible to pass. A missing script does not block a push: old checkouts and worktrees have to stay usable. gh is optional on purpose. Reading issue status needs the network, and a hook that cannot work on a train is a hook people switch off; offline it runs what it can and CI does the strict pass. The executable bit is the quiet part. Git skips a hook without +x and says nothing — the gate reports success by being absent. Measured on a real push: mode 644 produces zero lines from the gate and the push goes through, 755 stops it. The API cannot set the bit, so install-hooks restores it on every install. Issue: #121 User-Visible: no
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# PROCESS.md 10.1: the blocking process gate lives here, because commits go
|
||||
# straight to dev without pull requests and GitHub blocks nothing on its side.
|
||||
# CI still runs the same script (10.3), but by then the code is already in dev —
|
||||
# that catch-up pass reports, it does not prevent.
|
||||
#
|
||||
# Git feeds one line per ref on stdin:
|
||||
# <local ref> <local sha> <remote ref> <remote sha>
|
||||
|
||||
repo_root=$(git rev-parse --show-toplevel)
|
||||
gate="$repo_root/scripts/process-gate.mjs"
|
||||
zero=$(printf '%040d' 0)
|
||||
|
||||
# The gate reasons about commits. A repository without it — an old checkout, a
|
||||
# bisect, a worktree from before the script existed — must still be pushable.
|
||||
if [ ! -f "$gate" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Reading issue status needs gh, and a hook that cannot work on a train is a
|
||||
# hook people disable. Offline the checks that need no network still run, and the
|
||||
# strict pass happens in CI, where gh is always present.
|
||||
issues_flag=""
|
||||
if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
|
||||
issues_flag="--issues"
|
||||
else
|
||||
echo "process-gate: gh недоступен, проверка статуса issue пропущена — её выполнит CI" >&2
|
||||
fi
|
||||
|
||||
status=0
|
||||
|
||||
while read -r local_ref local_sha remote_ref remote_sha; do
|
||||
# Deleting a remote branch pushes nothing to examine.
|
||||
if [ "$local_sha" = "$zero" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Tags carry no process state of their own: the commit they point at was
|
||||
# already checked when it was pushed.
|
||||
case "$local_ref" in
|
||||
refs/tags/*) continue ;;
|
||||
esac
|
||||
|
||||
if [ "$remote_sha" = "$zero" ]; then
|
||||
# A branch that does not exist on the remote yet. Everything it adds on top
|
||||
# of dev is new, so that is the range — not the whole history, which would
|
||||
# drag in every violation committed before the gate existed.
|
||||
base=$(git merge-base "$local_sha" refs/remotes/origin/dev 2>/dev/null || true)
|
||||
if [ -z "$base" ]; then
|
||||
echo "process-gate: не нашёл общего предка с origin/dev, проверяю последние 20 коммитов" >&2
|
||||
base="$local_sha~20"
|
||||
fi
|
||||
else
|
||||
base="$remote_sha"
|
||||
fi
|
||||
|
||||
echo "process-gate: $local_ref, диапазон ${base}..${local_sha}" >&2
|
||||
# shellcheck disable=SC2086
|
||||
if ! node "$gate" --range "${base}..${local_sha}" $issues_flag >&2; then
|
||||
status=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$status" -ne 0 ]; then
|
||||
cat >&2 <<'EOF'
|
||||
|
||||
Push остановлен: нарушен процесс (PROCESS.md §10.2).
|
||||
|
||||
Починить надо причину, а не симптом. Если нарушение уже опубликовано, его
|
||||
исправляет следующий коммит плюс issue с меткой `process` — не force-push
|
||||
(§12, правило 17).
|
||||
|
||||
Обойти проверку можно через `git push --no-verify`, и тогда то же самое найдёт
|
||||
job `process-gate` в Validate — уже после того, как код окажется в dev.
|
||||
EOF
|
||||
fi
|
||||
|
||||
exit "$status"
|
||||
Reference in New Issue
Block a user