Git Hooks
Git hooks are scripts that Git automatically runs when specific events occur — such as before a commit is made, after a push, before a merge, and so on. They allow teams to automate tasks, enforce rules, and maintain code quality without requiring manual effort.
Real-life analogy: Think of a Git hook like a security checkpoint at an office building. Every time someone enters (commits code), they must pass through the checkpoint automatically. The checkpoint can run tests, check formatting, validate badges, and so on. If they do not pass the check, they are not allowed in.
Where are Hooks Stored?
Git hooks are stored as executable scripts inside the .git/hooks/ directory of a repository:
.git/
└── hooks/
├── pre-commit.sample
├── commit-msg.sample
├── pre-push.sample
├── post-commit.sample
├── pre-merge-commit.sample
└── ... (many more)
The .sample extension is added by default, which means they are disabled. To enable a hook, the .sample extension is removed and the script is made executable.
Types of Git Hooks
Client-Side Hooks
These run on the developer's local machine during common operations:
| Hook | When It Runs | Common Use |
|---|---|---|
pre-commit | Before the commit is created | Run linters, check formatting, run tests |
commit-msg | After commit message is entered | Enforce commit message format |
post-commit | After commit is created | Notifications, logging |
pre-push | Before pushing to remote | Run full test suite before sending code |
pre-rebase | Before a rebase starts | Validation checks before rewriting history |
post-checkout | After switching branches | Update dependencies, rebuild environment |
post-merge | After a merge completes | Install new packages if package.json changed |
Server-Side Hooks
These run on the remote server (GitHub/GitLab self-hosted) and cannot be set up on GitHub.com directly:
| Hook | When It Runs |
|---|---|
pre-receive | Before accepting a push — can reject it |
update | Called once per branch being pushed |
post-receive | After the push is accepted — trigger deployments |
Creating a pre-commit Hook
This example creates a pre-commit hook that prevents commits if there are any TODO comments left in the code:
# Step 1: Navigate to the hooks directory
cd .git/hooks
# Step 2: Create the hook script
nano pre-commit
Add the following script:
#!/bin/sh
# Check for TODO comments in staged files
if git diff --cached | grep -q "TODO"; then
echo "ERROR: Staged changes contain TODO comments."
echo "Please resolve them before committing."
exit 1
fi
exit 0
# Step 3: Make the hook executable
chmod +x .git/hooks/pre-commit
Now, every time git commit is run, Git will check for TODO comments. If found, the commit will be rejected with the error message.
Creating a commit-msg Hook
This hook enforces that every commit message must start with a capital letter:
#!/bin/sh
# Read the commit message
commit_msg=$(cat "$1")
first_char=$(echo "$commit_msg" | head -c 1)
if [ "$first_char" != "$(echo "$first_char" | tr '[:lower:]' '[:upper:]')" ]; then
echo "ERROR: Commit message must start with a capital letter."
exit 1
fi
exit 0
Creating a pre-push Hook
This hook runs the test suite before every push:
#!/bin/sh
echo "Running tests before push..."
npm test
if [ $? -ne 0 ]; then
echo "Tests failed! Push rejected."
exit 1
fi
echo "All tests passed. Proceeding with push."
exit 0
Bypassing Hooks (Emergency Use Only)
Hooks can be skipped with the --no-verify flag. This should only be used in genuine emergencies:
git commit --no-verify -m "Emergency hotfix"
git push --no-verify
Sharing Hooks with the Team
A critical limitation of Git hooks is that the .git/hooks/ directory is not committed to the repository — each developer's hooks are local only. To share hooks with a team:
Option 1 — Store hooks in the repo and configure Git
# Create a hooks folder in the project (not .git/hooks)
mkdir -p .githooks
# Add hooks there
cp pre-commit .githooks/
# Tell Git to use this directory for hooks
git config core.hooksPath .githooks
Commit the .githooks folder to the repository so the entire team gets the same hooks.
Option 2 — Use Husky (for JavaScript/Node.js projects)
Husky is a popular npm package that manages Git hooks for the entire team automatically:
npm install husky --save-dev
npx husky init
Husky hooks are stored in a .husky/ folder that is committed to the repository.
Summary
Git hooks are executable scripts that run automatically at specific Git events. The most useful ones are pre-commit (enforce code quality), commit-msg (enforce message format), and pre-push (run tests). Hooks are stored in .git/hooks/ and are not shared by default — use a .githooks/ folder or a tool like Husky to share hooks with the whole team. Hooks help enforce consistency and catch problems before they reach the repository.
