The pre-push hook builds the site in a temporary copy and writes nothing in the worktree; the foreign-tree check fails a hook that builds in place

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-06 20:02:02 +00:00
parent a581866a2d
commit ed6d37e583
2 changed files with 22 additions and 4 deletions

View file

@ -1,12 +1,22 @@
#!/usr/bin/env bash
# Installs the repository's git hooks into this checkout (pre-push: no conflict markers, the site builds).
# Installs the repository's git hooks into this checkout (pre-push: no conflict markers, the site builds). Linked
# worktrees share the main repository's .git/hooks, so one install covers every worktree.
# The hook is READ-ONLY (6 October 2026): it builds the site in a temporary copy and never writes a tracked file in the
# worktree. Before this, `node build.mjs` in site/ rewrote downloads.json, journey.json, bench.html and the stamped pages
# in whatever worktree the push ran from (five worktrees carried 0.3.14's rows as uncommitted edits, and generated pages
# kept changing under agents after 063bbca). Only the ship step writes, with SITE_DOWNLOADS_REFRESH=1 node site/build.mjs.
set -euo pipefail
cd "$(dirname "$0")/../.."
cat > .git/hooks/pre-push <<'HOOK'
HOOKS="$(git rev-parse --git-common-dir)/hooks"
mkdir -p "$HOOKS"
cat > "$HOOKS/pre-push" <<'HOOK'
#!/usr/bin/env bash
set -e
cd "$(git rev-parse --show-toplevel)"
bash tools/ci/no-conflict-markers.sh
(cd site && node build.mjs >/dev/null) || { echo "pre-push: the site build fails; fix it before pushing" >&2; exit 1; }
# the site build, in a temporary copy: a failing build blocks the push; nothing in the worktree is written
tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
cp -R site "$tmp/site"
(cd "$tmp/site" && SITE_DOWNLOADS_OFFLINE=1 node build.mjs >/dev/null) || { echo "pre-push: the site build fails; fix it before pushing" >&2; exit 1; }
HOOK
chmod +x .git/hooks/pre-push; echo "pre-push hook installed"
chmod +x "$HOOKS/pre-push"; echo "pre-push hook installed at $HOOKS/pre-push (read-only site build)"

View file

@ -35,6 +35,14 @@ if [ "${1:-}" = "--self-test" ]; then
echo "self-test passed: a Projects path fails, a worktree-list loop with a write fails, an own-toplevel write passes"; exit 0
fi
fail=0
# a git hook never writes a tracked file: the hook body in tools/ci/install-hooks.sh (between <<'HOOK' and HOOK) may run the
# site build only inside a mktemp copy (6 October 2026: the in-place build rewrote generated pages in other worktrees)
if [ -f tools/ci/install-hooks.sh ]; then
body="$(sed -n "/<<'HOOK'/,/^HOOK$/p" tools/ci/install-hooks.sh)"
if printf '%s' "$body" | grep -qE "build\.mjs" && ! printf '%s' "$body" | grep -qE "mktemp"; then
echo "foreign-tree: the pre-push hook runs the site build in the worktree (no mktemp copy): a hook never writes a tracked file"; fail=1
fi
fi
while IFS= read -r f; do grep -nE "$WARN" "$f" | grep -vE '^[0-9]+:\s*(#|//)' | sed "s|^|foreign-tree (warning, env-only default owed): $f:|" | cut -c1-200; done < <(git ls-files 'tools/**' 'packaging/**' 'infra/**' 'relay/**' | grep -E '\.(sh|mjs|js|py|ps1)$' | grep -v '^tools/ci/no-foreign-tree-writes.sh$')
while IFS= read -r f; do check_file "$f" || fail=1; done < <(git ls-files 'tools/**' 'packaging/**' 'site/*.mjs' 'infra/**' 'relay/**' | grep -E '\.(sh|mjs|js|py|ps1)$' | grep -v '^tools/ci/no-foreign-tree-writes.sh$')
[ "$fail" = 0 ] && echo "foreign-tree: every script writes under its own toplevel"