Learning Goals
3 min- Explain what a git hook is and why pre-commit checks help.
- Install the
pre-commitframework and write.pre-commit-config.yaml. - Wire up ruff, ruff-format, and mypy as hooks.
- Run hooks manually and understand the (rare) bypass.
Warm-Up · A Gate at the Door
5 min$ git commit -m "add feature" ruff.....................................Passed ruff-format..............................Failed - files were modified by this hook ← auto-formatted; re-stage & retry mypy.....................................Passed # the commit is BLOCKED until everything passes.
A pre-commit hook is a script git runs before finalising a commit. The pre-commit framework makes it trivial to run your linters/formatters/type-checker automatically — so unformatted or broken code is caught at the door, not in review or production.
New Concept · The pre-commit Framework
14 minInstall & activate
pip install pre-commit # create the config (below), then: pre-commit install # installs the git hook into .git/hooks/ # now hooks run automatically on every 'git commit'
The config file
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.0
hooks:
- id: ruff # lint
args: [--fix]
- id: ruff-format # format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.0
hooks:
- id: mypy
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yamlRun on demand
pre-commit run --all-files # run every hook on the whole repo (great first time) pre-commit run ruff # run just one hook pre-commit autoupdate # bump the hook versions
Hooks only check staged files
By default pre-commit runs on the files you're committing — fast feedback. Auto-fixing hooks (ruff --fix, formatters) modify files, which fails the commit so you re-stage and commit again with the fixes included.
The bypass (use rarely!)
git commit --no-verify # skips ALL hooks — emergency use only
Skipping hooks defeats their purpose. Reserve --no-verify for genuine emergencies, and fix the underlying issue right after. Don't make it a habit.
Tests in pre-commit?
Linting/formatting/types are fast and belong in pre-commit. A full test suite is often too slow to run on every commit — run fast unit tests here if quick, but leave the full suite (and slow E2E) to CI (next lesson).
Worked Example · A Real Hook Run
12 minYou stage a file with a formatting issue and an unused import, then commit:
$ git add cart.py $ git commit -m "add cart" trailing-whitespace......................Passed end-of-file-fixer........................Passed ruff.....................................Failed - hook id: ruff cart.py:1:8: F401 'os' imported but unused [*] Fixed 1 error. ← --fix removed the import ruff-format..............................Failed - files were modified by this hook mypy.....................................Passed # commit was BLOCKED because files changed. Re-stage and commit again: $ git add cart.py $ git commit -m "add cart" ... all hooks Passed ... [main a1b2c3d] add cart
Read the diff
The first commit failed — ruff removed the unused import and the formatter reflowed the file. Because the hooks changed files, you re-stage and commit again, now with clean code. The result: every commit in the repo's history is linted, formatted, and type-checked. No more "oops, forgot to run black" in review.
# the team onboards in one line — the config travels in the repo: # new dev: pip install pre-commit && pre-commit install # from then on, quality is automatic for everyone.
Try It Yourself
13 minIn a git repo, add a .pre-commit-config.yaml with ruff + ruff-format, run pre-commit install, then pre-commit run --all-files.
Commit a deliberately messy file. Watch the hooks fail/fix it. Re-stage and commit clean. Confirm the history only has clean code.
Add mypy and a local hook that runs fast unit tests (pytest -m "not slow"). Confirm a failing test blocks the commit.
Hint
- repo: local
hooks:
- id: pytest-fast
name: pytest (fast)
entry: pytest -m "not slow" -q
language: system
pass_filenames: falseMini-Challenge · The Quality Gate
8 minBuild a complete pre-commit config for a project: trailing-whitespace, end-of-file-fixer, ruff (--fix), ruff-format, mypy, and fast unit tests. Run it across the whole repo and fix everything until it's green. Now bad code literally cannot be committed.
Recap
3 minThe pre-commit framework runs your quality tools on every commit via .pre-commit-config.yaml + pre-commit install. Put fast checks here (ruff, format, mypy, quick tests); leave the full/slow suite to CI. Auto-fixers modify files and block the commit so you re-stage. --no-verify bypasses — emergencies only. Next: CI runs all of this in the cloud.
Vocabulary Card
- git hook
- A script git runs at a lifecycle event (e.g. before a commit).
- pre-commit (framework)
- A tool that manages and runs hooks from a YAML config.
- quality gate
- An automated barrier code must pass before it's accepted.
- --no-verify
- Skips hooks for one commit — emergency use only.
Homework
4 minAdd a full .pre-commit-config.yaml to a real git project (whitespace fixers + ruff + ruff-format + mypy + fast tests), install it, and get pre-commit run --all-files green. Demonstrate that a bad commit is blocked. Commit the config so the whole team inherits the gate.
Use the config from the lesson + the local pytest-fast hook from Try-It #3. The graded proof: a deliberately broken commit is rejected.