08 · Hooks¶
← Overview · Prev: Branch management · Next: Internals →
Hooks are executable scripts git runs at specific points. They live in .git/hooks/ (or the directory set by
core.hooksPath). They are local: not part of the repo, not pushed. Every developer must install them, or use a
shared directory.
| Hook | Runs | Typical use | Can abort? |
|---|---|---|---|
pre-commit |
before the commit is recorded | formatters, linters (cargo fmt, terraform fmt) |
yes |
prepare-commit-msg |
before the editor opens | pre-fill message (e.g. ticket key) | yes |
commit-msg |
after the message is written; gets the message file as $1 |
enforce message format | yes |
post-commit |
after the commit | notifications | no |
pre-rebase |
before a rebase | block rebasing published branches | yes |
post-merge / post-checkout |
after those operations | reinstall deps, regenerate files | no |
pre-push |
before git push; receives refs on stdin |
tests, version-bump checks, block pushes to main |
yes |
Bypass (use sparingly): git commit --no-verify, git push --no-verify.
Use one shared hooks directory for all repos¶
mkdir -p ~/.local/git_hooks
git config --global core.hooksPath ~/.local/git_hooks
chmod +x ~/.local/git_hooks/*
Note: when core.hooksPath is set, each repo's own .git/hooks/ is ignored. Symlink or copy the hooks
you need from one master directory so edits apply everywhere.
git-adm: git-adm hooks path (set the shared dir), git-adm hooks list (see what is installed and executable).
commit-msg: enforce PROJ-123456 - message¶
Message-format checks belong in commit-msg (it receives the message), not pre-commit.
#!/usr/bin/env bash
# ~/.local/git_hooks/commit-msg (chmod +x)
msg_file="$1"
subject="$(head -n1 "$msg_file")"
# Let git-generated messages through
case "$subject" in Merge\ *|Revert\ *|fixup!\ *|squash!\ *) exit 0 ;; esac
pattern='^[A-Z][A-Z0-9]*-[0-9]+ - .+'
if [[ ! "$subject" =~ $pattern ]]; then
cat >&2 <<'EOF2'
Commit message format invalid.
Expected: PROJ-123456 - descriptive message
Example: PROJ-123456 - Fix user login bug
EOF2
exit 1
fi
Test it: git commit -m "Fix bug" → rejected; git commit -m "PROJ-123456 - Fix bug" → accepted.
git-adm: git-adm hooks install-commit-msg [--force] writes this hook (pattern from key_pattern in ~/.git-adm.conf).
pre-commit: fail on formatting problems¶
#!/usr/bin/env bash
# Example for a Rust project
[ "${SKIP_FMT:-}" = "1" ] && exit 0
cargo fmt --check && cargo clippy -- -D warnings
pre-push: require a version file bump¶
Fails the push if a top-level directory changed but its version file did not.
#!/usr/bin/env bash
# ~/.local/git_hooks/pre-push (chmod +x)
[ "${GIT_NO_VERSION_CHECK:-}" = "true" ] && exit 0
git fetch origin main --quiet
base="$(git merge-base HEAD origin/main)"
changed_files="$(git diff --name-only "$base"...HEAD)"
changed_dirs="$(awk -F/ 'NF>1 {print $1}' <<<"$changed_files" | sort -u)"
rc=0
for dir in $changed_dirs; do
[ -f "$dir/version" ] || continue
if ! grep -qx "$dir/version" <<<"$changed_files"; then
echo "version not bumped in: $dir/version" >&2
rc=1
fi
done
exit "$rc"
git-adm push performs an equivalent check itself and sets GIT_NO_VERSION_CHECK=true so this hook is skipped.
Sharing hooks with a team¶
Because hooks aren't versioned, commit them to the repo (e.g. .githooks/) and point git at them:
git config core.hooksPath .githooks
Or use a framework such as pre-commit (https://pre-commit.com).
Reading: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks