Pular para o conteúdo principal

Git Hooks — Automate Quality Control

Module 05 45 min

Section Objectives

  • Understand what Git hooks are and how they work
  • Configure pre-commit to validate code quality
  • Configure commit-msg to enforce commit message format
  • Share hooks with the team via Husky or pre-commit

What are Git Hooks?

Git hooks are scripts that run automatically at specific points in the Git workflow:


Hook Types

HookWhenCommon Use
pre-commitBefore commitLinting, formatting, fast tests
commit-msgAfter entering messageValidate message format
post-commitAfter commitNotifications, logging
pre-pushBefore pushFull test suite
post-mergeAfter mergeInstall dependencies
post-checkoutAfter switchInstall dependencies
pre-rebaseBefore rebaseSafety checks

Manual Hook Configuration

Hooks are stored in .git/hooks/:

# View available hook examples
ls .git/hooks/
# pre-commit.sample
# commit-msg.sample
# pre-push.sample
# ...

pre-commit Hook

Create the file .git/hooks/pre-commit with this content:

#!/bin/bash

echo "Running pre-commit checks..."

# 1. Check for Python syntax errors
if command -v python3 &>/dev/null; then
python3 -m py_compile *.py 2>/dev/null
if [ $? -ne 0 ]; then
echo "Python syntax error detected!"
exit 1
fi
fi

# 2. Check for debug artifacts
if git diff --cached | grep -E "console\.log|debugger|pdb\.set_trace|TODO REMOVE"; then
echo "Debug artifacts found in staged files!"
echo "Remove console.log, debugger, pdb.set_trace before committing."
exit 1
fi

# 3. Check for .env files accidentally staged
if git diff --cached --name-only | grep -E "\.env$|\.env\..+$|credentials"; then
echo "Sensitive file detected in staging area!"
echo "Remove .env or credential files before committing."
exit 1
fi

echo "All pre-commit checks passed!"
exit 0
chmod +x .git/hooks/pre-commit

commit-msg Hook

Enforces Conventional Commits format. Create .git/hooks/commit-msg:

#!/bin/bash

COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")

# Conventional Commits pattern
PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf|ci|revert|build)(\(.+\))?: .{1,100}"

if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
echo "Invalid commit message format!"
echo ""
echo "Expected format: type(scope): description"
echo "Types: feat, fix, docs, style, refactor, test, chore, perf, ci, revert, build"
echo ""
echo "Examples:"
echo " feat(auth): add JWT authentication"
echo " fix(api): handle 404 for missing users"
echo " docs: update README"
echo ""
echo "Your message: '$COMMIT_MSG'"
exit 1
fi

echo "Commit message format OK"
exit 0
chmod +x .git/hooks/commit-msg

pre-push Hook

Create .git/hooks/pre-push:

#!/bin/bash

echo "Running test suite before push..."

if command -v python3 &>/dev/null && [ -f "test_app.py" ]; then
python3 test_app.py
if [ $? -ne 0 ]; then
echo "Tests failed! Push aborted."
exit 1
fi
fi

echo "All tests passed! Pushing..."
exit 0
chmod +x .git/hooks/pre-push

Sharing Hooks with the Team

Hooks in .git/hooks/ are not committed (.git/ is not tracked). To share hooks:

Option 1: Python pre-commit Tool

pip install pre-commit

Create .pre-commit-config.yaml:

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-merge-conflict
- id: detect-private-key
- id: no-commit-to-branch
args: ['--branch', 'main']

- repo: https://github.com/psf/black
rev: 24.1.0
hooks:
- id: black

- repo: https://github.com/pycqa/flake8
rev: 7.0.0
hooks:
- id: flake8
args: ['--max-line-length=100']

- repo: https://github.com/compilerla/conventional-pre-commit
rev: v3.0.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
pre-commit install
pre-commit install --hook-type commit-msg
pre-commit run --all-files

Option 2: Husky (JavaScript/Node.js projects)

npm install --save-dev husky
npx husky init

echo "npm run lint" > .husky/pre-commit
echo "npm test" > .husky/pre-push

Create .husky/commit-msg:

npx --no -- commitlint --edit $1
npm install --save-dev @commitlint/cli @commitlint/config-conventional
echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js

Bypassing Hooks (for Emergencies)

# Skip the pre-commit hook (use with caution!)
git commit --no-verify -m "emergency: hotfix prod outage"

# Skip the pre-push hook
git push --no-verify
Use with caution

--no-verify bypasses all quality checks. Only use it for genuine production emergencies, not as a shortcut to avoid fixing issues. Document the reason in your commit message.


Summary

HookCommon Use
pre-commitLinting, formatting, detect secrets
commit-msgValidate Conventional Commits
pre-pushRun full test suite
post-mergeRun npm install after merge
ToolEcosystemDescription
pre-commitUniversalYAML config, Python-based
huskyNode.jsHooks via npm scripts
lefthookUniversalFast, Go-based alternative

Next Steps