The Hook (The "Byte-Sized" Intro)
Client-side hooks are suggestions — developers can skip them with --no-verify. Server-side hooks are laws. They run on the Git server when someone pushes, and there's no way to bypass them from the client. Force-push to main? Blocked. Missing ticket number in commit message? Rejected. No signed commits? Denied.
📖 What are Server-Side Hooks?
Server-side hooks run on the Git server (or hosting platform like GitHub/GitLab) when a push is received. They can reject pushes that violate policies.
Conceptual Clarity
The three server-side hooks:
| Hook | When It Fires | Can Reject? | Use Case |
|---|---|---|---|
pre-receive | Before any refs are updated | ✅ Rejects entire push | Policy enforcement |
update | Before each individual ref is updated | ✅ Rejects per-ref | Per-branch rules |
post-receive | After all refs are updated | ❌ Informational | CI trigger, notifications |
pre-receive vs update:
| Feature | pre-receive | update |
|---|---|---|
| Runs once per push | ✅ | ❌ (once per ref) |
| Can reject everything | ✅ | 🟡 (only that ref) |
| Access to all refs | ✅ | ❌ (only current ref) |
| Use case | Global policies | Per-branch policies |
Real-Life Analogy
Server-side hooks are airport security. Client-side hooks are your own packing checklist — you can skip it. But airport security? Non-negotiable. Every bag gets scanned, every passenger goes through the gate.
Visual Architecture
Why It Matters
- Unskippable: Developers cannot bypass server-side hooks.
- Branch protection: Prevent force-pushes to main/release branches.
- Message enforcement: Require ticket numbers in commit messages.
- Automation:
post-receivetriggers CI/CD pipelines and notifications.
Code
# ─── pre-receive: block force-pushes to main ───
# (This runs on the SERVER, not client)
cat > hooks/pre-receive << 'EOF'
#!/bin/sh
while read oldrev newrev refname; do
if [ "$refname" = "refs/heads/main" ]; then
# Check if this is a force-push (non-fast-forward)
if ! git merge-base --is-ancestor "$oldrev" "$newrev" 2>/dev/null; then
echo "❌ Force-push to main is not allowed."
exit 1
fi
fi
done
EOF
# ─── update: require ticket in commit message ───
cat > hooks/update << 'EOF'
#!/bin/sh
refname="$1" oldrev="$2" newrev="$3"
for commit in $(git rev-list "$oldrev".."$newrev"); do
msg=$(git log -1 --format="%s" "$commit")
if ! echo "$msg" | grep -qE "^[A-Z]+-[0-9]+"; then
echo "❌ Commit $commit missing ticket: $msg"
exit 1
fi
done
EOF
# ─── GitHub/GitLab equivalent ───
# Modern platforms use branch protection rules instead:
# ✅ Require pull request reviews
# ✅ Require status checks
# ✅ Block force pushes
# ✅ Restrict who can pushKey Takeaways
- Server-side hooks cannot be bypassed from the client.
pre-receiveenforces global policies;updateenforces per-branch policies.post-receivetriggers automation (CI, notifications, deployments).- GitHub/GitLab branch protection rules are the modern equivalent.
Interview Prep
-
Q: What is the key difference between client-side and server-side hooks? A: Client-side hooks can be bypassed with
--no-verify. Server-side hooks run on the server and cannot be bypassed — they are enforced for every push regardless of the client's configuration. -
Q: What is the difference between
pre-receiveandupdate? A:pre-receiveruns once for the entire push and can reject everything.updateruns once per ref (branch/tag) and can selectively reject specific refs while allowing others. -
Q: How do modern platforms like GitHub implement server-side hook functionality? A: Through branch protection rules: requiring PR reviews, requiring CI status checks to pass, blocking force pushes, and restricting who can push to protected branches.