Git Hooks — Automation
Git hooks are scripts that run automatically at specific points in the Git workflow. They let you enforce standards, run tests, and automate tasks without relying on developer discipline.
How Hooks Work
Hooks live in the .git/hooks/ directory. When you initialize a repo, Git creates sample hooks with a .sample extension. Remove the extension to activate them:
.git/hooks/
pre-commit.sample → rename to pre-commit to activate
commit-msg.sample → rename to commit-msg to activate
pre-push.sample → rename to pre-push to activate
Hooks can be written in any language — Bash, Python, Ruby, Node.js — as long as the file is executable.
Most Useful Hooks
pre-commit
Runs before a commit is created. Use it for:
- Linting — check code style
- Formatting — auto-format staged files
- Secrets detection — prevent committing API keys
#!/bin/sh
# .git/hooks/pre-commit
# Run linter on staged files
npm run lint --staged
if [ $? -ne 0 ]; then
echo "Lint failed. Fix errors before committing."
exit 1
fi
If the script exits with a non-zero code, the commit is aborted.
commit-msg
Runs after you write a commit message. Use it to enforce message format:
#!/bin/sh
# .git/hooks/commit-msg
# Require commit messages to start with a type prefix
if ! grep -qE "^(feat|fix|docs|style|refactor|test|chore):" "$1"; then
echo "Commit message must start with: feat:, fix:, docs:, etc."
exit 1
fi
pre-push
Runs before pushing to a remote. Use it for:
- Running the full test suite
- Preventing pushes to protected branches
#!/bin/sh
# .git/hooks/pre-push
# Run tests before pushing
npm test
if [ $? -ne 0 ]; then
echo "Tests failed. Push aborted."
exit 1
fi
Hook Tools
Managing hooks manually is tedious. Popular tools help:
- Husky (JavaScript) — manages hooks via
package.json - pre-commit (Python) — multi-language hook framework
- lefthook (Go) — fast, language-agnostic hook manager
Example with Husky:
{
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
}
Client-Side vs Server-Side
| Type | Where | Examples |
|---|---|---|
| Client-side | Developer's machine | pre-commit, commit-msg, pre-push |
| Server-side | Git server | pre-receive, post-receive, update |
Server-side hooks enforce rules that cannot be bypassed. Client-side hooks can be skipped with --no-verify.
Common Patterns
- Lint on commit — catch style issues early
- Run tests on push — prevent broken code from reaching the remote
- Enforce commit messages — keep history clean and searchable
- Auto-format — apply Prettier/Black on staged files
- Check branch names — enforce naming conventions like
feature/,fix/
Limitations
- Hooks are not pushed with the repo (they live in
.git/hooks/) - Developers must set them up locally (or use tools like Husky)
- Client-side hooks can be bypassed with
git commit --no-verify