Fatskills
Practice. Master. Repeat.
Study Guide: TECH **Git & GitHub Hooks for Automation: Zero-Fluff, Hands-On Guide**
Source: https://www.fatskills.com/web-development/chapter/tech-git-github-hooks-for-automation-zero-fluff-hands-on-guide

TECH **Git & GitHub Hooks for Automation: Zero-Fluff, Hands-On Guide**

By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.

⏱️ ~7 min read

Git & GitHub Hooks for Automation: Zero-Fluff, Hands-On Guide

(Pre-commit, Post-receive, and Beyond)


1. What This Is & Why It Matters

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.

Why This Matters in Production

  • Pre-commit hooks catch bugs before they hit the repo (e.g., linting, security scans, formatting).
  • Post-receive hooks automate deployments after code lands in a remote (e.g., auto-deploy to staging when main is updated).
  • Without hooks, you’re stuck with:
  • Broken builds in CI (wasting time).
  • Manual deployments (human error).
  • Security vulnerabilities slipping into main.

Real-World Scenario

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.

Hooks solve this in seconds. No CI/CD pipeline needed for local checks, and deployments trigger instantly.


2. Core Concepts & Components

Term Definition Production Insight
Client-Side Hooks Run on your local machine (e.g., pre-commit, pre-push). If a hook fails, the Git action (commit/push) is blocked. Use for local validation.
Server-Side Hooks Run on the Git server (e.g., post-receive, pre-receive). Critical for enforcing policies (e.g., "no direct pushes to main").
pre-commit Runs before a commit is finalized. Use for linting, formatting, or security scans (e.g., eslint, bandit).
pre-push Runs before a push to a remote. Use for integration tests (e.g., "run pytest before pushing").
post-receive Runs after code is pushed to a remote (e.g., GitHub, GitLab, self-hosted). Use for auto-deployments (e.g., "trigger a Docker build on main push").
pre-receive Runs before code is accepted by the remote. Use for branch protection (e.g., "reject pushes to main without a PR").
.git/hooks Directory where hooks live (not version-controlled by default). Copy hooks to your repo (e.g., hooks/pre-commit) to share them with the team.
Husky A Node.js tool to manage Git hooks easily. Best for JS/TS projects (avoids manual hook setup).
pre-commit (Python) A framework for managing multi-language hooks. Best for Python projects (supports black, flake8, mypy).


3. Step-by-Step Hands-On: Setting Up Hooks


Prerequisites

  • A Git repo (local or remote).
  • Basic terminal knowledge (cd, chmod, git).
  • (Optional) Node.js or Python for tooling.


Task 1: Enforce Code Formatting with pre-commit (Local)

Goal: Block commits if code isn’t formatted with prettier.


Steps

  1. Navigate to your repo:
    bash
    cd ~/projects/my-repo

  2. Create a pre-commit hook:
    bash
    touch .git/hooks/pre-commit
    chmod +x .git/hooks/pre-commit

  3. 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
```


  1. Test it:
    bash
    echo "const x=1" > test.js # Bad formatting
    git add test.js
    git commit -m "Test pre-commit"

    Expected: Prettier auto-formats the file, and the commit succeeds.

Task 2: Auto-Deploy to Staging with post-receive (Server-Side)

Goal: When code is pushed to main, auto-deploy to a staging server.


Steps

  1. SSH into your Git server (e.g., a VPS):
    bash
    ssh user@your-git-server

  2. Create a bare repo (if it doesn’t exist):
    bash
    mkdir -p ~/repos/my-app.git
    cd ~/repos/my-app.git
    git init --bare

  3. Create a post-receive hook:
    bash
    touch hooks/post-receive
    chmod +x hooks/post-receive

  4. Edit the hook (Bash example):
    ```bash
    #!/bin/sh

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
```


  1. Push to the repo from your local machine:
    bash
    git remote add staging user@your-git-server:repos/my-app.git
    git push staging main

    Expected: Code auto-deploys to /var/www/my-app-staging.

Task 3: Use pre-commit Framework (Python)

Goal: Enforce black, flake8, and mypy checks before commits.


Steps

  1. Install pre-commit:
    bash
    pip install pre-commit

  2. Create .pre-commit-config.yaml:
    ```yaml
    repos:


    • repo: https://github.com/psf/black
      rev: 23.12.1
      hooks:
      • id: black
    • repo: https://github.com/PyCQA/flake8
      rev: 6.1.0
      hooks:
      • id: flake8
    • repo: https://github.com/pre-commit/mirrors-mypy
      rev: v1.8.0
      hooks:
      • id: mypy
        ```
  3. Install the hooks:
    bash
    pre-commit install

  4. 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.


4. ? Production-Ready Best Practices


Security

  • Never hardcode secrets in hooks (use environment variables or git-secrets).
  • Restrict server-side hooks to specific users (e.g., chmod 750 hooks/post-receive).
  • Use pre-receive to block force-pushes to protected branches.

Reliability

  • Test hooks locally before deploying to production.
  • Log hook output for debugging (e.g., echo "Deploy failed" >> /var/log/git-hooks.log).
  • Make hooks idempotent (e.g., don’t restart a service if the build fails).

Maintainability

  • Version-control hooks (e.g., store them in hooks/ and symlink to .git/hooks).
  • Use frameworks (Husky for JS, pre-commit for Python) to avoid manual setup.
  • Document hooks in your repo’s README.md (e.g., "This repo uses pre-commit for linting").


5. ⚠️ Common Mistakes & Traps

Mistake Symptom Fix/Prevention
Hooks not executable git commit fails silently. Run chmod +x .git/hooks/pre-commit.
Hooks not version-controlled Team members don’t get the hooks. Store hooks in hooks/ and symlink: ln -s ../../hooks/pre-commit .git/hooks/.
Server-side hooks fail silently Deployments don’t trigger. Log output: echo "Hook ran at $(date)" >> /var/log/git-hooks.log.
pre-commit blocks all commits git commit hangs or fails. Add exit 0 at the end of the hook (or use --no-verify to bypass).
Hooks run on every file Slow commits (e.g., running mypy on all files). Use git diff --cached to target only staged files.


6. ? Exam/Certification Focus


Typical Question Patterns

  1. "Which hook runs before a commit is finalized?"
  2. pre-commit
  3. post-commit (runs after)
  4. pre-push (runs before push, not commit)

  5. "How do you enforce a policy that blocks direct pushes to main?"

  6. ✅ Use a pre-receive hook on the server.
  7. ❌ Use a pre-commit hook (client-side only).

  8. "What’s the difference between pre-receive and post-receive?"

  9. pre-receive: Runs before code is accepted (can reject pushes).
  10. post-receive: Runs after code is accepted (used for deployments).

Key Trap Distinctions

  • Client vs. Server Hooks:
  • Client: pre-commit, pre-push (local machine).
  • Server: pre-receive, post-receive (remote server).
  • Hooks are not version-controlled by default (must manually add them to the repo).


7. ? Hands-On Challenge


Challenge

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.

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).


8. ? Rapid-Reference Crib Sheet

Command/Concept Usage
chmod +x .git/hooks/pre-commit Make a hook executable.
git commit --no-verify Bypass hooks (use sparingly!).
pre-commit install Install Python pre-commit hooks.
Husky npx husky add .husky/pre-commit "npm test" (JS/TS projects).
pre-receive ⚠️ Runs on the server before code is accepted.
post-receive Runs on the server after code is accepted (use for deployments).
git diff --cached Get staged files (useful for targeting hooks).
exit 1 Fail the hook (block the Git action).
exit 0 Succeed the hook (allow the Git action).
Default hook location .git/hooks/ (not version-controlled by default).


9. ? Where to Go Next

  1. Git Hooks Official Docs
  2. Pre-commit Framework (Python)
  3. Husky (JS/TS)
  4. Conventional Commits (for commit message hooks)


ADVERTISEMENT