Installing pre-commit and triggering a block
What you're going to do
This module doesn't depend on the previous one (module 4, on reading devcontainer.json): it's a second, independent topic, that touches neither Docker nor Dev Containers. You'll install the pre-commit tool, configure it with a single simple hook, then deliberately trigger a blocked commit to see, with your own eyes, what that looks like — exactly the scenario described in module 2.
Prerequisite: module 1 must be green (git installed and configured). You'll also need Python 3 with pip available on your machine — most recent systems already have them. If not, install Python from python.org, or use pipx if you'd rather keep the install isolated in its own environment.
Step 1 — install pre-commit
pip install pre-commit
Alternative with pipx, if you'd rather not install Python tools globally:
pipx install pre-commit
Verify:
pre-commit --version
Expected healthy output: a version number, for example pre-commit 4.6.0 — the exact value doesn't matter, only that you get a number back (not an error).
Step 2 — create a git repo for the exercise
mkdir my-pre-commit-project
cd my-pre-commit-project
git init
Expected output:
Initialized empty Git repository in .../my-pre-commit-project/.git/
If git has never been used on this machine, a few hint: lines about the default branch name may print before this line — ignore them, that's not an error.
Step 3 — write .pre-commit-config.yaml
At the root of this folder, create a .pre-commit-config.yaml file with exactly this content:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
What this file says: repos is a list of source repositories for hooks. Here, a single repo, pre-commit/pre-commit-hooks — an official collection of generic hooks. rev pins the exact version (the v6.0.0 tag) of that repo to use, so the configuration is reproducible across machines, as discussed in module 2. hooks lists the hooks to enable among those the repo offers; here just one, identified by id: trailing-whitespace, which detects (and fixes) trailing whitespace.
Step 4 — install the git hook
pre-commit install
Expected output:
pre-commit installed at .git/hooks/pre-commit
This command writes a script into .git/hooks/pre-commit, the native hook that git automatically runs before every commit in this repo. You'll inspect this script in detail in the next module — for now, just note that it exists and is now active.
Step 5 — trigger a block
Create a file with a line ending in extra whitespace:
printf 'hello everyone \n' > example.txt
Stage it and try to commit:
git add example.txt
git commit -m "add example"
Expected output — the commit is rejected:
Trim Trailing Whitespace.................................................Failed
- hook id: trailing-whitespace
- exit code: 1
- files were modified by this hook
Fixing example.txt
What happened: the trailing-whitespace hook inspected example.txt, stripped the trailing whitespace directly on the file on disk, then returned a non-zero exit code because it modified something. A hook that returns a non-zero exit code fails the whole check, and git aborts the commit — the original content (with the extra whitespace) never enters the history.
Verify it:
git status
You'll see example.txt listed twice: under "Changes to be committed" (the original version, still pending in the index) and under "Changes not staged for commit" (the version the hook fixed on disk). That's expected: the commit never happened, so the index still holds the old version, while the hook modified the file on disk afterward.
Step 6 — fix and commit again
The file is already fixed on disk (the hook did that). All that's left is to re-stage it and commit again:
git add example.txt
git commit -m "add example"
This time, the hook passes:
Trim Trailing Whitespace.................................................Passed
And the commit is accepted:
[main (root-commit) a1b2c3d] add example
1 file changed, 1 insertion(+)
create mode 100644 example.txt
(The branch name shown — main or master — depends on your machine's init.defaultBranch setting; either is normal, only the rest of the line matters.)
What you just did
You installed pre-commit, wrote a minimal configuration pointing to an official, versioned hook, activated the git hook that runs it, then confirmed in practice — not just in theory — that a commit containing a detectable problem gets rejected before entering the history. This is exactly the safety net described in module 2: nothing broken ever ships into the shared repo without passing through this check.
In the next module, you'll open the .git/hooks/pre-commit file generated in step 4 to understand what it actually contains, and how it differs from a native git hook you'd have written yourself.
Check your understanding
In .pre-commit-config.yaml, what is the rev field for?
During the first commit attempt with the file containing trailing whitespace, what actually happened?
Which command created the .git/hooks/pre-commit file used to block your commit?
Want to hear about the next modules?
The Academy stays free and open-access, no sign-up required. If you'd just like to be notified by email when a new module ships, here you go — no obligation, unsubscribe anytime with one click.