Git Hooks: Your Code’s Security Guards 🛡️
Imagine your code repository is a fancy building. Every time someone tries to enter (commit code), exit (push code), or receive deliveries (receive code from others), there are security guards at the doors who can check, approve, or reject what’s happening.
These guards are Git Hooks!
What Are Git Hooks?
Git hooks are small programs that run automatically when certain events happen in your Git repository. They’re like automatic helpers who spring into action at just the right moment.
Think of it like this:
- You have a robot at your front door
- Every time someone rings the bell, the robot checks if they’re allowed in
- If yes, the door opens. If no, the door stays locked!
graph TD A["You do something in Git"] --> B{Hook exists?} B -->|Yes| C["Hook runs automatically"] B -->|No| D["Git continues normally"] C --> E{Hook says OK?} E -->|Yes| D E -->|No| F["Action blocked!"]
Where Do Hooks Live?
All hooks live in a special folder:
your-project/
└── .git/
└── hooks/
├── pre-commit
├── pre-push
└── ...more hooks
Fun fact: Git gives you sample hooks when you create a repository! They end with .sample so they don’t run. Remove .sample to activate them!
Client-Side Hooks
Client-side hooks run on YOUR computer. They’re like your personal helpers that check your work before you share it with others.
Pre-Commit Hook
The pre-commit hook runs right before you make a commit. It’s like a spell-checker that runs before you send an email!
What can it do?
- Check for spelling mistakes
- Run code linters
- Look for debug statements you forgot to remove
Example: Block commits with “TODO” comments
#!/bin/bash
# .git/hooks/pre-commit
# Search for TODO in staged files
if git diff --cached | grep -i "TODO"; then
echo "Oops! Remove TODOs first!"
exit 1 # Block the commit
fi
exit 0 # Allow the commit
How it works:
- You type
git commit - Hook checks your changes
- Found “TODO”? Commit blocked!
- No “TODO”? Commit goes through!
Commit-Msg Hook
This hook checks your commit message. It’s like a teacher checking if your essay title makes sense!
Example: Require ticket number
#!/bin/bash
# .git/hooks/commit-msg
# $1 is the file with commit message
if ! grep -qE "^TICKET-[0-9]+" "$1"; then
echo "Add ticket number!"
echo "Example: TICKET-123 Fix bug"
exit 1
fi
exit 0
Pre-Push Hook
Runs before your code goes to the shared repository. It’s the last checkpoint!
Example: Run tests before pushing
#!/bin/bash
# .git/hooks/pre-push
echo "Running tests..."
npm test
if [ $? -ne 0 ]; then
echo "Tests failed! Fix first!"
exit 1
fi
exit 0
Other Client-Side Hooks
| Hook | When It Runs |
|---|---|
prepare-commit-msg |
Before commit editor opens |
post-commit |
After commit completes |
pre-rebase |
Before rebase starts |
post-checkout |
After checkout completes |
post-merge |
After merge completes |
Server-Side Hooks
Server-side hooks run on the Git server (like GitHub or your company’s server). They protect the whole team’s code!
graph TD A["Developer pushes code"] --> B["Server receives code"] B --> C{pre-receive hook} C -->|Approved| D{update hook} D -->|Approved| E["Code accepted!"] C -->|Rejected| F["Push blocked!"] D -->|Rejected| F E --> G["post-receive hook"] G --> H["Notifications sent!"]
Pre-Receive Hook
This is the main gatekeeper. It can reject ALL changes in a push!
Example: Block force pushes to main
#!/bin/bash
# hooks/pre-receive (on server)
while read old new ref; do
if [[ "$ref" == "refs/heads/main" ]]; then
# Check if force push
if [[ "$old" != "0000..." ]]; then
echo "No force push to main!"
exit 1
fi
fi
done
exit 0
Update Hook
Runs once for EACH branch being updated. More precise control!
Example: Only admins can push to release
#!/bin/bash
# hooks/update
ref=$1
ADMINS="alice bob charlie"
if [[ "$ref" == "refs/heads/release" ]]; then
if [[ ! " $ADMINS " =~ " $USER " ]]; then
echo "Only admins push to release!"
exit 1
fi
fi
exit 0
Post-Receive Hook
Runs AFTER changes are accepted. Perfect for:
- Sending notifications
- Triggering deployments
- Updating dashboards
Example: Notify team on Slack
#!/bin/bash
# hooks/post-receive
while read old new ref; do
branch=$(basename $ref)
curl -X POST \
-d "New push to $branch!" \
https://slack-webhook-url
done
Bypassing Hooks
Sometimes you NEED to skip a hook. Maybe it’s an emergency fix, or the hook has a bug!
Skip Client-Side Hooks
Use the --no-verify flag:
# Skip pre-commit and commit-msg
git commit --no-verify -m "Emergency fix"
# Skip pre-push
git push --no-verify
Short version:
git commit -n -m "Quick fix"
When to Bypass?
graph TD A["Need to bypass?"] --> B{Emergency?} B -->|Yes| C["Document why!"] B -->|No| D{Hook broken?} D -->|Yes| E["Fix hook first!"] D -->|No| F[Don't bypass!] C --> G["Use --no-verify"]
Good reasons:
- Emergency production fix
- Hook script has a bug
- One-time special commit
Bad reasons:
- Too lazy to fix code
- Want to skip tests
- Commit message doesn’t match rules
Server Hooks Can’t Be Bypassed!
Important: The --no-verify flag only skips LOCAL hooks. Server-side hooks ALWAYS run!
This is a safety feature:
- Developers can’t bypass team rules
- Protected branches stay protected
- Code quality is enforced
Server admin options:
- Temporarily disable the hook
- Add exceptions to hook logic
- Grant special permissions
Summary: Your Hook Toolkit
| Need | Hook Type | Location |
|---|---|---|
| Check my own code | Client-side | .git/hooks/ |
| Protect team’s code | Server-side | Server’s hooks folder |
| Skip local checks | Bypass | --no-verify |
Remember:
- Client hooks = Personal helpers
- Server hooks = Team guardians
- Bypass with care!
Quick Setup Guide
-
Find hooks folder:
ls .git/hooks/ -
Create a hook:
touch .git/hooks/pre-commit chmod +x .git/hooks/pre-commit -
Edit with your rules:
nano .git/hooks/pre-commit -
Test it:
git commit -m "Test hook"
You’re now ready to be a Git Hooks master! 🎉
