Git Hooks and Automation¶
Overview¶
Git hooks are scripts Git runs automatically at lifecycle events — before commit, after merge, before push. They enforce team standards locally (format, lint, secrets scan) and on the server (reject non-compliant pushes). For DevOps teams, hooks are the first line of defense before CI minutes are spent.
This is Tutorial 16 in Module 6: Advanced & DevOps of the REBASH Academy Git series.
Prerequisites¶
- Git Bisect and Debugging History
- Basic Git Workflow — Add, Commit, Push
- Shell scripting basics from the Linux track
Learning Objectives¶
By the end of this tutorial, you will be able to:
- Identify client-side vs server-side hooks
- Install and write hooks in
.git/hooks/ - Use the pre-commit framework for shared hook management
- Implement pre-commit, commit-msg, and pre-push hooks
- Understand server-side hooks on GitHub, GitLab, and bare repos
- Bypass hooks safely when necessary (
--no-verify) - Integrate hooks with Terraform, YAML, and secret scanning
Architecture¶
Hooks run scripts around Git events so teams can enforce policy locally and on the server before history is accepted.
Theory¶
Hook Basics¶
Hooks live in .git/hooks/ (non-bare) or bare repo root hooks directory. Filename is hook name without extension — must be executable.
Sample listing:
pre-commit
commit-msg
pre-push
post-merge
pre-receive (server)
update (server)
post-receive (server)
Exit non-zero to abort the Git operation.
Client-Side Hooks¶
Run on developer machine:
| Hook | When | Common use |
|---|---|---|
| pre-commit | Before commit created | Lint, format, secrets scan |
| commit-msg | After message entered | Enforce conventional commits |
| pre-push | Before push | Run unit tests |
| post-merge | After merge | npm install, terraform init |
| prepare-commit-msg | Before editor opens | Add ticket prefix |
Not copied on clone — each developer must install.
Server-Side Hooks¶
Run on Git server (bare repo or platform equivalent):
| Hook | Purpose |
|---|---|
| pre-receive | Reject entire push if any ref fails |
| update | Per-ref check during push |
| post-receive | Trigger deploy, mirror, email |
GitHub/GitLab don't expose raw hooks on SaaS — use Actions, CI rules, or push rules instead. Self-hosted GitLab and bare repos support native hooks.
Sharing Hooks — pre-commit Framework¶
pre-commit.com manages hook configs in .pre-commit-config.yaml:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: check-yaml
- id: detect-private-key
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.86.0
hooks:
- id: terraform_fmt
- id: terraform_validate
Install:
Version-controlled — entire team gets same hooks on pre-commit install.
Husky (Node.js projects)¶
JavaScript ecosystems use Husky + lint-staged:
Common in full-stack repos alongside DevOps configs.
Example: commit-msg Hook¶
#!/usr/bin/env bash
# .git/hooks/commit-msg
commit_msg_file="$1"
commit_msg=$(cat "$commit_msg_file")
pattern='^(feat|fix|docs|chore|ci|refactor)(\(.+\))?: .{1,72}$'
if ! echo "$commit_msg" | grep -qE "$pattern"; then
echo "ERROR: Commit message must match conventional format"
echo " Example: feat(iam): add bucket policy"
exit 1
fi
Example: pre-push Hook¶
#!/usr/bin/env bash
# .git/hooks/pre-push — run quick tests before push
remote="$1"
url="$2"
while read -r local_ref local_sha remote_ref remote_sha; do
if [ "$local_sha" = "0000000000000000000000000000000000000000" ]; then
continue # branch delete
fi
echo "Running tests before push to $remote_ref..."
make test-quick || exit 1
done
Skipping Hooks¶
Emergency only — bypasses all hooks. Audit who uses --no-verify via server rules where possible.
core.hooksPath¶
Share custom hooks directory:
Committed to repo — works without pre-commit framework.
Hands-on Lab¶
Create a workspace for this tutorial.
Focus: install a pre-commit hook that blocks .env files
Step 1 – Local hook¶
git init
git config user.name "REBASH Learner"
git config user.email "learner@rebash.local"
cat > .git/hooks/pre-commit << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
if git diff --cached --name-only | grep -E '(^|/)\.env$|\.pem$'; then
echo "Refuse to commit secrets (.env/.pem)" >&2
exit 1
fi
EOF
chmod +x .git/hooks/pre-commit
echo ok > ok.txt
git add ok.txt && git commit -m "chore: ok file"
Step 2 – Prove the hook blocks secrets¶
echo SECRET=1 > .env
git add .env
if git commit -m "bad"; then echo "hook failed to block"; exit 1; else echo "hook blocked secret commit"; fi
git reset HEAD .env
rm -f .env
Final step – Cleanup note¶
Validation¶
Confirm the lab before moving on:
- Re-run the critical commands from the Hands-on Lab and compare them to the expected output in each step.
- Check that you can explain why each successful result matters (not only that it printed).
- Note any warnings or unexpected output — resolve them using Troubleshooting before continuing.
| Check | Pass criteria |
|---|---|
| Hook fires | Client hook blocks or messages as designed on commit/push |
| Bypass awareness | You know --no-verify risks and did not leave risky hooks installed |
| Sample script | Hook script is executable and path is correct |
| Cleanup | Lab hooks removed or disabled |
Code Walkthrough¶
| Command | Description | Example |
|---|---|---|
pre-commit install | Install framework hooks | After cloning repo |
pre-commit run --all-files | Run all hooks manually | CI local mirror |
git commit --no-verify | Skip client hooks | Emergency only |
git config core.hooksPath DIR | Shared hooks directory | Team standardization |
chmod +x .git/hooks/name | Make hook executable | Required for hooks |
pre-commit autoupdate | Update hook versions | Maintenance |
.pre-commit-config.yaml starter for DevOps¶
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-yaml
args: [--allow-multiple-documents]
- id: check-merge-conflict
- id: detect-private-key
- id: end-of-file-fixer
- repo: https://github.com/adrienverge/yamllint
rev: v1.33.0
hooks:
- id: yamllint
Security Considerations¶
- Never install hooks from untrusted repos without reading them — hooks run as your user
- Prefer managed hook frameworks (pre-commit) with pinned versions over ad-hoc curl installs
- Do not put secrets in hook scripts; read them from the environment or a vault at runtime
- Server-side hooks must validate, not silently mutate, pushed content
- Fail closed on secret detection — do not allow
--no-verifyin CI
Common Mistakes¶
Hooks not executable
Git silently skips non-executable hooks. Always chmod +x.
Slow hooks on every commit
Run heavy tests in pre-push or CI; keep pre-commit fast (<5 seconds).
Relying only on client hooks for security
Client hooks are bypassable. Enforce on server via CI and branch protection.
Not versioning shared hook config
.git/hooks isn't cloned. Use pre-commit or core.hooksPath in repo.
Best Practices¶
Fast pre-commit, thorough CI
Pre-commit: format + secrets. CI: full test suite + plan.
Pin hook versions in pre-commit config
Reproducible runs across developer machines.
Document --no-verify policy
Only break-glass incidents; require ticket and post-incident review.
Mirror pre-commit in CI
pre-commit run --all-files in pipeline catches hook-skipping.
Troubleshooting¶
| Issue | Cause | Solution |
|---|---|---|
| Hook not running | Not executable or wrong name | chmod +x; verify filename |
| pre-commit not found | Not installed | pip install pre-commit |
| Hook passes locally, fails CI | Different versions | Pin rev in config |
| Infinite hook loop | Hook modifies files triggering itself | Use pre-commit file locking |
| Server hook not firing | SaaS platform limitation | Use CI push rules |
| --no-verify abused | No policy | Audit; server-side checks |
Summary¶
- Git hooks automate validation at commit, push, and receive events
- Client hooks enforce local quality; server hooks enforce org policy
- pre-commit framework shares versioned hook configs across the team
- Exit non-zero to abort; use --no-verify only in emergencies
- Combine hooks with CI — never trust client-side alone for security
Interview Questions¶
- Client-side versus server-side hooks?
- Why aren't .git/hooks committed by default?
- What should a pre-commit hook check in DevOps repos?
- How do hooks fail closed without blocking emergencies?
- Risks of downloading hook scripts from the internet?
Sample answer — question 2
Confirm the hook is executable and runs in the expected shell. Reproduce by running the hook script directly.
Sample answer — question 4
Do not bypass hooks on production repos without audit. Prefer managed frameworks pinned to reviewed versions.
Related Tutorials¶
- Git Bisect and Debugging History (previous — Module 5)
- Advanced Git Workflows (next)
- gitignore and gitattributes
- Git in CI/CD and DevOps
- Signed Commits and Git Security
- Cheat sheet: Git Cheat Sheet
- Interview prep: Git Interview Prep
- Learning path: DevOps Engineer