Git hooks are scripts that run automatically at fixed points in the workflow — before a commit is created, before a push leaves the machine, after a checkout. They turn "remember to run the linter" into something that runs because the workflow made it run.
Where they live and what exists #
$ ls .git/hooks/ # samples end in .sample; rename to activate
applypatch-msg pre-rebase pre-push post-checkout
prepare-commit-msg pre-commit commit-msg post-merge
A hook is any executable script named exactly like the event — no extension, marked executable, in .git/hooks/. Hooks are not versioned with the repository (they are in .git/), which is the central logistics problem of sharing them — solved below.
The two hooks everyone starts with #
pre-commit runs before the commit is created; exiting non-zero cancels it:
#!/usr/bin/env bash
set -e
files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.js$' || true)
[ -z "$files" ] && exit 0
echo "$files" | xargs npx eslint --max-warnings 0
commit-msg receives the message file as $1 — the format police:
#!/usr/bin/env bash
head -1 "$1" | grep -qE '^(feat|fix|chore|docs|refactor|test)(\(.+\))?: .+' \
|| { echo "commit message must be 'type: summary'" >&2; exit 1; }
Sharing hooks: the logistics #
Because .git/hooks/ is not tracked, teams do one of:
| Approach | How |
|---|---|
| Committed hook files + install step | hooks/ in repo; git config core.hooksPath hooks |
| husky / pre-commit frameworks | Tooling manages install on setup |
| Symlinks from a setup script | Setup creates the links once |
$ git config core.hooksPath .githooks
$ # .githooks/ is now a tracked directory of hook scripts
core.hooksPath is the clean answer: commit the hooks like any script, point git at the directory, and every clone gets them after one config line — which the project README or a setup script runs.
What hooks can and cannot do #
Hooks are local: a pre-push hook on your machine does not run on a teammate's, and no hook can stop a determined --no-verify. So the architecture is: hooks for fast, local gates (format, lint, message style), CI for authoritative gates (build and test on the server). The two layers agree on the same commands, so surprises stay rare.
The commits hooks guard are add and commit; the messages they police follow the message format. The Git series continues.
