Chapter 8.2 — pre-commit Hook☕ 15 min read

pre-commit Hook

5 second se zyada? Developers --no-verify use karenge. Fast rakhho, effective rakhvo.

01pre-commit: The Last Quality Gate

The pre-commit hook runs BEFORE git commit creates the commit. It is your last chance to validate code before it enters history.

If the hook exits with code 0, the commit proceeds. If non-zero, the commit is BLOCKED and the code stays in your working directory.

It receives NO arguments — it just runs, and you check whatever you want.

Most common uses: lint code, format code, check for debug statements, prevent large files, validate configurations.

The pre-commit hook is the MOST IMPORTANT client-side hook. It is your automated code reviewer for mechanical issues.

The golden rule: pre-commit must be FAST. Under 5 seconds. If it is slow, developers will use --no-verify to bypass it.

# Create a pre-commit hook
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
echo "🔍 Pre-commit checks running..."

# Check 1: No console.log in staged files
if git diff --cached | grep -q "console.log"; then
  echo "❌ Found console.log in staged files. Remove before committing."
  exit 1
fi

# Check 2: No TODO or FIXME left behind
if git diff --cached | grep -qE "TODO|FIXME"; then
  echo "⚠️ Found TODO/FIXME. Consider resolving before committing."
  # Warning only — do not block (exit 0)
fi

echo "✅ All checks passed!"
exit 0
EOF

chmod +x .git/hooks/pre-commit

# Now try to commit with console.log
echo "console.log('debug');" > app.js
git add app.js
git commit -m "add app"
# ❌ Found console.log. Commit BLOCKED!

# Fix it
echo "function app() {}" > app.js
git add app.js
git commit -m "add app"
# ✅ All checks passed! Commit created.
02Common pre-commit Checks

Linting: run ESLint, Pylint, or equivalent on staged files. Block if errors.

Formatting: run Prettier, Black, or equivalent. Auto-format and re-stage.

Debug statements: check for console.log, debugger, print statements.

Large files: prevent committing files over a size limit (use Git LFS instead).

Secrets: check for API keys, passwords, tokens using tools like detect-secrets or gitleaks.

Trailing whitespace: prevent committing files with trailing spaces or missing newline.

Broken JSON/YAML: validate configuration files before they break your deployment.

For each check, decide: should it BLOCK (exit 1) or WARN (echo message but exit 0)?

cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
set -e  # Exit on any error

# 1. Check for debug statements (BLOCK)
if git diff --cached --name-only | grep -qE '\.(js|ts)$'; then
  if git diff --cached -- '*.js' '*.ts' | grep -qE 'console\.(log|debug)|debugger'; then
    echo "❌ Debug statements found. Remove them."
    exit 1
  fi
fi

# 2. Check for secrets (BLOCK)
if git diff --cached | grep -qE "api_key|api_secret|password"; then
  echo "❌ Potential secret detected. Use environment variables!"
  exit 1
fi

# 3. Check for large files (BLOCK)
large_files=$(git diff --cached --name-only | xargs -I{} du -b {} 2>/dev/null | awk '$1 > 1048576 {print $2}')
if [ -n "$large_files" ]; then
  echo "❌ Large files detected (>1MB): $large_files"
  echo "Use Git LFS for large files."
  exit 1
fi

# 4. Auto-format with Prettier (FIX, don't block)
if command -v npx &> /dev/null; then
  npx prettier --write $(git diff --cached --name-only -- '*.js' '*.ts' '*.css' 2>/dev/null) 2>/dev/null || true
  git add -u  # Re-stage formatted files
fi

echo "✅ Pre-commit checks passed!"
exit 0
EOF
chmod +x .git/hooks/pre-commit
03The --no-verify Escape Hatch

git commit --no-verify skips ALL client-side hooks: pre-commit, prepare-commit-msg, commit-msg.

It exists because sometimes you NEED to commit without hooks: WIP commits, emergency fixes, fixing a broken hook.

But overusing --no-verify defeats the purpose of hooks entirely.

If developers frequently use --no-verify, your hooks are TOO SLOW or TOO STRICT.

Fix the root cause: make hooks faster, reduce false positives, allow warnings for non-critical issues.

NEVER make --no-verify part of your normal workflow. It should be the exception, not the rule.

💡 Pro Tip: Track how often your team uses --no-verify. If it is more than 10% of commits, your hooks need adjustment. Common fixes: only lint STAGED files (not entire project), use lint-staged for speed, move slow checks to pre-push or CI, and distinguish between errors (block) and warnings (allow).
04Auto-formatting in pre-commit: The Magic Pattern

Auto-formatters like Prettier or Black can run inside pre-commit and FIX code automatically.

The pattern: run formatter → re-stage the formatted files → commit proceeds with clean code.

This is MAGIC for teams — no more formatting arguments in code review.

Important: after auto-formatting, you must git add the changed files again so the formatted version is committed.

Use git diff --cached --name-only -- '*.js' to get only staged JS files for formatting.

This pattern is exactly what lint-staged automates (Chapter 8.5).

# Auto-format pattern in pre-commit
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
# Get staged JS/TS files
staged_js_files=$(git diff --cached --name-only --diff-filter=ACM -- '*.js' '*.ts')

if [ -n "$staged_js_files" ]; then
  # Run Prettier on staged files
  echo "🎨 Formatting code with Prettier..."
  npx prettier --write $staged_js_files 2>/dev/null
  
  # Re-stage the formatted files
  git add $staged_js_files
fi

exit 0
EOF
chmod +x .git/hooks/pre-commit

# Now when you commit:
echo "const x=1" > messy.js  # bad formatting
git add messy.js
git commit -m "add messy code"
# 🎨 Formatting code with Prettier...
# Prettier fixes the formatting automatically
# git add re-stages the fixed version
# Commit contains CLEAN code, not messy code!

# Check the committed version
git show HEAD:messy.js
# const x = 1;  ← properly formatted!
05Preventing Large Files and Secrets

Two things you should NEVER commit: large binary files and secrets (API keys, passwords).

Large files bloat the repository forever. Use Git LFS instead (covered in Chapter 10.8).

Secrets in Git history are a security incident. Even if deleted, they remain in history.

Pre-commit is your last line of defense — check for these BEFORE they enter history.

Tools: gitleaks or detect-secrets for finding secrets in staged files.

For large files: check file size against a limit (e.g., 1MB or 5MB) before allowing commit.

But remember: this is client-side. A developer can bypass with --no-verify. Server-side checks (GitHub secret scanning) are the real enforcement.

GitHub now has built-in secret scanning that runs server-side — it catches secrets even if you bypass local hooks. But catching them locally with a pre-commit hook is MUCH better because the secret never reaches GitHub at all. Once it is on GitHub (even in a private repo), consider it compromised. Rotate the key immediately. Prevention is infinitely better than remediation.

Lo kar liya — Key Points:

  • ✅ pre-commit hook runs BEFORE git commit creates the commit — it can block bad code from entering history
  • ✅ Common checks: linting, formatting, debug statements, large files, secrets
  • ✅ Exit code 0 = allow commit, non-zero = block commit
  • ✅ Auto-formatting pattern: run formatter → git add formatted files → commit proceeds with clean code
  • ✅ Keep pre-commit under 5 seconds — slow hooks make developers use --no-verify, defeating the purpose
  • ✅ git commit --no-verify bypasses ALL hooks — use only for emergencies, not as regular practice
  • ✅ Pre-commit is client-side enforcement — always pair with server-side checks (CI) for defense in depth
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase