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 Bisect Tutorial: Find the Bad Commit Fast
Use git bisect to binary-search through history and pinpoint the commit that introduced a regression, with manual and automated examples.
- Git Git Cherry-pick and Revert Tutorial
Learn how to copy specific commits across branches with cherry-pick and how to safely undo merged changes with revert, including conflict handling and recovery.
- Git Git Large File Storage (LFS) Tutorial
Set up Git LFS to version large binaries like images, models, and datasets without bloating your repository, including tracking, migration, and CI tips.
- Git Git Pull Request Best Practices
How to open pull requests reviewers actually enjoy: small scope, clear descriptions, good commit hygiene, useful CI, and how to handle review feedback without endless back-and-forth.