Skip to content
Laviyx Learn

Try

Git and GitHub course menu

Git Hooks Tutorial: Run Checks Before Every Commit

Automate checks with Git hooks: write a pre-commit hook, check commit messages with commit-msg, share hooks with your team using core.hooksPath, Husky or pre-commit, and bypass hooks when needed.

Advanced7 min readUpdated
On this page

A hook is a script that Git runs automatically at certain moments, such as before a commit. Teams use hooks to run linters and tests, and to check commit messages, before a mistake leaves your computer.

Hooks live in .git/hooks/. Git ships sample files there, ending in .sample, which do nothing until renamed.

HookRunsCommon use
pre-commitBefore the commit is createdLint and format, find secrets
commit-msgAfter you write the messageCheck the message format
pre-pushBefore a pushRun the tests
post-mergeAfter a merge or pullInstall changed dependencies

Write a pre-commit hook#

If a hook exits with a non-zero code, Git cancels the action. This hook refuses a commit containing the word TODO:

Terminal
cat > .git/hooks/pre-commit <<'EOF'
#!/bin/sh
if git diff --cached | grep -q '^+.*TODO'; then
  echo "Commit blocked: remove TODO first."
  exit 1
fi
EOF
chmod +x .git/hooks/pre-commit

git diff --cached shows what is staged (it is another name for --staged). chmod +x makes the file executable, which is required on macOS and Linux.

Try it:

Terminal
echo "TODO: finish this" >> app.txt
git add app.txt
git commit -m "Add task"
git log --oneline
Output
Commit blocked: remove TODO first.
617d59f Start

The hook printed its message, and no commit was created. Fix the line and try again:

Terminal
sed -i 's/TODO: finish this/Finished/' app.txt
git add app.txt
git commit -q -m "Add task"
git log --oneline
Output
73bc778 Add task
617d59f Start

Check commit messages#

commit-msg receives the path of the message file as its first argument. This hook enforces a Conventional Commits prefix:

Terminal
cat > .git/hooks/commit-msg <<'EOF'
#!/bin/sh
if ! grep -qE '^(feat|fix|docs|chore|test|refactor)(\(.+\))?!?: ' "$1"; then
  echo "Bad message. Use a prefix, for example: feat: add login"
  exit 1
fi
EOF
chmod +x .git/hooks/commit-msg
echo "more" >> app.txt
git add app.txt
git commit -m "add stuff"
git commit -q -m "feat: add more text"
git log --oneline -n 1
Output
Bad message. Use a prefix, for example: feat: add login
52cb096 feat: add more text

Skipping hooks#

--no-verify (or -n) skips pre-commit and commit-msg:

Terminal
git commit --no-verify -m "Emergency fix"

This is why hooks are only a convenience. Anyone can skip them, so important checks should also run in CI, where they cannot be avoided.

Sharing hooks with a team#

The .git folder is not committed, so hooks there are not shared. There are three common solutions.

1. A hooks folder in the repository:

Terminal
mkdir .githooks
mv .git/hooks/pre-commit .githooks/
git config core.hooksPath .githooks
git config core.hooksPath
Output
.githooks

Each developer needs to run that git config command once.

2. Husky, for JavaScript projects. It installs hooks when people run npm install:

Terminal
npm install --save-dev husky
npx husky init

3. pre-commit, a tool written in Python that works for any language. You list ready-made checks in a .pre-commit-config.yaml file:

Terminal
pip install pre-commit
pre-commit install

Keep hooks fast#

Developers will skip a hook that takes a minute. Run quick checks such as linting on changed files in pre-commit, and leave the full test suite for pre-push or CI.

Common mistakes#

Practice#

Practice exercise

Write a pre-commit hook that blocks any commit that adds a file ending in .env. Test it by trying to commit prod.env.

Show hint

git diff --cached --name-only lists the staged files.

Show solution
Terminal
cat > .git/hooks/pre-commit <<'EOF'
#!/bin/sh
if git diff --cached --name-only | grep -q '\.env$'; then
  echo "Blocked: do not commit .env files."
  exit 1
fi
EOF
chmod +x .git/hooks/pre-commit
echo "KEY=abc" > prod.env
git add prod.env
git commit -m "Add settings"
Output
Blocked: do not commit .env files.

Key takeaway#

Next: Secrets and Large Files.

Finished this lesson?