Git hooks

Git · Advanced · 10 min

Hooks catch lint errors, bad messages and failing tests before they leave your machine.

Hooks are executable scripts Git runs at certain events. They live in .git/hooks/ (which ships .sample files to copy) or in a directory set with core.hooksPath. A hook is any executable named after the event, in any language with a shebang line.

Common client-side hooks:

  • pre-commit: before the commit message is asked for. Exit non-zero to abort, which is ideal for linters and formatters.
  • prepare-commit-msg / commit-msg: edit or validate the message, for example to enforce Conventional Commits or an issue ID. commit-msg receives the message file path as $1.
  • pre-push: before anything is sent; run the faster test suite here.
  • post-checkout / post-merge: after switching or pulling, for example to reinstall dependencies when a lockfile changed.

Servers have their own hooks (pre-receive, update, post-receive), which hosting platforms expose as protected branches, required checks and webhooks.

Two facts shape how hooks are used in teams:

  • Hooks are not cloned. .git/hooks isn't versioned, so to share them commit a folder such as .githooks/ and have everyone run git config core.hooksPath .githooks, or use a manager like pre-commit, Husky or Lefthook that installs them.
  • Hooks are bypassable. git commit --no-verify (-n) skips pre-commit and commit-msg; git push --no-verify skips pre-push. So hooks are a convenience for fast feedback, and CI remains the real gate.

Keep hooks fast, a couple of seconds at most, and check only staged files, or people will start using --no-verify by habit.

Example

mkdir -p .githooks
cat > .githooks/commit-msg <<'END'
#!/bin/sh
# Require "ABC-123: summary" style messages
if ! head -1 "$1" | grep -qE '^[A-Z]+-[0-9]+: .+'; then
  echo "commit-msg: start the message with a ticket ID, e.g. 'ABC-12: Fix login'" >&2
  exit 1
fi
END
chmod +x .githooks/commit-msg
git config core.hooksPath .githooks
git add .githooks && git commit -m "ABC-7: Add commit-msg hook"
git commit -m "oops"        # rejected by the hook

Remember

  • A hook is an executable named after the event in .git/hooks or core.hooksPath
  • Non-zero exit from pre-commit, commit-msg or pre-push aborts the action
  • Hooks aren't cloned; share via core.hooksPath or a hook manager
  • --no-verify bypasses them, so CI must still enforce rules

Watch out for

  • Forgetting chmod +x on the hook file — Git silently ignores non-executable hooks.

Check yourself

  1. Why can't you rely on client-side hooks to enforce a policy?
  2. Where does a commit-msg hook find the message it should check?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: git-scm.com/book/en/v2/Customizing-Git-Git-Hooks

Builds on

Unlocks

Nothing builds on this yet.