Your branching strategy is solid. Your team knows git rebase from git merge. Now what?
The difference between a team that uses Git and a team that leverages Git often comes down to hooks - scripts that catch problems before they spread.
Why This Matters for Infrastructure Engineers
DevOps lives at the intersection of code and operations. Git can do more than track changes - it can enforce standards automatically.
I once spent an hour reviewing a junior engineer’s Terraform PR that should have taken twenty minutes. The logic was solid - they’d done good work - but it was hard to follow. Inconsistent indentation, tabs mixed with spaces, resource blocks formatted differently depending on which example they’d referenced.
Instead of reviewing infrastructure logic, I left a dozen comments about formatting. That’s not the feedback anyone wants to give or receive. The engineer had put real effort into the PR, and we ended up spending our sync talking about whitespace instead of their actual contribution.
The fix wasn’t more code review diligence - it was a pre-commit hook. Now terraform fmt runs
automatically before any commit reaches the repo. The code is always readable, reviews focus on
what matters, and new engineers get consistent formatting from day one without anyone having to
point it out.
What Are Git Hooks?
Hooks are scripts Git runs on specific events. They’re your first line of defense.
Local vs Server-Side
- Local hooks live in
.git/hooks/(not versioned by default) - Server-side hooks run on the remote (
pre-receive,update) - managed by platform admins
The hooks you’ll use most:
| Hook | When it runs | Use case |
|---|---|---|
pre-commit | Before commit is created | Lint, format, block secrets |
commit-msg | After message is written | Enforce conventional commits |
pre-push | Before push to remote | Fast validation |
post-merge | After merge completes | Sync dependencies |
Pre-Commit: Quality at the Edge
A pre-commit hook prevents garbage from entering your history. Here’s one that handles Terraform formatting and catches secrets:
#!/usr/bin/env bash
set -euo pipefail
echo "[pre-commit] running checks..."
STAGED=$(git diff --cached --name-only --diff-filter=ACM)
# Terraform fmt on staged *.tf files
echo "$STAGED" | grep -E '\.tf$' >/dev/null && {
terraform fmt -recursive
git add $(echo "$STAGED" | grep -E '\.tf$' || true)
}
# YAML lint
if command -v yamllint >/dev/null; then
echo "$STAGED" | grep -E '\.ya?ml$' >/dev/null && \
yamllint -s $(echo "$STAGED" | grep -E '\.ya?ml$' || true)
fi
# Block secrets (basic pattern matching)
if echo "$STAGED" | xargs grep -E 'AKIA[0-9A-Z]{16}|secret_key|password\s*:' 2>/dev/null; then
echo "Potential secret detected. Aborting."
exit 1
fi
echo "[pre-commit] OK"
The Better Way: pre-commit Framework
Writing raw hook scripts works, but the pre-commit framework makes it maintainable:
pip install pre-commit
.pre-commit-config.yaml:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.105.0
hooks:
- id: terraform_fmt
- id: terraform_validate
pre-commit install
Now every commit runs through these checks automatically.
Commit Message Hooks: Enforce Conventions
Conventional Commits (feat:, fix:, chore:) let CI generate changelogs and trigger release logic. Enforce them:
.git/hooks/commit-msg:
#!/usr/bin/env bash
set -euo pipefail
MSG_FILE="$1"
PATTERN='^(feat|fix|build|docs|style|refactor|perf|test|ci|chore|revert)(\(.+\))?: .{1,80}$'
if ! grep -Eq "$PATTERN" "$MSG_FILE"; then
echo "Commit message must follow Conventional Commits:"
echo " feat(api): add rate limiter"
echo " fix(terraform): correct storage account SKU"
exit 1
fi
Pre-Push: One Last Check
Run fast validations before code leaves your machine. Keep it under 10 seconds or people will bypass it.
#!/usr/bin/env bash
set -euo pipefail
# Check if upstream is set; skip diff if not
if ! git rev-parse --abbrev-ref '@{u}' &>/dev/null; then
echo "[pre-push] No upstream set, skipping diff check"
exit 0
fi
FOUND=$(git diff --name-only @{u}..HEAD | grep -E '\.tf$' || true)
if [ -n "$FOUND" ]; then
terraform fmt -check -recursive || exit 1
fi
echo "[pre-push] OK"
Post-Merge: Keep Environments Fresh
After pulling or merging, refresh dependencies automatically:
#!/usr/bin/env bash
set -euo pipefail
[ -f requirements.txt ] && pip install -r requirements.txt >/dev/null || true
[ -f package.json ] && npm ci --prefer-offline >/dev/null || true
[ -d .gitmodules ] && git submodule update --init --recursive
echo "[post-merge] deps refreshed"
Hands-On Lab: Set Up Git Hooks
Step 1: Initialize a repo
mkdir git-hooks-lab && cd git-hooks-lab
git init
echo "# Git Hooks Lab" > README.md
git add README.md && git commit -m "chore: init"
Step 2: Install pre-commit hooks
pip install pre-commit
cat > .pre-commit-config.yaml <<'YAML'
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.105.0
hooks:
- id: terraform_fmt
- id: terraform_validate
YAML
pre-commit install
Step 3: Add commit message enforcement
cat > .git/hooks/commit-msg <<'HOOK'
#!/usr/bin/env bash
set -euo pipefail
PATTERN='^(feat|fix|build|docs|style|refactor|perf|test|ci|chore|revert)(\(.+\))?: .{1,80}$'
if ! grep -Eq "$PATTERN" "$1"; then
echo "Commit message must follow Conventional Commits."
exit 1
fi
HOOK
chmod +x .git/hooks/commit-msg
Troubleshooting Guide
| Problem | Cause | Fix |
|---|---|---|
| Hook didn’t run | Not executable | chmod +x .git/hooks/pre-commit |
| Hooks not shared with team | .git/hooks/ isn’t versioned | Use pre-commit framework or bootstrap script |
Quick Reference
pre-commit install # Install pre-commit framework
chmod +x .git/hooks/pre-commit # Make hook executable
pre-commit run --all-files # Run hooks on all files
pre-commit autoupdate # Update hook versions
What’s Next
Next post: Git Submodules - Managing Shared Code Across Repos. We’ll look at how to embed repositories within repositories, keep them in sync, and avoid the pitfalls that trip up every team.
Happy automating!