After the fact
Where commit-time hooks shape commits, the hooks on this page react to them. post-commit runs locally after each commit, pre-push runs before transferring objects to a remote, and post-receive runs on the server after a push lands.
post-commit
Cannot block — runs after the commit is recorded. Useful for notifications, ctags regeneration, or kicking off background tasks.
#!/usr/bin/env bash
# .git/hooks/post-commit
ctags -R --languages=python -f .tags & disown
notify-send "Committed: $(git log -1 --format=%s)"
pre-push
Runs locally before git push sends data. Receives stdin describing every ref being pushed, allowing fine-grained checks. Exit non-zero to abort.
#!/usr/bin/env bash
# .git/hooks/pre-push
remote="$1"
url="$2"
while read local_ref local_sha remote_ref remote_sha; do
if [ "$remote_ref" = "refs/heads/main" ]; then
if ! cargo test --quiet; then
echo "Tests fail; refusing to push to main."
exit 1
fi
fi
done
post-receive (server-side)
Runs on the remote after a push completes. Common uses: deploy a static site, trigger CI, send chat notifications, mirror to another remote.
#!/usr/bin/env bash
# hooks/post-receive on the bare repo
while read oldrev newrev refname; do
branch=${refname#refs/heads/}
if [ "$branch" = "main" ]; then
GIT_WORK_TREE=/var/www/site git checkout -f main
curl -X POST https://chat.example/notify \
-d "Deployed $newrev to production"
fi
done
Other server hooks
pre-receive runs once per push and can reject the entire push. update runs once per ref and can reject individual refs. Use these for policy enforcement: protected branches, signed-commit requirements, message format checks.
Hook chaining
Git only runs one script per hook name. To chain, write a dispatcher that runs everything in hooks.d/:
#!/usr/bin/env bash
for hook in .githooks/post-commit.d/*; do
[ -x "$hook" ] && "$hook" "$@" || exit $?
done
Common mistakes
post-commit cannot prevent a commit; do not put validation there. pre-push runs only when push is invoked manually — automated systems pushing via libgit2 may skip it. Server-side hooks must be installed on the remote, which means access to that machine; hosting providers like GitHub or Gitea expose equivalents through their UI rather than raw hooks.
Debugging
Hooks run with stdin/stdout connected to Git. Use set -x at the top while debugging, and log to a file:
exec >> /tmp/git-hook.log 2>&1
set -x
Related
See "Git hooks: pre-commit, prepare-commit-msg, commit-msg" for commit-time hooks.