Git Hooks Tutorial โ Automate Your Git Workflow
In this tutorial, you'll learn about Git Hooks Tutorial. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
What You'll Learn
Use Git hooks to automate tasks โ lint code before commits, enforce commit message formats, run tests before push, and trigger deployments.
Why It Matters
Git hooks prevent bad commits before they enter your history. They enforce your team's standards automatically โ no more "forgot to lint" or "wrong commit format."
Real-World Use
Blocking a commit if tests fail, ensuring every commit follows Conventional Commits, or running a linter before allowing code to be pushed.
What are Git Hooks?
Git hooks are scripts that run automatically at specific points in Git's workflow. They're stored in .git/hooks/ in every Repository.
Available Hooks
Client-Side Hooks
| Hook | When It Runs | Common Use |
|---|---|---|
| pre-commit | Before commit message | Lint, format, check for secrets |
| prepare-commit-msg | Before commit message editor | Auto-populate message |
| commit-msg | After commit message | Validate message format |
| post-commit | After commit | Notifications |
| pre-push | Before push | Run tests, check branches |
| post-checkout | After checkout | Update dependencies |
| post-merge | After merge | Install new dependencies |
| pre-rebase | Before rebase | Prevent rebase on certain branches |
Server-Side Hooks
| Hook | When It Runs | Common Use |
|---|---|---|
| pre-receive | Before push is accepted | Enforce policies |
| update | Per branch, during push | Branch-specific rules |
| post-receive | After push accepted | Deploy, CI triggers |
Pre-Commit Hook โ Lint Before Commit
#!/bin/bash
# .git/hooks/pre-commit
echo "Running linter..."
# Check for lint errors
npm run lint
if [ $? -ne 0 ]; then
echo "โ Linting failed. Fix errors before committing."
exit 1
fi
# Check for debugger statements
if grep -r "debugger" --include="*.js" --include="*.ts" .; then
echo "โ Found debugger statements. Remove them."
exit 1
fi
# Check for API keys
if grep -r "sk-[A-Za-z0-9_-]\{20,\}" --include="*.js" .; then
echo "โ Possible API key detected. Remove it."
exit 1
fi
echo "โ
All checks passed"
exit 0
Commit-Msg Hook โ Enforce Conventional Commits
#!/bin/bash
# .git/hooks/commit-msg
commit_msg=$(cat "$1")
# Format: type(scope): description
pattern="^(feat|fix|docs|style|refactor|test|chore|perf|ci|build)\([a-z-]+\): .{1,}$"
if ! [[ "$commit_msg" =~ $pattern ]]; then
echo "โ Invalid commit message format."
echo ""
echo "Expected format: type(scope): description"
echo ""
echo "Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build"
echo ""
echo "Example: feat(login): add OAuth authentication"
echo ""
echo "Your message: $commit_msg"
exit 1
fi
exit 0
Pre-Push Hook โ Run Tests
#!/bin/bash
# .git/hooks/pre-push
echo "๐งช Running tests before push..."
# Run the test suite
npm test
if [ $? -ne 0 ]; then
echo "โ Tests failed. Push rejected."
exit 1
fi
# Check if pushing to main without PR
while read local_ref local_sha remote_ref remote_sha; do
if [[ "$remote_ref" == "refs/heads/main" ]]; then
echo "โ Direct push to main is not allowed. Use a PR."
exit 1
fi
done
echo "โ
All checks passed. Pushing..."
exit 0
Using Husky (Node.js)
Husky makes hooks manageable and shareable:
npm install --save-dev husky
# package.json
{
"scripts": {
"prepare": "husky install"
}
}
npx husky add .husky/pre-commit "npx lint-staged"
npx husky add .husky/commit-msg "npx commitlint --edit $1"
npx husky add .husky/pre-push "npm test"
Pre-Commit Hooks with Python
#!/usr/bin/env python3
# .git/hooks/pre-commit
"""Pre-commit hook: Check for Python errors and formatting."""
import subprocess
import sys
def run_command(cmd):
result = subprocess.run(
cmd, shell=True, capture_output=True, text=True
)
if result.returncode != 0:
print(result.stdout)
print(result.stderr)
return result.returncode
def main():
errors = []
# Check for syntax errors
result = run_command("python -m py_compile $(git diff --cached --name-only | grep '\\.py$')")
if result != 0:
errors.append("Syntax errors found")
# Check with flake8
result = run_command("flake8 $(git diff --cached --name-only | grep '\\.py$')")
if result != 0:
errors.append("Style issues found (flake8)")
if errors:
print("โ Errors:", "; ".join(errors))
sys.exit(1)
print("โ
Python checks passed")
sys.exit(0)
if __name__ == "__main__":
main()
Advanced: Python pre-commit Framework
# .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-added-large-files
- repo: https://github.com/psf/black
rev: 24.2.0
hooks:
- id: black
- repo: https://github.com/commitizen-tools/commitizen
rev: v3.13.0
hooks:
- id: commitizen
pip install pre-commit
pre-commit install
Testing Hooks
# Make the hook executable
chmod +x .git/hooks/pre-commit
# Skip a hook temporarily
git commit --no-verify -m "WIP: skip hooks"
# Skip all hooks
git commit -n -m "Emergency fix"
Team Setup Script
#!/bin/bash
# setup-hooks.sh โ run once per developer clone
echo "Setting up Git hooks..."
# Symlink shared hooks
HOOK_DIR=$(git rev-parse --show-toplevel)/.githooks
git config core.hooksPath .githooks
echo "โ
Hooks configured. Files in .githooks/ are now active."
With core.hooksPath, hooks are tracked in your repo instead of .git/hooks/.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro