Git hooks: what they are and which ones exist

A git hook is a program git runs at a fixed point of an operation: before a commit is created, after a push lands on a server, after a branch is checked out. It is not a plugin and not a git command. It is an executable file with an agreed name in a folder git looks at, and its exit status is the answer git waits for: zero means continue, anything else stops the operation at the points where git allows a hook to stop it.

Hooks are how a repository enforces what its authors agreed on. The pre-commit hook refuses a commit that does not parse, the post-commit hook regenerates a file after the commit exists, and a pre-receive hook on the server refuses a push that breaks the same rules for everyone. How the two local hooks work as one pair, and what went wrong when they did not, is in pre-commit and post-commit together.

What a git hook is

Git does not interpret hooks. It looks for a file with a known name in the hooks directory, and if the file exists and is executable, it runs it with the arguments, the standard input and the environment that belong to that point of the operation. Everything else is the hook's business: bash, Python, PHP or a compiled binary. The name of the file decides when it runs; the exit status decides what git does next.

Where git looks for hooks

The default directory is .git/hooks inside the repository. git init fills it with .sample files: every hook git ships an example for, none of them active. Renaming one and making it executable turns it on.

ls .git/hooks

applypatch-msg.sample commit-msg.sample fsmonitor-watchman.sample post-update.sample pre-applypatch.sample pre-commit.sample pre-merge-commit.sample prepare-commit-msg.sample pre-push.sample pre-rebase.sample pre-receive.sample push-to-checkout.sample sendemail-validate.sample update.sample

The directory can be moved with core.hooksPath, which is how a repository keeps its hooks in a tracked folder instead of inside .git:

git config core.hooksPath .githooks

The setting lives in the local configuration, so a fresh clone still has to set it. A tracked folder plus one setup command is the smallest way to share hooks. A framework is the larger way: the pre-commit framework installs the two hook files that call it and keeps the checks themselves in a YAML file next to the code.

Turn one on, skip one, watch one

Three commands cover most of the daily use. The first makes a hook active. The second skips the hooks that guard the commit — pre-commit, pre-merge-commit and commit-msg — but not prepare-commit-msg, and not the post-* hooks, which still run. The third prints every hook git executes, with its path and arguments, which answers «did it run at all?» before any debugging of the hook itself.

chmod +x .git/hooks/pre-commit

git commit --no-verify -m "WIP"

GIT_TRACE=1 git commit -m "Test"

The three hook lines of a commit, with the timestamps cut off:

run-command.c:659 trace: run_command: GIT_EDITOR=: GIT_INDEX_FILE=.git/index .git/hooks/pre-commit run-command.c:659 trace: run_command: GIT_EDITOR=: GIT_INDEX_FILE=.git/index .git/hooks/commit-msg .git/COMMIT_EDITMSG run-command.c:659 trace: run_command: GIT_EDITOR=: GIT_INDEX_FILE=.git/index .git/hooks/post-commit

The hooks of Git 2.43

Git 2.43 knows 28 hooks. They are easier to read in the order git runs them than alphabetically, so the tables below follow one commit, one push and one piece of housekeeping.

Around a commit.

Commit and merge hooks
HookWhen it runsCan it stop the commit
pre-commiton git commit, before the message is composedyes, --no-verify skips it
pre-merge-commitafter a merge succeeded, before the merge commityes, --no-verify skips it
prepare-commit-msgafter the default message exists, before the editor opensyes, and --no-verify does not skip it
commit-msgafter the message is written, gets its file as an argumentyes, --no-verify skips it
post-commitafter the commit object exists and HEAD movedno
pre-rebasebefore git rebase startsyes, refuses the rebase

Around the working tree and the refs.

Checkout, merge result and ref hooks
HookWhen it runsCan it stop the command
post-checkoutafter git checkout, git switch, git clone and git worktree addno, its status becomes the command's status
post-mergeafter a merge or a pull, not after a conflicted mergeno
post-rewriteafter git commit --amend and git rebase rewrite commitsno
reference-transactionaround every ref update: prepared, committed, abortedyes, in the prepared state
post-index-changeafter the index file is writtenno

Around patches and mail.

Patch and mail hooks
HookWhen it runsCan it stop the command
applypatch-msgon git am, on the proposed messageyes
pre-applypatchon git am, after the patch is applied, before the commityes
post-applypatchon git am, after the commitno
sendemail-validatebefore git send-email sends a patchyes
p4-changelist, p4-prepare-changelist, p4-post-changelist, p4-pre-submitthe Perforce bridge: edit, prepare, after submit, before submityes, except p4-post-changelist

Around a push. One hook on the client, six on the server.

Push hooks
HookWhere it runsWhat it can do
pre-pushclient, before the objects are sent; the refs arrive on stdinrefuse the whole push
pre-receiveserver, once per push, before any ref is updatedrefuse every ref in the push
updateserver, once per ref, before that ref is updatedrefuse that one ref
proc-receiveserver, when a ref is handled by a helper commandrefuse or rewrite that ref
post-receiveserver, after all refs are updatednotify; cannot refuse
post-updateserver, after post-receive, gets the updated refs as argumentsnotify; cannot refuse
push-to-checkoutserver, when a push targets the branch checked out in a non-bare repositoryrefuse, or update the tree itself

Housekeeping.

Background hooks
HookWhen it runsWhat it can do
pre-auto-gcbefore the automatic garbage collectionpostpone it
fsmonitor-watchmanthe integration between the fsmonitor daemon and watchmanreport changed paths
post-index-changeafter the index file is written, with two flags as argumentsnotify

What a hook cannot do

Client side and server side

The split that matters is not pre and post, it is local and remote. Everything up to pre-push runs on the machine that commits: fast, private, and optional. Everything from pre-receive runs on the server, after the objects have travelled, and cannot be skipped by the person pushing. A rule that must hold for every commit belongs on the server; a rule that saves the author a round trip belongs on the client. Most repositories run the same check in both places: the local hook for speed, the server hook for certainty.

The pre-commit framework

Writing the same shell checks in every repository gets old, so most projects use a framework that keeps the checks in a YAML file, installs the hook files and runs the tools in isolated environments. The pre-commit framework is the one this site uses. It installs both .git/hooks/pre-commit and .git/hooks/post-commit, and the checks are listed in .pre-commit-config.yaml at the repository root.

The framework adds one behaviour that plain hooks do not have, and it is the source of the incident described in the workflow article: before each stage it saves every unstaged change into ~/.cache/pre-commit/patch<timestamp>-<pid>, runs git checkout -- . so the checks see a clean worktree, and re-applies the patch afterwards. If that re-apply fails, the unstaged work is not lost — the patch file stays on disk — but it is no longer in the working tree.

Articles in this section

Git hooks
The pre-commit hook: what it checks, what it sees and how to skip it
The post-commit hook: reactions that cannot undo a commit
pre-commit and post-commit together: a gate and a reaction

Search this site

Channel @aofeed Chat @aofeedchat

Contacts and cooperation:
I recommend our hosting beget.com
Write to info@aredel.com if you:
1. Want to write an article for our site or translate an article into your native language.
2. Want to place thematically relevant ads on the site.
3. Ads on my site pass maximum censorship. If you see an ad block unsuitable for school-age children, shocking or misleading - please contact us by e-mail
4. Found a mistake, inaccuracy, bug, etc. on the site. ... .......
5. Articles can be shared on social media by clicking a network icon: