By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.
(Pre-commit, Post-receive, and Beyond)
Git hooks are scripts that run automatically before or after specific Git events (e.g., commit, push, merge). They live in your repo’s .git/hooks directory and let you enforce policies, run tests, or trigger deployments without relying on external CI/CD tools.
commit
push
merge
.git/hooks
main
You’re a DevOps engineer at a fintech startup. Your team pushes code to main 50+ times a day. Requirements:1. Every commit must pass eslint and prettier checks.2. When code lands in main, it must auto-deploy to staging.3. If a commit message doesn’t follow Conventional Commits, the push should be rejected.
eslint
prettier
Hooks solve this in seconds. No CI/CD pipeline needed for local checks, and deployments trigger instantly.
pre-commit
pre-push
post-receive
pre-receive
bandit
pytest
hooks/pre-commit
Husky
black
flake8
mypy
cd
chmod
git
Goal: Block commits if code isn’t formatted with prettier.
Navigate to your repo: bash cd ~/projects/my-repo
bash cd ~/projects/my-repo
Create a pre-commit hook: bash touch .git/hooks/pre-commit chmod +x .git/hooks/pre-commit
bash touch .git/hooks/pre-commit chmod +x .git/hooks/pre-commit
Edit the hook (Bash example): ```bash #!/bin/sh
# Run prettier on staged JS/TS files STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '.(js|ts)$')
if [ -n "$STAGED_FILES" ]; then echo "Running prettier..." npx prettier --write $STAGED_FILES git add $STAGED_FILES fi
exit 0 ```
bash echo "const x=1" > test.js # Bad formatting git add test.js git commit -m "Test pre-commit"
Goal: When code is pushed to main, auto-deploy to a staging server.
SSH into your Git server (e.g., a VPS): bash ssh user@your-git-server
bash ssh user@your-git-server
Create a bare repo (if it doesn’t exist): bash mkdir -p ~/repos/my-app.git cd ~/repos/my-app.git git init --bare
bash mkdir -p ~/repos/my-app.git cd ~/repos/my-app.git git init --bare
Create a post-receive hook: bash touch hooks/post-receive chmod +x hooks/post-receive
bash touch hooks/post-receive chmod +x hooks/post-receive
TARGET_DIR="/var/www/my-app-staging" GIT_DIR="/home/user/repos/my-app.git" BRANCH="main"
while read oldrev newrev refname; do if [ "$refname" = "refs/heads/$BRANCH" ]; then echo "Deploying $BRANCH to staging..." git --work-tree=$TARGET_DIR --git-dir=$GIT_DIR checkout -f $BRANCH cd $TARGET_DIR npm install && npm run build # Example for Node.js systemctl restart my-app # Restart service fi done ```
bash git remote add staging user@your-git-server:repos/my-app.git git push staging main
/var/www/my-app-staging
Goal: Enforce black, flake8, and mypy checks before commits.
Install pre-commit: bash pip install pre-commit
bash pip install pre-commit
Create .pre-commit-config.yaml: ```yaml repos:
.pre-commit-config.yaml
Install the hooks: bash pre-commit install
bash pre-commit install
Test it: bash echo "x=1 # Bad formatting" > test.py git add test.py git commit -m "Test pre-commit" Expected: black reformats the file, and flake8 blocks the commit if there are errors.
bash echo "x=1 # Bad formatting" > test.py git add test.py git commit -m "Test pre-commit"
git-secrets
chmod 750 hooks/post-receive
echo "Deploy failed" >> /var/log/git-hooks.log
hooks/
README.md
git commit
chmod +x .git/hooks/pre-commit
ln -s ../../hooks/pre-commit .git/hooks/
echo "Hook ran at $(date)" >> /var/log/git-hooks.log
exit 0
--no-verify
git diff --cached
post-commit
❌ pre-push (runs before push, not commit)
"How do you enforce a policy that blocks direct pushes to main?"
❌ Use a pre-commit hook (client-side only).
"What’s the difference between pre-receive and post-receive?"
Create a pre-commit hook that: 1. Blocks commits if the message doesn’t follow Conventional Commits (e.g., feat: add login).2. Only checks commits to the main branch.
feat: add login
Solution:
#!/bin/sh BRANCH=$(git rev-parse --abbrev-ref HEAD) if [ "$BRANCH" = "main" ]; then COMMIT_MSG=$(cat "$1") if ! echo "$COMMIT_MSG" | grep -Eq '^(feat|fix|docs|style|refactor|test|chore)\(?.*\)?: .+'; then echo "❌ Commit message must follow Conventional Commits (e.g., 'feat: add login')." exit 1 fi fi exit 0
Why it works:- git rev-parse --abbrev-ref HEAD gets the current branch.- grep -Eq checks if the commit message matches the pattern.- $1 is the path to the commit message file (passed by Git).
git rev-parse --abbrev-ref HEAD
grep -Eq
$1
git commit --no-verify
pre-commit install
npx husky add .husky/pre-commit "npm test"
exit 1
.git/hooks/
Join 4M+ learners. Unlock unlimited quizzes, wrong-answer tracking, flashcards + reminders, study guides, and 1-on-1 challenges.