Git Hooks and pre-commit Tutorial
Automate checks before commits, pushes, and merges with native git hooks and the pre-commit framework. Keep your repo clean without slowing down.
What you'll learn
- ✓What git hooks are and where they live
- ✓Useful hooks: pre-commit, commit-msg, pre-push
- ✓How the pre-commit framework simplifies setup
- ✓Sharing hooks across a team
- ✓When to bypass hooks safely
Prerequisites
- •Comfortable with basic git commands
What and Why
Hooks are scripts git runs at certain points in the workflow. They can format code, run linters, validate commit messages, or block bad pushes. Done well, hooks catch issues before they hit CI. Done poorly, they annoy everyone. The trick is fast, focused hooks that only fix what is changed.
Mental Model
Hooks live in .git/hooks/ by default and are not committed with the repo. Tools like pre-commit solve this by storing a config file in the repo and installing the hooks on demand. A hook is just an executable script that exits 0 to allow the action or non-zero to abort it.
git commit
-> pre-commit (lint, format, run quick tests)
-> prepare-commit-msg
-> commit-msg (validate message)
-> post-commit
git push
-> pre-push (run extra checks before sharing) Hands-on Example
A handwritten pre-commit hook.
# .git/hooks/pre-commit
#!/usr/bin/env bash
set -e
# Only check staged files
files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.py$' || true)
if [ -n "$files" ]; then
ruff check $files
ruff format --check $files
fi
Make it executable. chmod +x .git/hooks/pre-commit.
Better: use the pre-commit framework so the whole team gets the same hooks.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
hooks:
- id: ruff
- id: ruff-format
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- repo: local
hooks:
- id: no-todo
name: block TODOs in commits
entry: bash -c 'git diff --cached -U0 | grep -E "^\+.*TODO" && exit 1 || exit 0'
language: system
pass_filenames: false
Install once, enjoy forever.
pipx install pre-commit
pre-commit install # installs the git hook
pre-commit install --hook-type commit-msg
pre-commit run --all-files # run against everything
Validate commit messages with commit-msg.
# .git/hooks/commit-msg
#!/usr/bin/env bash
msg_file=$1
pattern='^(feat|fix|docs|chore|refactor|test)(\(.+\))?: .{1,72}'
if ! grep -qE "$pattern" "$msg_file"; then
echo "Commit message must follow Conventional Commits"
exit 1
fi
Heavy checks like the full test suite belong in pre-push, not pre-commit.
# .git/hooks/pre-push
#!/usr/bin/env bash
npm test --silent
Common Pitfalls
- Hooks that scan the whole repo. They get slow and people start using
--no-verify. Only check staged files. - Network calls in hooks. They block commits and break on bad wifi.
- Inconsistent hooks across machines. Use the
pre-commitframework so versions are pinned. - Auto-formatting that conflicts with the editor. Pick one source of truth.
- Using hooks as the only check. CI must enforce the same rules; hooks are a courtesy, not a guarantee.
Practical Tips
- Bypass with
git commit --no-verifyfor emergencies, but log a follow-up. - Set
default_stagesin the pre-commit config to limit when hooks run. - Add
pre-commit autoupdateto dependabot or to a weekly cron. - Run hooks in CI too with
pre-commit run --all-files. Same rules, no surprises. - Keep total runtime under a couple of seconds for
pre-commit. Anything slower belongs inpre-pushor CI.
Wrap-up
Git hooks are a small tool with a big impact when they stay fast and focused. The pre-commit framework turns ad-hoc shell scripts into a versioned, shareable config. Use hooks to catch the obvious stuff at commit time, save slower checks for push or CI, and your repo stays clean without slowing anyone down.
Related articles
- Git Git Hooks for CI/CD and Automation Pipelines
Build custom Git hooks that integrate with CI/CD pipelines: server-side hooks, push-based triggers, branch protection enforcement, and automated deployment gates.
- Git Git Bisect: Find the Commit That Introduced a Bug
Learn how to use git bisect to perform binary search through your commit history and pinpoint the exact commit that introduced a bug in your codebase.
- Git Git Monorepo Management at Scale
Manage large Git monorepos with sparse checkout, partial clone, subtrees, CODEOWNERS, and scaling strategies that keep performance acceptable as the repo grows.
- Git Git Rebasing Strategies for Clean Team Histories
Advanced git rebase strategies for teams: interactive rebase workflows, rebase --onto, autosquash, and policies that keep shared branches clean without rewriting public history.