Git hooks are powerful scripts that Git executes before or after events such as commit, push, and receive. They're your secret weapon for automating repetitive tasks and enforcing code quality standards.
Understanding Git Hooks
Git hooks are scripts that live in the .git/hooks
directory of every Git repository. They're triggered automatically by Git events and can be written in any scripting language.
Types of Git Hooks
Client-Side Hooks
- pre-commit: Runs before a commit is created
- prepare-commit-msg: Runs before the commit message editor is fired up
- commit-msg: Validates the commit message
- post-commit: Runs after a commit is created
- pre-push: Runs before code is pushed to remote
- pre-rebase: Runs before a rebase
Server-Side Hooks
- pre-receive: Runs when receiving a push
- update: Similar to pre-receive but runs once per branch
- post-receive: Runs after a push is received
Setting Up Your First Hook
Basic Pre-Commit Hook
Create .git/hooks/pre-commit
:
#!/bin/sh
# Run tests before commit
echo "Running tests..."
npm test
if [ $? -ne 0 ]; then
echo "Tests failed. Commit aborted."
exit 1
fi
echo "Tests passed. Proceeding with commit."
Make it executable:
chmod +x .git/hooks/pre-commit
Practical Git Hook Examples
1. Code Quality Pre-Commit Hook
#!/bin/sh
# .git/hooks/pre-commit
# Enforce code quality standards
# Run ESLint
echo "๐ Running ESLint..."
npx eslint --fix .
if [ $? -ne 0 ]; then
echo "โ ESLint found errors. Please fix them before committing."
exit 1
fi
# Run Prettier
echo "๐
Running Prettier..."
npx prettier --write .
git add -A
# Check for console.log statements
echo "๐ Checking for console.log statements..."
if git diff --cached | grep -E "\+.*console\.log"; then
echo "โ ๏ธ Warning: console.log statements detected"
read -p "Continue with commit? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
echo "โ
All checks passed!"
2. Commit Message Validation
#!/bin/sh
# .git/hooks/commit-msg
# Enforce conventional commit format
commit_regex='^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{1,50}'
if ! grep -qE "$commit_regex" "$1"; then
echo "โ Invalid commit message format!"
echo "๐ Format: <type>(<scope>): <subject>"
echo "๐ Example: feat(auth): add login functionality"
echo ""
echo "Types: feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert"
exit 1
fi
3. Pre-Push Protection
#!/bin/sh
# .git/hooks/pre-push
# Prevent pushing to protected branches
protected_branches="main master production"
current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,')
for branch in $protected_branches; do
if [ "$current_branch" = "$branch" ]; then
echo "โ Direct push to $branch branch is not allowed!"
echo "Please create a feature branch and open a pull request."
exit 1
fi
done
# Run tests before push
echo "๐งช Running tests before push..."
npm test
if [ $? -ne 0 ]; then
echo "โ Tests failed. Push aborted."
exit 1
fi
echo "โ
Push validation passed!"
4. Auto-Update Dependencies Post-Merge
#!/bin/sh
# .git/hooks/post-merge
# Auto-install dependencies after merge
changed_files="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)"
check_run() {
echo "$changed_files" | grep -E "$1" > /dev/null 2>&1
}
# JavaScript/Node.js
if check_run "package.json"; then
echo "๐ฆ package.json changed, running npm install..."
npm install
fi
# Python
if check_run "requirements.txt"; then
echo "๐ requirements.txt changed, updating pip packages..."
pip install -r requirements.txt
fi
# Ruby
if check_run "Gemfile"; then
echo "๐ Gemfile changed, running bundle install..."
bundle install
fi
Managing Hooks with Husky
Husky makes Git hooks shareable and easier to manage:
Installation
npm install --save-dev husky
npx husky install
Configuration
// package.json
{
"scripts": {
"prepare": "husky install"
}
}
Adding Hooks
# Add pre-commit hook
npx husky add .husky/pre-commit "npm test"
# Add commit-msg hook
npx husky add .husky/commit-msg 'npx commitlint --edit "$1"'
Advanced Hook Patterns
1. Conditional Hooks
#!/bin/sh
# Only run on specific file changes
files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.jsx\?$')
if [ -z "$files" ]; then
exit 0
fi
echo "$files" | xargs npx eslint
2. Branch-Specific Hooks
#!/bin/sh
# Different rules for different branches
branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$branch" = "develop" ]; then
npm run test:unit
elif [ "$branch" = "release" ]; then
npm run test:all
npm run build
fi
3. Security Scanning
#!/bin/sh
# Scan for secrets before commit
if which gitleaks > /dev/null; then
gitleaks protect --verbose --redact --staged
if [ $? -ne 0 ]; then
echo "โ Potential secrets detected! Commit aborted."
exit 1
fi
fi
Hook Templates and Sharing
Creating a Hooks Directory
# Create shared hooks directory
mkdir .githooks
# Configure Git to use it
git config core.hooksPath .githooks
# Add to repository
git add .githooks
git commit -m "Add shared Git hooks"
Team Setup Script
#!/bin/sh
# setup-hooks.sh
echo "Setting up Git hooks..."
git config core.hooksPath .githooks
chmod +x .githooks/*
echo "โ
Git hooks configured!"
Best Practices
1. Keep Hooks Fast
# Run only on changed files
files=$(git diff --cached --name-only)
if [ -z "$files" ]; then
exit 0
fi
2. Provide Bypass Options
# Allow emergency commits
if [ "$SKIP_HOOKS" = "1" ]; then
echo "โ ๏ธ Skipping hooks (SKIP_HOOKS=1)"
exit 0
fi
3. Give Clear Feedback
# Informative messages
echo "๐ Checking code style..."
echo "โ
Code style check passed"
echo "โ Code style check failed:"
echo " - Line 42: Missing semicolon"
echo " Run 'npm run fix' to auto-fix"
Troubleshooting Common Issues
Hook Not Executing
# Check permissions
ls -la .git/hooks/
# Fix: chmod +x .git/hooks/pre-commit
# Check shebang
head -n 1 .git/hooks/pre-commit
# Should be: #!/bin/sh or #!/bin/bash
Bypassing Hooks
# Skip pre-commit and commit-msg hooks
git commit --no-verify -m "Emergency fix"
# Skip pre-push hook
git push --no-verify
Debugging Hooks
#!/bin/sh
# Add debug output
set -x # Enable debug mode
echo "Current directory: $(pwd)"
echo "Git root: $(git rev-parse --show-toplevel)"
echo "Changed files: $(git diff --cached --name-only)"
Conclusion
Git hooks are a powerful way to automate your workflow and maintain code quality. Start with simple hooks and gradually add more sophisticated checks as your team grows comfortable with them.
Remember: hooks should help, not hinder. If they become a bottleneck, revisit and optimize them. The goal is to catch issues early while maintaining developer productivity.
With the right hooks in place, you'll spend less time on repetitive tasks and more time writing great code.
Share this article
David Childs
Consulting Systems Engineer with over 10 years of experience building scalable infrastructure and helping organizations optimize their technology stack.