H11 (.claude/hooks/prepush-rebase-check.sh) refuses to push a branch that is behind origin/main. It fired on tag-only pushes too, breaking every release cut: docs/ci-cd.md's "Cutting a release" flow lands a release-notes commit via PR and then tags that merge commit, so the local branch is always one commit behind origin/main at tag time. A tag push cannot revert anyone's merged work, which is the failure H11 exists to prevent, so skip the freshness check when every ref being pushed is under refs/tags/. .husky/pre-push previously consumed pre-push's stdin ref lines and forwarded them only to prepush-donewhen.sh; prepush-rebase-check.sh got none. Forward the captured $_prepush_refs to it too, or the new logic is dead. Guard against the vacuous-truth case explicitly required by #719: "all pushed refs are tags" is trivially true over zero ref lines (manual run, forgotten forwarding), which would silently disable H11 for every push. Require at least one parsed ref line before granting the exemption. Adds scripts/tests/test_prepush_rebase_check_tag_exemption.py using real local git repos (bare origin + a work tree pushed one commit behind it) to exercise git fetch/merge-base/rev-list against a genuinely-moved origin: tag-only allowed, branch-only still blocked, mixed branch+tag still blocked, and zero ref lines still blocked (the vacuous-truth guard). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
30 lines
2.0 KiB
Plaintext
Executable File
30 lines
2.0 KiB
Plaintext
Executable File
# H6 merge-consent backstop (ersatztv#303): gate a direct push to main on the linked issue's
|
|
# ## Done-when checklist. Read git's pre-push ref lines FIRST (before the web checks below, which
|
|
# may consume stdin) and forward them. Fail-open: no creds / not main / docs-only -> allow.
|
|
_prepush_refs="$(cat)"
|
|
printf '%s\n' "$_prepush_refs" | ./.claude/hooks/prepush-donewhen.sh || exit 1
|
|
|
|
# Git exports GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE while running hooks. In a worktree
|
|
# (or any subdir), an explicit GIT_DIR makes nested `git` commands mislocate the working
|
|
# tree — notably `check:api`'s `git diff --exit-code` (run from web/) silently reports "no
|
|
# diff" and lets drift through. Unset them so nested git rediscovers the repo normally.
|
|
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
|
|
|
|
# H11 (ersatztv#311): refuse to push a branch that is BEHIND origin/main — rebase, don't merge
|
|
# main in (a merge drags in files you never touched, e.g. legacy-BOM .cs, and trips the format
|
|
# hook on code that isn't yours). Fail-open; escape with ETV_SKIP_REBASE_CHECK=1. Exempts a
|
|
# tag-only push (ersatztv#719) — forward the ref lines captured above so it can tell.
|
|
printf '%s\n' "$_prepush_refs" | ./.claude/hooks/prepush-rebase-check.sh || exit 1
|
|
|
|
# H13 (ersatztv#416 session): refuse to push when a file in the pushed diff still has uncommitted
|
|
# working-tree/index changes — the pushed commit wouldn't match what you built/reviewed (the #416
|
|
# index/worktree trap: a review fix left in the working tree shipped without being committed).
|
|
# Runs before the slow CI-parity checks so it fails fast. Fail-open; escape ETV_ALLOW_DIRTY_PUSH=1.
|
|
./.claude/hooks/prepush-clean-worktree-check.sh || exit 1
|
|
|
|
# CI-parity checks: catch "green locally, red in CI" before the push leaves the machine.
|
|
# check:api guards the generated OpenAPI types (v1.json / v1.d.ts drift); the full
|
|
# lint/typecheck/build catch a staged change that breaks an UNstaged file (lint-staged
|
|
# only sees staged files).
|
|
cd web && npm run check:api && npm run lint && npm run typecheck && npm run build
|