#!/usr/bin/env bash # Smoke test for the lefthook + git-lfs pre-push integration. # # Builds a disposable sandbox at SANDBOX_DIR, copies the repo's # current lefthook.yml + .githooks/ into it, and runs a push that # exercises both: lefthook's internal LFS handler and the sync-check # user command. Passes when: # # 1. Push completes within the timeout. # 2. Exactly one `[git-lfs] executing hook` line is present in the # verbose log (proves no duplicate manual command). # 3. `Uploading LFS objects: 100%` is present (proves the internal # handler did the upload). # # Fails loudly otherwise. See dev-docs/ADR-LEFTHOOK-LFS-INTEGRATION.md # for what each failure mode signals. set -euo pipefail REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" SANDBOX_DIR="${SANDBOX_DIR:-/tmp/lefthook-lfs-smoke-$$}" TIMEOUT_SECS="${TIMEOUT_SECS:-30}" # Resolve the lefthook binary the repo already has installed. LEFTHOOK_BIN="${LEFTHOOK_BIN:-}" if [ -z "${LEFTHOOK_BIN}" ]; then candidate=$(find "${REPO_ROOT}/node_modules" -type f -name lefthook \ -path '*/lefthook-darwin-arm64/bin/*' 2>/dev/null | head -n 1) if [ -z "${candidate}" ]; then candidate=$(find "${REPO_ROOT}/node_modules" -type f -name lefthook \ 2>/dev/null | head -n 1) fi LEFTHOOK_BIN="${candidate}" fi if [ -z "${LEFTHOOK_BIN}" ] || [ ! -x "${LEFTHOOK_BIN}" ]; then echo "FAIL: could not locate lefthook binary (set LEFTHOOK_BIN explicitly)" >&2 exit 2 fi if ! command -v git-lfs >/dev/null 2>&1; then echo "FAIL: git-lfs not installed on this host" >&2 exit 2 fi cleanup() { rm -rf "${SANDBOX_DIR}"; } trap cleanup EXIT echo "[smoke] sandbox: ${SANDBOX_DIR}" echo "[smoke] lefthook: ${LEFTHOOK_BIN}" mkdir -p "${SANDBOX_DIR}" git init --bare -q "${SANDBOX_DIR}/remote.git" git init -q "${SANDBOX_DIR}/work" cd "${SANDBOX_DIR}/work" git config user.email "smoke@crewli.local" git config user.name "smoke" git remote add origin "${SANDBOX_DIR}/remote.git" # Mirror the repo's hook layer cp "${REPO_ROOT}/lefthook.yml" . mkdir .githooks cp "${REPO_ROOT}/.githooks/pre-push" .githooks/ cp "${REPO_ROOT}/.githooks/post-commit" .githooks/ chmod +x .githooks/* LEFTHOOK_BIN="${LEFTHOOK_BIN}" "${LEFTHOOK_BIN}" install >/dev/null git lfs install --skip-repo >/dev/null git lfs track "*.png" >/dev/null # Tiny valid PNG so LFS has something to push python3 - <<'PY' import struct, zlib hdr = b'\x89PNG\r\n\x1a\n' def chunk(t, d): return struct.pack('>I', len(d)) + t + d + struct.pack('>I', zlib.crc32(t + d)) ihdr = chunk(b'IHDR', struct.pack('>IIBBBBB', 1, 1, 8, 2, 0, 0, 0)) idat = chunk(b'IDAT', zlib.compress(b'\x00\xff\xff\xff', 9)) iend = chunk(b'IEND', b'') open('smoke.png', 'wb').write(hdr + ihdr + idat + iend) PY git add .gitattributes lefthook.yml .githooks smoke.png LEFTHOOK=0 git commit -q -m "smoke test" LOG_FILE="${SANDBOX_DIR}/push.log" # Background push; kill if it exceeds the timeout ( cd "${SANDBOX_DIR}/work" LEFTHOOK_BIN="${LEFTHOOK_BIN}" \ LEFTHOOK_VERBOSE=1 \ git push -u origin master >"${LOG_FILE}" 2>&1 echo "EXIT=$?" >>"${LOG_FILE}" ) & PUSH_PID=$! elapsed=0 while kill -0 "${PUSH_PID}" 2>/dev/null; do if [ "${elapsed}" -ge "${TIMEOUT_SECS}" ]; then kill -KILL "${PUSH_PID}" 2>/dev/null || true pkill -KILL -P "${PUSH_PID}" 2>/dev/null || true echo "[smoke] FAIL: push exceeded ${TIMEOUT_SECS}s timeout" >&2 echo "[smoke] partial log:" >&2 cat "${LOG_FILE}" >&2 || true exit 1 fi sleep 1 elapsed=$((elapsed + 1)) done wait "${PUSH_PID}" 2>/dev/null || true if ! grep -q '^EXIT=0$' "${LOG_FILE}"; then echo "[smoke] FAIL: push exited non-zero" >&2 cat "${LOG_FILE}" >&2 exit 1 fi # Exactly one internal LFS invocation; zero manual ones internal_count=$(grep -c '\[git-lfs\] executing hook' "${LOG_FILE}" || true) manual_count=$(grep -c '\[lefthook\] run: git lfs pre-push' "${LOG_FILE}" || true) upload_marker=$(grep -c 'Uploading LFS objects: 100%' "${LOG_FILE}" || true) if [ "${internal_count}" != "1" ]; then echo "[smoke] FAIL: expected 1 internal LFS invocation, found ${internal_count}" >&2 cat "${LOG_FILE}" >&2 exit 1 fi if [ "${manual_count}" != "0" ]; then echo "[smoke] FAIL: a manual 'git lfs pre-push' command is present" \ "(${manual_count} occurrences). The duplicate-execution regression has returned." >&2 echo "[smoke] See dev-docs/ADR-LEFTHOOK-LFS-INTEGRATION.md for why this fails." >&2 cat "${LOG_FILE}" >&2 exit 1 fi if [ "${upload_marker}" = "0" ]; then echo "[smoke] FAIL: no 'Uploading LFS objects' marker in log" >&2 cat "${LOG_FILE}" >&2 exit 1 fi echo "[smoke] PASS: push completed; 1 internal LFS call, 0 manual, LFS upload confirmed."