What are Git Hooks
Hooks = tumhare repo mein tripwires. Step karo, script trigger ho. Quality automatic, manual nahi.
Git hooks are scripts that run automatically when certain Git events occur.
They let you enforce rules, automate checks, and trigger actions without manual intervention.
Think of them as tripwires: when a developer runs git commit, the pre-commit hook fires BEFORE the commit is created.
If the hook script exits with code 0, the Git operation proceeds. If exit code is non-zero, the operation is BLOCKED.
Hooks live in .git/hooks/ directory. Git creates sample hooks there when you run git init.
Sample hooks end with .sample extension — they are disabled by default. Remove .sample to activate.
Hooks must be executable: chmod +x .git/hooks/pre-commit. Without execute permission, Git ignores them.
Hooks can be written in ANY language: bash, Python, Node.js, Ruby — as long as the file is executable.
# See sample hooks in any Git repository
ls .git/hooks/
# applypatch-msg.sample pre-commit.sample pre-push.sample ...
# These are INACTIVE (end with .sample)
# Activate a hook by removing .sample extension
mv .git/hooks/pre-commit.sample .git/hooks/pre-commit
# Make it executable (required!)
chmod +x .git/hooks/pre-commit
# Now every `git commit` will run this script first
# If script exits 0 → commit proceeds
# If script exits 1 → commit is BLOCKED
# Test it — create a hook that blocks all commits
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
echo "🚫 Commits are blocked on this machine!"
exit 1
EOF
chmod +x .git/hooks/pre-commit
# Try to commit
git commit -m "test"
# 🚫 Commits are blocked on this machine!
# Commit FAILED — hook blocked itClient-side hooks: run on the DEVELOPER'S machine. Developer controls them (can bypass with --no-verify).
Server-side hooks: run on the GIT SERVER (GitHub, GitLab, self-hosted). CANNOT be bypassed by developers.
Client-side hooks are for PERSONAL automation and team conventions (linting, formatting).
Server-side hooks are for ENFORCED rules (no force push to main, commit message format, access control).
Client-side hooks: pre-commit, prepare-commit-msg, commit-msg, post-commit, pre-push, post-merge, pre-rebase.
Server-side hooks: pre-receive, update, post-receive.
GitHub.com doesn't allow custom server hooks — use GitHub Actions instead. Self-hosted Git servers do.
The key insight: client hooks are GUIDELINES, server hooks are LAWS.
# Client-side hooks (run on your machine)
# .git/hooks/pre-commit → runs before git commit
# .git/hooks/pre-push → runs before git push
# .git/hooks/commit-msg → runs after message is written
# Developer can bypass ALL client hooks:
git commit --no-verify # skips pre-commit, commit-msg hooks
git push --no-verify # skips pre-push hook
# Server-side hooks (run on GitHub/GitLab server)
# custom_hooks/pre-receive → runs when server receives push
# custom_hooks/update → runs once per branch being updated
# custom_hooks/post-receive → runs after push is accepted
# Developer CANNOT bypass server hooks
# Even with --no-verify, server hooks still run
git push --no-verify origin main
# Server hook still checks and can REJECT the push
# That's why server hooks are for ENFORCEMENT
# Client hooks are for CONVENIENCE and QUALITYGit operations have a specific sequence, and hooks fire at exact points in that sequence.
Commit sequence: pre-commit → prepare-commit-msg → commit-msg → post-commit
Push sequence: pre-push → (server: pre-receive → update → post-receive)
Merge sequence: pre-merge-commit → post-merge
Rebase sequence: pre-rebase → post-rewrite
Checkout sequence: post-checkout
Each hook has a specific purpose and receives different arguments.
- pre-commit: no arguments. Runs BEFORE the commit editor opens. Use for linting, validation.
- prepare-commit-msg: receives message file path, message source, and commit SHA. Use for auto-populating messages.
- commit-msg: receives message file path. Use for validating the message format.
- pre-push: receives remote name and URL. Use for running tests before push.
.git/ directory is NOT tracked by Git. It's local to each clone.
This means hooks in .git/hooks/ are NOT shared with your team. Every developer must set them up manually.
This is a MAJOR problem: if you rely on pre-commit for code quality, but your teammate doesn't have it, bad code gets committed.
Solutions:
- Husky: stores hooks in your project directory (tracked by Git), sets up
.git/hooks/automatically onnpm install. - Put hook scripts in a
scripts/directory and tell developers to copy them manually (error-prone). - Use server-side hooks or CI for ENFORCEMENT — don't rely solely on client hooks.
Husky is the industry standard for JavaScript/TypeScript projects. We'll cover it in Chapter 8.5.
# The problem: .git/ is not tracked
git status .git/hooks/
# fatal: not a git repository: .git/
# You can't commit hooks to .git/hooks/
cd .git/hooks/
git add pre-commit
# fatal: not a git repository: .git/
# Your carefully crafted pre-commit hook is LOCAL ONLY
# When teammate clones the repo:
git clone https://github.com/team/project.git
ls project/.git/hooks/
# Only .sample files exist — NO custom hooks!
# Manual sharing (bad — nobody does this consistently):
# 1. Store hook in repo
mkdir -p scripts/git-hooks
cp .git/hooks/pre-commit scripts/git-hooks/pre-commit
git add scripts/git-hooks/
git commit -m "add pre-commit hook"
# 2. Teammate must manually copy:
cp scripts/git-hooks/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
# They WILL forget to do this. Every time.
# 3. Better: Husky (Chapter 8.5)
# Hooks stored in repo, installed automatically on npm install
# Everyone gets hooks, no manual stepsHooks: run LOCALLY, instant feedback, can block the operation. Best for: fast checks (lint, format, message format).
CI/CD (GitHub Actions): run on SERVER after push. Can't block the push, but can block merge. Best for: full test suite, build verification, security scans.
The strategy: hooks catch problems FAST before they're committed. CI catches problems that hooks missed.
Don't put slow operations in hooks. pre-commit that takes 30 seconds makes developers hate commits and use --no-verify.
Keep pre-commit under 5 seconds. Use pre-push for slower checks (30-60 seconds). Use CI for everything else.
Hook + CI layered defense: hook catches it instantly on your machine. If you bypass with --no-verify, CI catches it before merge.
Lo kar liya — Key Points:
- ✅ Git hooks are scripts that run automatically at specific Git events — commit, push, merge, etc.
- ✅ Client-side hooks run locally and can be bypassed with --no-verify; server-side hooks cannot be bypassed
- ✅ Hooks live in .git/hooks/ — activate samples by removing the .sample extension and making them executable
- ✅ Exit code 0 allows the Git operation to proceed; non-zero exit code BLOCKS the operation
- ✅ The 4 most important hooks: pre-commit (validate code), commit-msg (validate message), pre-push (run tests), post-merge (auto-setup)
- ✅ .git/hooks/ is NOT tracked by Git — custom hooks do not travel with clones. Use Husky to share hooks with your team
- ✅ Keep pre-commit hooks under 5 seconds — slow hooks make developers use --no-verify, defeating the purpose
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login