Git Hooks — Automate Quality Control
Module 05 45 min
Section Objectives
- Understand what Git hooks are and how they work
- Configure
pre-committo validate code quality - Configure
commit-msgto enforce commit message format - Share hooks with the team via Husky or pre-commit
What are Git Hooks?
Git hooks are scripts that run automatically at specific points in the Git workflow:
Hook Types
| Hook | When | Common Use |
|---|---|---|
pre-commit | Before commit | Linting, formatting, fast tests |
commit-msg | After entering message | Validate message format |
post-commit | After commit | Notifications, logging |
pre-push | Before push | Full test suite |
post-merge | After merge | Install dependencies |
post-checkout | After switch | Install dependencies |
pre-rebase | Before rebase | Safety checks |
Manual Hook Configuration
Hooks are stored in .git/hooks/:
# View available hook examples
ls .git/hooks/
# pre-commit.sample
# commit-msg.sample
# pre-push.sample
# ...
pre-commit Hook
Create the file .git/hooks/pre-commit with this content:
#!/bin/bash
echo "Running pre-commit checks..."
# 1. Check for Python syntax errors
if command -v python3 &>/dev/null; then
python3 -m py_compile *.py 2>/dev/null
if [ $? -ne 0 ]; then
echo "Python syntax error detected!"
exit 1
fi
fi
# 2. Check for debug artifacts
if git diff --cached | grep -E "console\.log|debugger|pdb\.set_trace|TODO REMOVE"; then
echo "Debug artifacts found in staged files!"
echo "Remove console.log, debugger, pdb.set_trace before committing."
exit 1
fi
# 3. Check for .env files accidentally staged
if git diff --cached --name-only | grep -E "\.env$|\.env\..+$|credentials"; then
echo "Sensitive file detected in staging area!"
echo "Remove .env or credential files before committing."
exit 1
fi
echo "All pre-commit checks passed!"
exit 0
chmod +x .git/hooks/pre-commit
commit-msg Hook
Enforces Conventional Commits format. Create .git/hooks/commit-msg:
#!/bin/bash
COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
# Conventional Commits pattern
PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf|ci|revert|build)(\(.+\))?: .{1,100}"
if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
echo "Invalid commit message format!"
echo ""
echo "Expected format: type(scope): description"
echo "Types: feat, fix, docs, style, refactor, test, chore, perf, ci, revert, build"
echo ""
echo "Examples:"
echo " feat(auth): add JWT authentication"
echo " fix(api): handle 404 for missing users"
echo " docs: update README"
echo ""
echo "Your message: '$COMMIT_MSG'"
exit 1
fi
echo "Commit message format OK"
exit 0
chmod +x .git/hooks/commit-msg
pre-push Hook
Create .git/hooks/pre-push:
#!/bin/bash
echo "Running test suite before push..."
if command -v python3 &>/dev/null && [ -f "test_app.py" ]; then
python3 test_app.py
if [ $? -ne 0 ]; then
echo "Tests failed! Push aborted."
exit 1
fi
fi
echo "All tests passed! Pushing..."
exit 0
chmod +x .git/hooks/pre-push
Sharing Hooks with the Team
Hooks in .git/hooks/ are not committed (.git/ is not tracked). To share hooks:
Option 1: Python pre-commit Tool
pip install pre-commit
Create .pre-commit-config.yaml:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-merge-conflict
- id: detect-private-key
- id: no-commit-to-branch
args: ['--branch', 'main']
- repo: https://github.com/psf/black
rev: 24.1.0
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
rev: 7.0.0
hooks:
- id: flake8
args: ['--max-line-length=100']
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v3.0.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
pre-commit install
pre-commit install --hook-type commit-msg
pre-commit run --all-files
Option 2: Husky (JavaScript/Node.js projects)
npm install --save-dev husky
npx husky init
echo "npm run lint" > .husky/pre-commit
echo "npm test" > .husky/pre-push
Create .husky/commit-msg:
npx --no -- commitlint --edit $1
npm install --save-dev @commitlint/cli @commitlint/config-conventional
echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js
Bypassing Hooks (for Emergencies)
# Skip the pre-commit hook (use with caution!)
git commit --no-verify -m "emergency: hotfix prod outage"
# Skip the pre-push hook
git push --no-verify
Use with caution
--no-verify bypasses all quality checks. Only use it for genuine production emergencies, not as a shortcut to avoid fixing issues. Document the reason in your commit message.
Summary
| Hook | Common Use |
|---|---|
pre-commit | Linting, formatting, detect secrets |
commit-msg | Validate Conventional Commits |
pre-push | Run full test suite |
post-merge | Run npm install after merge |
| Tool | Ecosystem | Description |
|---|---|---|
pre-commit | Universal | YAML config, Python-based |
husky | Node.js | Hooks via npm scripts |
lefthook | Universal | Fast, Go-based alternative |
Next Steps
- Hands-on Lab TP6 — Hooks, tags, cherry-pick in practice