#!/usr/bin/env bash # Tests for the git hook install block in scripts/install.sh. # Runs install.sh from a temp repo to avoid touching the real .git/hooks/. set -euo pipefail # This script may itself run inside a git hook (e.g. pre-push), which sets # GIT_DIR/GIT_EXEC_PATH/etc. in the environment. `git init`/`git -C` below # would silently re-target the inherited GIT_DIR instead of creating an # isolated repo in $TEMP_REPO, so start from a clean slate. for var in $(compgen -v | grep '^GIT_'); do unset "$var"; done REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PASS=0 FAIL=0 pass() { echo " PASS: $1"; PASS=$((PASS + 1)); } fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); } # --------------------------------------------------------------------------- # Temp repo setup # # install.sh derives REPO_ROOT from the script's own location, so copying it # into a temp tree makes REPO_ROOT point there, not at the real checkout. # --------------------------------------------------------------------------- TEMP_REPO="$(mktemp -d)" TEMP_HOME="$(mktemp -d)" trap 'rm -rf "$TEMP_REPO" "$TEMP_HOME"' EXIT mkdir -p "$TEMP_REPO/scripts" git -C "$TEMP_REPO" init -q # Minimal deploy-manifest.sh — empty deploy lists so install.sh reaches the # hook-copy block without attempting to copy files that don't exist in the # temp tree. cat > "$TEMP_REPO/scripts/deploy-manifest.sh" << 'EOF' # shellcheck disable=SC2034 DEPLOY_FILES=() DEPLOY_EXECUTABLES=() DEPLOY_DIRS=() EOF # Copy the real install.sh into the temp repo. cp "$REPO_ROOT/scripts/install.sh" "$TEMP_REPO/scripts/install.sh" HOOKS_SRC="$TEMP_REPO/scripts/git-hooks" mkdir -p "$HOOKS_SRC" # Copy whatever real hooks exist, then add a synthetic fixture. The repo # currently ships none — the only entry was `post-push`, removed once it was # found that git has no such client-side hook, so it had never fired (see # ADR-0019). install.sh's copy block is generic and stays worth testing, so the # fixture keeps that coverage alive independently of whether any real hook # happens to exist. Real hooks are still picked up by the loops below. if [[ -d "$REPO_ROOT/scripts/git-hooks" ]]; then find "$REPO_ROOT/scripts/git-hooks" -maxdepth 1 -type f -exec cp {} "$HOOKS_SRC/" \; fi FIXTURE_HOOK="fixture-hook" printf '#!/usr/bin/env bash\nexit 0\n' > "$HOOKS_SRC/$FIXTURE_HOOK" chmod +x "$HOOKS_SRC/$FIXTURE_HOOK" run_install() { HOME="$TEMP_HOME" bash "$TEMP_REPO/scripts/install.sh" > /dev/null 2>&1 } # --------------------------------------------------------------------------- echo "--- hook install: files copied to .git/hooks/ ---" # --------------------------------------------------------------------------- run_install for hook_file in "$HOOKS_SRC"/*; do [[ -f "$hook_file" ]] || continue hook_name="$(basename "$hook_file")" if [[ -f "$TEMP_REPO/.git/hooks/$hook_name" ]]; then pass "$hook_name installed to .git/hooks/" else fail "$hook_name missing from .git/hooks/" fi done # --------------------------------------------------------------------------- echo "" echo "--- hook install: installed hooks are executable ---" # --------------------------------------------------------------------------- for hook_file in "$HOOKS_SRC"/*; do [[ -f "$hook_file" ]] || continue hook_name="$(basename "$hook_file")" if [[ -x "$TEMP_REPO/.git/hooks/$hook_name" ]]; then pass "$hook_name is executable" else fail "$hook_name is not executable" fi done # --------------------------------------------------------------------------- echo "" echo "--- hook install: directories in git-hooks/ are not installed ---" # --------------------------------------------------------------------------- # Add a subdirectory to scripts/git-hooks/ — the install block must skip it mkdir -p "$HOOKS_SRC/not-a-hook" run_install if [[ ! -e "$TEMP_REPO/.git/hooks/not-a-hook" ]]; then pass "directories in scripts/git-hooks/ are not copied to .git/hooks/" else fail "a directory from scripts/git-hooks/ was incorrectly installed" fi rmdir "$HOOKS_SRC/not-a-hook" # --------------------------------------------------------------------------- echo "" echo "--- hook install: resolves correctly when GIT_DIR is inherited (hook context) ---" # --------------------------------------------------------------------------- # Git sets GIT_DIR/GIT_WORK_TREE for child processes when this script itself # runs as a hook (e.g. pre-push). Simulate that and confirm install.sh still # resolves hooks against $TEMP_REPO, not the inherited GIT_DIR. OTHER_REPO="$(mktemp -d)" git -C "$OTHER_REPO" init -q if HOME="$TEMP_HOME" GIT_DIR="$OTHER_REPO/.git" GIT_WORK_TREE="$OTHER_REPO" \ bash "$TEMP_REPO/scripts/install.sh" > /dev/null 2>&1; then if [[ -f "$TEMP_REPO/.git/hooks/$FIXTURE_HOOK" ]]; then pass "resolves \$TEMP_REPO/.git/hooks/ even with inherited GIT_DIR" else fail "installed into inherited GIT_DIR instead of \$TEMP_REPO" fi else fail "install.sh failed when GIT_DIR/GIT_WORK_TREE were inherited" fi rm -rf "$OTHER_REPO" # --------------------------------------------------------------------------- echo "" echo "--- hook install: idempotent ---" # --------------------------------------------------------------------------- run_install for hook_file in "$HOOKS_SRC"/*; do [[ -f "$hook_file" ]] || continue hook_name="$(basename "$hook_file")" if diff -q "$hook_file" "$TEMP_REPO/.git/hooks/$hook_name" > /dev/null 2>&1; then pass "idempotent: $hook_name content unchanged after second install" else fail "idempotent: $hook_name content corrupted after second install" fi if [[ -x "$TEMP_REPO/.git/hooks/$hook_name" ]]; then pass "idempotent: $hook_name still executable after second install" else fail "idempotent: $hook_name lost executable bit after second install" fi done # --------------------------------------------------------------------------- echo "" echo "Results: $PASS passed, $FAIL failed" [[ $FAIL -eq 0 ]]