pre-commit Hook

Writing a pre-commit Hook by Hand

Sam's first hook refuses two mistakes: a committed .env file with the database password, and JavaScript that does not parse. node --check - parses a script from standard input without running it:

A hand-written pre-commit hook that checks the staged contentShell
cat > .git/hooks/pre-commit <<'EOF'
#!/bin/sh
# Refuse secrets and JavaScript that does not parse. It checks the staged
# version of each file (":path"), which is what the commit will contain.
fail=0
for f in $(git diff --cached --name-only --diff-filter=ACM); do
  case "$f" in
    .env|*/.env) echo "pre-commit: $f holds secrets; unstage it"; fail=1 ;;
    *.js) git show ":$f" | node --check - 2>/dev/null ||
            { echo "pre-commit: $f has a syntax error"; fail=1; } ;;
  esac
done
exit $fail
EOF
chmod +x .git/hooks/pre-commit
# Edit app.js: start a request logger and forget its closing "});"
git commit -qam "Log each request"; echo "exit status $?"
git restore --staged --worktree app.js
Output
pre-commit: app.js has a syntax error
exit status 1

--diff-filter=ACM lists only staged files that still exist, so a deletion does not break the loop. git show ":$f" reads the index, the version being committed, not the working tree, which may hold unstaged edits. The hook reports every problem before exiting. Nothing was recorded, and git restore discarded the broken edit.