<aside> 🔀

Module 15 — Hooks, Maintenance, and Git at Scale (hooks, git gc, fsck). The final module takes Git from a personal tool to fleet infrastructure. Hooks automate and enforce policy at commit and push time; gc/fsck/packfiles keep repositories healthy and small; and shallow, partial, and sparse clones — plus the hard realities of monorepos — are how Git survives repositories with millions of files and commits. Everything from Modules 1–14 converges here, at the scale where it actually runs a company.

🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)

</aside>

<aside> 🚦

Before you start. You need Module 3 (objects, packfiles, refs — the raw material gc and fsck manage), Module 8 (remotes, push — where server hooks and clones live), and Module 9 (protected branches — hooks are the mechanism beneath them). The exercises build local repos; some scale flags (--depth, --filter) need a file:// URL to be honored, which the exercises use.

</aside>

Part A — Hooks: automating and enforcing

A1. Client-side hooks

<aside> 📖

Official docs: Git Book §8.3 — Git Hooks · githooks manual

</aside>

A hook is a script Git runs automatically at a defined moment. They live in .git/hooks/ (Module 3's floor-plan directory, finally opened), named for the event they fire on: pre-commit runs before a commit is recorded — exit nonzero and the commit is aborted; commit-msg validates the message; pre-push runs before a push leaves. These client-side hooks are for convenience and early feedback: run the linter, block a commit with a syntax error or a leftover conflict marker (Module 6) or a secret, enforce the message format from Module 2. A hook is any executable — bash, Python, anything — and it must be marked executable. Crucially, client hooks are not committed (they live in .git, which doesn't travel), so they can't be relied on for enforcement — and can be bypassed with --no-verify. Hooks are ordinary shell scripts — everything about writing robust ones (exit codes, quoting, set -e) is covered in the Bash Scripting track in this same DevOps Learning library.

🧪 Exercise 15.1 — a pre-commit hook that blocks a marker, and its bypass

cd ~/git-course/kitchen                # any repo with a commit
cat > .git/hooks/pre-commit <<'EOF'
#!/bin/bash
# refuse to commit staged changes containing FIXME
if git diff --cached | grep -q "FIXME"; then
  echo "pre-commit: refusing commit — staged changes contain FIXME"
  exit 1
fi
exit 0
EOF
chmod +x .git/hooks/pre-commit         # hooks MUST be executable
echo "x = 1  # FIXME later" >> app.py && git add app.py
git commit -m "Add x with a FIXME"     # blocked by the hook
echo "exit code: $?"
sed -i 's/  # FIXME later//' app.py && git add app.py
git commit -m "Add x"                  # now it passes
git commit --no-verify -m "..." --allow-empty   # --no-verify SKIPS the hook

<aside> 🚨

Real-world analogy — the spellchecker vs the printing press's editor

A pre-commit hook is your word processor's spellcheck: it flags problems as you type, red-underlining mistakes before you print. Fast, helpful, catches typos early. But you can click "ignore" on any squiggle, and the spellchecker only exists on your computer — a coworker's document isn't checked by yours.

Where the analogy stops working. Spellcheck is purely advisory and everyone knows it. The dangerous illusion with client hooks is treating them as a gate — "our pre-commit hook prevents secrets from being committed." It prevents nothing enforceable: it's not committed to the repo (new clones don't have it), and --no-verify skips it. The actual gate is at the press itself — the server-side hook (A2) or the host's protected-branch rules (Module 9) that check every push regardless of what any developer's machine did. Client hooks help authors; server hooks enforce policy. Confusing the two is a real security failure.

</aside>

A2. Server-side hooks

<aside> 📖

Official docs: Git Book §8.3 — Git Hooks · githooks manual

</aside>

Server-side hooks live in the bare repository (Module 8) and run when a push arrives, so they apply to everyone and can't be bypassed by a developer's --no-verify. pre-receive runs once per push before anything is accepted — exit nonzero and the entire push is rejected; this is where you enforce non-negotiables: branch protection, required commit-message format, rejecting force-pushes to main, blocking large files or secrets. update runs per-ref; post-receive runs after acceptance — the trigger for deploys, CI, and notifications. On hosting platforms you rarely write these directly (they're exposed as branch protection rules, required checks, and webhooks — Module 9's enforcement layer is server hooks, productized), but knowing they're the underlying mechanism is what the interview probes.

🧪 Exercise 15.2 — a pre-receive hook that rejects a push

cd ~/git-course && git init --bare -q origin.git   # a server
git clone -q origin.git work && cd work
echo "code" > f.txt && git add f.txt && git commit -q -m "initial"
cat > ../origin.git/hooks/pre-receive <<'EOF'
#!/bin/bash
echo "pre-receive: this server is frozen for the release"
exit 1
EOF
chmod +x ../origin.git/hooks/pre-receive
git push origin main                   # the server refuses

🎯 Interview questions — Part A

Part B — Maintenance: gc, packfiles, fsck