Git Hooks Overview
Git hooks are scripts that run automatically at certain points in the Git workflow. They're powerful for enforcing standards and automating tasks.
Hook Types
Client-side Hooks:
├── pre-commit # Before commit is created
├── prepare-commit-msg # Before commit message editor
├── commit-msg # Validate commit message
├── post-commit # After commit is created
├── pre-rebase # Before rebase starts
├── post-rewrite # After commit is rewritten
├── post-checkout # After checkout
├── post-merge # After merge
└── pre-push # Before push
Server-side Hooks:
├── pre-receive # Before accepting push
├── update # Per-branch before update
└── post-receive # After push is accepted
pre-commit Hook
#!/bin/bash
# .git/hooks/pre-commit
# Run linter
npm run lint
if [ $? -ne 0 ]; then
echo "Linting failed. Fix errors before committing."
exit 1
fi
# Run tests
npm test
if [ $? -ne 0 ]; then
echo "Tests failed. Fix tests before committing."
exit 1
fi
# Check for console.log
if git diff --cached | grep -E "console\.log" > /dev/null; then
echo "Warning: console.log found in staged changes"
# exit 1 # Uncomment to block commit
fi
# Check for large files
for file in $(git diff --cached --name-only); do
size=$(wc -c < "$file" 2>/dev/null || echo 0)
if [ $size -gt 1000000 ]; then
echo "Error: $file is larger than 1MB"
exit 1
fi
done
exit 0
commit-msg Hook
#!/bin/bash
# .git/hooks/commit-msg
commit_msg_file=$1
commit_msg=$(cat "$commit_msg_file")
# Conventional commits pattern
pattern="^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,50}"
if ! echo "$commit_msg" | grep -qE "$pattern"; then
echo "Error: Commit message doesn't follow conventional commits"
echo "Format: type(scope): description"
echo "Types: feat, fix, docs, style, refactor, test, chore"
exit 1
fi
# Check minimum length
msg_length=${#commit_msg}
if [ $msg_length -lt 10 ]; then
echo "Error: Commit message too short (min 10 chars)"
exit 1
fi
exit 0
pre-push Hook
#!/bin/bash
# .git/hooks/pre-push
remote="$1"
url="$2"
# Prevent push to main
current_branch=$(git branch --show-current)
if [ "$current_branch" = "main" ]; then
echo "Error: Direct push to main is not allowed"
echo "Please create a pull request instead"
exit 1
fi
# Run full test suite before push
npm run test:all
if [ $? -ne 0 ]; then
echo "Tests failed. Fix before pushing."
exit 1
fi
# Check for WIP commits
if git log @{u}.. --oneline | grep -i "wip" > /dev/null; then
echo "Warning: WIP commits detected. Continue? (y/n)"
read -r response
if [ "$response" != "y" ]; then
exit 1
fi
fi
exit 0
Using Husky
# Install Husky
npm install husky -D
# Initialize
npx husky init
# Add hook
echo "npm test" > .husky/pre-commit
# Add commit-msg hook
cat > .husky/commit-msg << 'EOF'
#!/bin/sh
npx --no -- commitlint --edit $1
EOF
Husky with lint-staged
// package.json
{
"scripts": {
"prepare": "husky"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{css,scss}": [
"stylelint --fix"
],
"*.{json,md}": [
"prettier --write"
]
}
}
# .husky/pre-commit
#!/bin/sh
npx lint-staged
Commitlint Configuration
// commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat',
'fix',
'docs',
'style',
'refactor',
'perf',
'test',
'build',
'ci',
'chore',
'revert',
],
],
'subject-min-length': [2, 'always', 10],
'subject-max-length': [2, 'always', 72],
'body-max-line-length': [2, 'always', 100],
},
};
Server-side Hook Example
#!/bin/bash
# hooks/pre-receive (server-side)
while read oldrev newrev refname; do
# Block force push
if [ "$oldrev" != "0000000000000000000000000000000000000000" ]; then
if ! git merge-base --is-ancestor "$oldrev" "$newrev"; then
echo "Error: Force push detected and blocked"
exit 1
fi
fi
# Block push to protected branches
branch=$(echo "$refname" | sed 's/refs\/heads\///')
if [[ "$branch" =~ ^(main|production)$ ]]; then
echo "Error: Direct push to $branch is blocked"
exit 1
fi
done
exit 0
Sharing Hooks
# Store hooks in repository
mkdir -p .githooks
# Configure Git to use custom hooks directory
git config core.hooksPath .githooks
# Add to project setup
# In package.json or Makefile:
# "prepare": "git config core.hooksPath .githooks"
Common Hook Patterns
- Pre-commit: Lint, format, run unit tests
- Commit-msg: Validate message format
- Pre-push: Full test suite, prevent WIP
- Post-checkout: Install dependencies
- Post-merge: Warn about dependency changes