pre-push Hook
pre-push = local checkpoint. CI = server checkpoint. Dono lagao, protection strong rakho.
The pre-push hook runs when you execute git push, BEFORE any data is sent to the remote server.
It is your last quality gate before code leaves your machine and enters the shared repository. Once data reaches the remote, it affects your entire team — broken code, failing tests, direct pushes to main — all of it.
Unlike pre-commit (which must be FAST — under 5 seconds), pre-push can afford to be SLOWER. Why? Because push happens far less frequently than commit. A developer might commit 20 times but push only once. So 30-60 second checks are acceptable.
Common uses for pre-push:
- Run full test suite — unit tests + integration tests before code reaches remote
- Prevent direct push to main — block accidental pushes to protected branches
- Check for large files — prevent accidentally pushing binaries or data files
- Verify branch is up-to-date — warn if main has new commits you have not rebased on
If the hook exits with a non-zero code, the push is CANCELLED — no data is sent to the remote at all.
git push --no-verify bypasses this hook — the same escape hatch as other hooks. Use sparingly.
The hook receives information about what is being pushed on stdin: local ref, local SHA, remote ref, remote SHA. You can read this to make conditional checks based on the remote.
# Create a pre-push hook
cat > .git/hooks/pre-push << 'EOF'
#!/bin/bash
echo "🚀 Pre-push checks running..."
# Run tests before push
echo "🧪 Running tests..."
npm test
if [ $? -ne 0 ]; then
echo "❌ Tests failed! Push blocked."
exit 1
fi
echo "✅ All checks passed! Pushing..."
exit 0
EOF
chmod +x .git/hooks/pre-push
# Now when you push:
git push origin feature
# 🚀 Pre-push checks running...
# 🧪 Running tests...
# ... tests run ...
# ✅ All checks passed! Pushing...
# Data is sent to remote
# If tests fail:
git push origin feature
# ❌ Tests failed! Push blocked.
# No data sent to remote — your team is safe!
One of the most valuable pre-push use cases: BLOCK direct pushes to main/master.
All changes to main should come through pull requests — direct pushes bypass code review. One accidental git push origin main can break production for everyone.
The hook checks which branch you are pushing to. If it is main, master, or develop, the push is blocked with a helpful message telling you to use a PR instead.
This is CLIENT-SIDE enforcement — developers can bypass with --no-verify. Server-side enforcement (GitHub branch protection) is the real guarantee. But the hook catches ACCIDENTAL pushes to main, which happen more often than you would think.
Pattern: git rev-parse --abbrev-ref HEAD gets the current branch name. Compare it against your list of protected branches.
# Prevent direct push to main
cat > .git/hooks/pre-push << 'EOF'
#!/bin/bash
BRANCH=$(git rev-parse --abbrev-ref HEAD)
PROTECTED_BRANCHES="main master develop"
for protected in $PROTECTED_BRANCHES; do
if [ "$BRANCH" = "$protected" ]; then
echo "❌ Direct push to $BRANCH is not allowed!"
echo " Create a feature branch and use a pull request."
echo ""
echo " git checkout -b feature/your-feature"
echo " git push origin feature/your-feature"
exit 1
fi
done
exit 0
EOF
chmod +x .git/hooks/pre-push
# Try to push directly to main
git checkout main
git push origin main
# ❌ Direct push to main is not allowed!
# Push CANCELLED
# Correct way: use a feature branch
git checkout -b feature/new-api
git push origin feature/new-api
# ✅ Push proceeds (not a protected branch)
pre-push is the right place to run your full test suite — it runs less often than pre-commit, so you can afford more thorough checks.
Unit tests + integration tests are appropriate for pre-push. Full E2E tests might be too slow — save those for CI.
Target time: under 60 seconds. If your test suite takes longer, developers will use --no-verify to skip it. A hook that everyone bypasses is worse than no hook at all.
If your test suite is slow, run only the RELEVANT tests based on what changed. Tools like Jest can run only tests related to changed files.
Always run tests on the CURRENT code, not just what is committed. Use git stash --keep-index if you need to test the staged version separately.
# Run test suite in pre-push
cat > .git/hooks/pre-push << 'EOF'
#!/bin/bash
set -e
echo "🧪 Running test suite..."
# Run unit tests (fast, ~30 seconds)
npm run test:unit
if [ $? -ne 0 ]; then
echo "❌ Unit tests failed! Push blocked."
exit 1
fi
# Run integration tests if available
if npm run test:integration 2>/dev/null; then
echo "✅ Integration tests passed"
else
echo "⚠️ No integration tests found, skipping"
fi
echo "✅ All tests passed! Safe to push."
exit 0
EOF
chmod +x .git/hooks/pre-push
# When you push:
git push origin feature
# 🧪 Running test suite...
# Unit tests: 45 passed, 0 failed
# Integration tests: 12 passed, 0 failed
# ✅ All tests passed! Safe to push.
# For slow test suites — run only relevant tests:
# npm run test:unit -- --changedSince=origin/mainA common problem: you are pushing a feature branch that is behind main. This causes merge conflicts on the PR and messy merge commits.
pre-push can check if your branch is behind the remote and warn you before you push.
It can also remind you to rebase before pushing to keep history clean and linear.
git fetch origin main && git log HEAD..origin/main --oneline — check if main has new commits that your branch does not have.
If main has new commits, suggest rebasing before push. This prevents the "merge main into feature" anti-pattern that pollutes history.
# Check if branch is behind main before push
cat > .git/hooks/pre-push << 'EOF'
#!/bin/bash
set -e
BRANCH=$(git rev-parse --abbrev-ref HEAD)
# Only check feature branches
if [ "$BRANCH" != "main" ] && [ "$BRANCH" != "master" ]; then
# Fetch latest from remote
git fetch origin main 2>/dev/null || true
# Check if main has commits we lack
NEW_COMMITS=$(git log HEAD..origin/main --oneline 2>/dev/null | wc -l)
if [ "$NEW_COMMITS" -gt 0 ]; then
echo "⚠️ Warning: origin/main has $NEW_COMMITS new commits"
echo " Consider rebasing before push:"
echo " git rebase origin/main"
echo ""
echo " Continuing push anyway... (rebase recommended)"
fi
fi
# Run tests
echo "🧪 Running tests..."
npm test --silent 2>/dev/null
exit 0
EOF
chmod +x .git/hooks/pre-push
read remote url. You can use this to apply different rules for different remotes (e.g., strict checks for production remote, relaxed for personal fork).pre-push: fast feedback on YOUR machine. Catches problems before they reach the remote.
CI (GitHub Actions): comprehensive checks on the SERVER. Catches problems that bypassed local hooks.
What belongs in pre-push: unit tests, linting (if not in pre-commit), branch protection checks, large file detection.
What belongs in CI only: E2E tests, security scans, performance benchmarks, cross-platform tests, deployment validation.
What belongs in BOTH: unit tests. pre-push catches failures fast locally, CI verifies nobody bypassed hooks.
Do not duplicate slow checks in pre-push that already run in CI — it wastes developer time. The goal is complementary coverage, not redundancy.
The goal: pre-push catches 80% of problems instantly. CI catches the remaining 20% and verifies the 80%.
# What goes where — decision guide
# PRE-COMMIT (< 5 seconds)
# - Lint staged files only
# - Format staged files only
# - Quick syntax checks
# PRE-PUSH (30-60 seconds)
# - Full unit test suite
# - Branch protection checks
# - Large file detection
# - Integration tests (if fast)
# CI / GITHUB ACTIONS (5-30 minutes)
# - Full test suite (unit + integration + E2E)
# - Security scans (SAST, dependency audit)
# - Performance benchmarks
# - Cross-platform tests
# - Deployment preview / staging
# Example: pre-push runs unit tests (30s)
# CI runs full suite + E2E + security (15min)
# Developer gets instant feedback locally
# PR still cannot merge until CI passes
Lo kar liya — Key Points:
- ✅ pre-push hook runs BEFORE git push sends data to the remote — your final local quality gate
- ✅ It can be slower than pre-commit — push happens less frequently, so 30-60 second checks are acceptable
- ✅ Most valuable uses: prevent direct push to main, run test suite, verify branch is up-to-date
- ✅ Exit code 0 = allow push, non-zero = cancel push (no data sent to remote)
- ✅ git push --no-verify bypasses pre-push — always pair with server-side branch protection
- ✅ Keep test runs under 60 seconds — longer pushes frustrate developers into using --no-verify
- ✅ pre-push catches problems fast locally; CI catches what bypasses hooks — use both for defense in depth
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