Git Hooks
Git hooks are scripts that Git executes automatically at certain points in the Git workflow. They allow you to automate tasks, enforce policies, and integrate with external tools to maintain code quality and consistency.
Understanding Git Hooks
What are Git Hooks?
Git hooks are executable scripts that:
- Run automatically at specific Git events
- Can prevent actions if they exit with non-zero status
- Are stored in
.git/hooks/directory - Can be written in any scripting language
- Are not version controlled by default
Types of Git Hooks
Client-side Hooks
- pre-commit: Before commit is created
- prepare-commit-msg: Before commit message editor
- commit-msg: After commit message is entered
- post-commit: After commit is completed
- pre-rebase: Before rebase operation
- post-checkout: After checkout
- post-merge: After merge
- pre-push: Before push to remote
- post-rewrite: After commands that rewrite commits
Server-side Hooks
- pre-receive: Before any refs are updated
- update: Before each ref is updated
- post-receive: After all refs are updated
- post-update: After all refs are updated (similar to post-receive)
Setting Up Hook Examples
bash
# Create example project
mkdir git-hooks-demo
cd git-hooks-demo
git init
# Create package.json for Node.js project
cat > package.json << EOF
{
"name": "git-hooks-demo",
"version": "1.0.0",
"description": "Demo project for Git hooks",
"main": "index.js",
"scripts": {
"test": "node test.js",
"lint": "eslint .",
"format": "prettier --write .",
"lint:fix": "eslint . --fix"
},
"devDependencies": {
"eslint": "^8.0.0",
"prettier": "^2.0.0"
}
}
EOF
# Create sample JavaScript files
cat > index.js << EOF
const Calculator = require('./calculator');
function main() {
const calc = new Calculator();
console.log('2 + 3 =', calc.add(2, 3));
console.log('10 - 4 =', calc.subtract(10, 4));
}
if (require.main === module) {
main();
}
module.exports = { main };
EOF
cat > calculator.js << EOF
class Calculator {
add(a, b) {
return a + b;
}
subtract(a, b) {
return a - b;
}
multiply(a, b) {
return a * b;
}
divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
}
module.exports = Calculator;
EOF
# Create test file
cat > test.js << EOF
const Calculator = require('./calculator');
function runTests() {
const calc = new Calculator();
let passed = 0;
let failed = 0;
function test(description, actual, expected) {
if (actual === expected) {
console.log(`✓ ${description}`);
passed++;
} else {
console.log(`✗ ${description}: expected ${expected}, got ${actual}`);
failed++;
}
}
// Run tests
test('Addition: 2 + 3', calc.add(2, 3), 5);
test('Subtraction: 10 - 4', calc.subtract(10, 4), 6);
test('Multiplication: 3 * 4', calc.multiply(3, 4), 12);
test('Division: 15 / 3', calc.divide(15, 3), 5);
console.log(`\nTests completed: ${passed} passed, ${failed} failed`);
if (failed > 0) {
process.exit(1);
}
}
runTests();
EOF
# Create ESLint configuration
cat > .eslintrc.js << EOF
module.exports = {
env: {
node: true,
es2021: true,
},
extends: ['eslint:recommended'],
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
},
rules: {
'no-console': 'off',
'no-unused-vars': 'error',
'no-undef': 'error',
'semi': ['error', 'always'],
'quotes': ['error', 'single'],
'indent': ['error', 4],
},
};
EOF
# Create Prettier configuration
cat > .prettierrc << EOF
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 4
}
EOF
git add .
git commit -m "Initial project setup"
Pre-commit Hook
Basic Pre-commit Hook
bash
# Create pre-commit hook
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
echo "Running pre-commit checks..."
# Check if this is the initial commit
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=$(git hash-object -t tree /dev/null)
fi
# Get list of staged files
staged_files=$(git diff --cached --name-only --diff-filter=ACM)
if [ -z "$staged_files" ]; then
echo "No staged files to check"
exit 0
fi
echo "Checking staged files: $staged_files"
# Check for syntax errors in JavaScript files
for file in $staged_files; do
if [[ "$file" =~ \.(js|jsx)$ ]]; then
echo "Checking syntax: $file"
node -c "$file"
if [ $? -ne 0 ]; then
echo "❌ Syntax error in $file"
exit 1
fi
fi
done
echo "✅ Pre-commit checks passed"
EOF
chmod +x .git/hooks/pre-commit
Advanced Pre-commit Hook with ESLint and Prettier
bash
# Create comprehensive pre-commit hook
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
set -e
echo "🔍 Running pre-commit checks..."
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# Get staged files
staged_files=$(git diff --cached --name-only --diff-filter=ACM)
js_files=$(echo "$staged_files" | grep -E '\.(js|jsx|ts|tsx)$' || true)
json_files=$(echo "$staged_files" | grep -E '\.(json)$' || true)
if [ -z "$staged_files" ]; then
print_status $YELLOW "No staged files to check"
exit 0
fi
print_status $YELLOW "Staged files: $(echo $staged_files | wc -w)"
# Check 1: Prevent commits to main branch
current_branch=$(git symbolic-ref --short HEAD)
if [ "$current_branch" = "main" ] || [ "$current_branch" = "master" ]; then
print_status $RED "❌ Direct commits to $current_branch branch are not allowed"
print_status $YELLOW "Please create a feature branch:"
print_status $YELLOW " git checkout -b feature/your-feature-name"
exit 1
fi
# Check 2: Prevent large files
max_file_size=1048576 # 1MB in bytes
for file in $staged_files; do
if [ -f "$file" ]; then
file_size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo 0)
if [ "$file_size" -gt "$max_file_size" ]; then
print_status $RED "❌ File $file is too large ($(($file_size / 1024))KB > 1MB)"
exit 1
fi
fi
done
# Check 3: Prevent sensitive information
print_status $YELLOW "🔒 Checking for sensitive information..."
sensitive_patterns=(
"password\s*=\s*['\"][^'\"]+['\"]" # password = "..."
"api[_-]?key\s*=\s*['\"][^'\"]+['\"]" # api_key = "..."
"secret\s*=\s*['\"][^'\"]+['\"]" # secret = "..."
"token\s*=\s*['\"][^'\"]+['\"]" # token = "..."
"-----BEGIN [A-Z ]+-----" # Private keys
)
for file in $staged_files; do
if [ -f "$file" ]; then
for pattern in "${sensitive_patterns[@]}"; do
if grep -iE "$pattern" "$file" >/dev/null; then
print_status $RED "❌ Potential sensitive information found in $file"
print_status $RED " Pattern: $pattern"
exit 1
fi
done
fi
done
# Check 4: JSON syntax validation
if [ -n "$json_files" ]; then
print_status $YELLOW "📋 Validating JSON files..."
for file in $json_files; do
if [ -f "$file" ]; then
if ! python -m json.tool "$file" >/dev/null 2>&1; then
print_status $RED "❌ Invalid JSON syntax in $file"
exit 1
fi
fi
done
print_status $GREEN "✅ JSON validation passed"
fi
# Check 5: JavaScript/TypeScript linting
if [ -n "$js_files" ]; then
print_status $YELLOW "🔍 Running ESLint..."
# Check if ESLint is available
if command -v npx >/dev/null 2>&1 && [ -f "package.json" ]; then
# Run ESLint on staged files
echo "$js_files" | xargs npx eslint
if [ $? -ne 0 ]; then
print_status $RED "❌ ESLint found issues"
print_status $YELLOW "💡 Try running: npm run lint:fix"
exit 1
fi
print_status $GREEN "✅ ESLint passed"
else
print_status $YELLOW "⚠️ ESLint not available, skipping lint check"
fi
# Check 6: Prettier formatting
print_status $YELLOW "💅 Checking Prettier formatting..."
if command -v npx >/dev/null 2>&1 && [ -f "package.json" ]; then
# Check if files are formatted
echo "$js_files" | xargs npx prettier --check
if [ $? -ne 0 ]; then
print_status $RED "❌ Code formatting issues found"
print_status $YELLOW "💡 Try running: npm run format"
exit 1
fi
print_status $GREEN "✅ Prettier formatting passed"
else
print_status $YELLOW "⚠️ Prettier not available, skipping format check"
fi
fi
# Check 7: Run tests
print_status $YELLOW "🧪 Running tests..."
if [ -f "package.json" ] && grep -q '"test"' package.json; then
npm test
if [ $? -ne 0 ]; then
print_status $RED "❌ Tests failed"
exit 1
fi
print_status $GREEN "✅ Tests passed"
elif [ -f "test.js" ]; then
node test.js
if [ $? -ne 0 ]; then
print_status $RED "❌ Tests failed"
exit 1
fi
print_status $GREEN "✅ Tests passed"
else
print_status $YELLOW "⚠️ No tests found, skipping test check"
fi
# Check 8: Commit message preparation
print_status $YELLOW "📝 Preparing commit message validation..."
print_status $GREEN "🎉 All pre-commit checks passed!"
EOF
chmod +x .git/hooks/pre-commit
Testing Pre-commit Hook
bash
# Test the pre-commit hook
echo "console.log('test');" > test-file.js
git add test-file.js
# This should trigger the pre-commit hook
git commit -m "Test commit"
# Clean up
git reset --soft HEAD~1
rm test-file.js
Commit Message Hook
Commit Message Validation
bash
# Create commit-msg hook for conventional commits
cat > .git/hooks/commit-msg << 'EOF'
#!/bin/bash
# Conventional Commits validation
# Format: type(scope): description
# Example: feat(auth): add login functionality
commit_regex='^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{1,50}'
commit_message=$(cat "$1")
# Skip merge commits
if echo "$commit_message" | grep -qE '^Merge (branch|pull request)'; then
exit 0
fi
# Skip revert commits
if echo "$commit_message" | grep -qE '^Revert '; then
exit 0
fi
# Validate commit message format
if ! echo "$commit_message" | grep -qE "$commit_regex"; then
echo "❌ Invalid commit message format!"
echo ""
echo "Commit message must follow Conventional Commits format:"
echo " type(scope): description"
echo ""
echo "Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert"
echo "Scope: optional, e.g., (auth), (ui), (api)"
echo "Description: 1-50 characters"
echo ""
echo "Examples:"
echo " feat(auth): add login functionality"
echo " fix: resolve memory leak in calculator"
echo " docs: update README with installation steps"
echo " test(calculator): add unit tests for division"
echo ""
echo "Your message: $commit_message"
exit 1
fi
# Check for imperative mood
first_word=$(echo "$commit_message" | sed 's/^[^:]*: *//' | cut -d' ' -f1)
if echo "$first_word" | grep -qE '(ed|ing)$'; then
echo "⚠️ Warning: Use imperative mood in commit messages"
echo " Instead of '$first_word', use the base form of the verb"
echo " Example: 'add' instead of 'added', 'fix' instead of 'fixing'"
fi
echo "✅ Commit message format is valid"
EOF
chmod +x .git/hooks/commit-msg
Advanced Commit Message Hook
bash
# Create comprehensive commit-msg hook
cat > .git/hooks/commit-msg << 'EOF'
#!/bin/bash
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
print_status() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
commit_message_file="$1"
commit_message=$(cat "$commit_message_file")
# Skip certain types of commits
if echo "$commit_message" | grep -qE '^(Merge|Revert|Initial commit)'; then
print_status $GREEN "✅ Special commit type, skipping validation"
exit 0
fi
# Extract first line (subject)
subject=$(echo "$commit_message" | head -n1)
body=$(echo "$commit_message" | tail -n +3)
print_status $YELLOW "📝 Validating commit message..."
# Validation 1: Conventional Commits format
conventional_regex='^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?!?: .{1,50}'
if ! echo "$subject" | grep -qE "$conventional_regex"; then
print_status $RED "❌ Invalid commit message format!"
echo ""
echo "Format: type(scope): description"
echo "Types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert"
echo "Scope: optional, e.g., (auth), (ui), (api)"
echo "Breaking change: add ! after type/scope"
echo ""
echo "Examples:"
echo " feat(auth): add OAuth2 integration"
echo " fix!: resolve breaking API change"
echo " docs: update installation guide"
echo ""
echo "Your subject: $subject"
exit 1
fi
# Validation 2: Subject line length
subject_length=${#subject}
if [ $subject_length -gt 72 ]; then
print_status $RED "❌ Subject line too long ($subject_length > 72 characters)"
exit 1
fi
# Validation 3: Subject line should not end with period
if echo "$subject" | grep -q '\.$'; then
print_status $RED "❌ Subject line should not end with a period"
exit 1
fi
# Validation 4: Body line length (if body exists)
if [ -n "$body" ]; then
while IFS= read -r line; do
if [ ${#line} -gt 72 ]; then
print_status $YELLOW "⚠️ Body line exceeds 72 characters: ${#line}"
print_status $YELLOW " Consider wrapping: $line"
fi
done <<< "$body"
fi
# Validation 5: Check for issue references
if echo "$commit_message" | grep -qE '#[0-9]+'; then
print_status $GREEN "✅ Issue reference found"
fi
# Validation 6: Suggest breaking change format
if echo "$subject" | grep -iE '(break|breaking)' && ! echo "$subject" | grep -q '!'; then
print_status $YELLOW "⚠️ Possible breaking change detected"
print_status $YELLOW " Consider using ! in type: feat!: or fix!:"
fi
# Validation 7: Check for co-authors
if echo "$commit_message" | grep -qE '^Co-authored-by:'; then
print_status $GREEN "✅ Co-author information found"
fi
print_status $GREEN "✅ Commit message validation passed"
# Optional: Add commit statistics
files_changed=$(git diff --cached --name-only | wc -l)
lines_added=$(git diff --cached --numstat | awk '{add += $1} END {print add+0}')
lines_deleted=$(git diff --cached --numstat | awk '{del += $2} END {print del+0}')
print_status $YELLOW "📊 Commit stats: $files_changed files, +$lines_added/-$lines_deleted lines"
EOF
chmod +x .git/hooks/commit-msg
Pre-push Hook
Basic Pre-push Hook
bash
# Create pre-push hook
cat > .git/hooks/pre-push << 'EOF'
#!/bin/bash
echo "🚀 Running pre-push checks..."
remote="$1"
url="$2"
echo "Pushing to: $remote ($url)"
# Get current branch
current_branch=$(git symbolic-ref --short HEAD)
# Prevent push to main/master from local branches
if [ "$current_branch" = "main" ] || [ "$current_branch" = "master" ]; then
echo "❌ Direct push to $current_branch is not allowed"
echo "Please use pull requests for main branch changes"
exit 1
fi
# Run tests before push
echo "🧪 Running tests before push..."
if [ -f "package.json" ] && grep -q '"test"' package.json; then
npm test
if [ $? -ne 0 ]; then
echo "❌ Tests failed, push aborted"
exit 1
fi
fi
echo "✅ Pre-push checks passed"
EOF
chmod +x .git/hooks/pre-push
Advanced Pre-push Hook
bash
# Create comprehensive pre-push hook
cat > .git/hooks/pre-push << 'EOF'
#!/bin/bash
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_status() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
remote="$1"
url="$2"
print_status $BLUE "🚀 Running pre-push checks..."
print_status $YELLOW "Remote: $remote ($url)"
# Get current branch and commits being pushed
current_branch=$(git symbolic-ref --short HEAD)
print_status $YELLOW "Branch: $current_branch"
# Read stdin to get the range of commits being pushed
while read local_ref local_sha remote_ref remote_sha; do
if [ "$local_sha" = "0000000000000000000000000000000000000000" ]; then
# Branch is being deleted
print_status $YELLOW "🗑️ Deleting branch: $(basename "$remote_ref")"
continue
fi
if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then
# New branch
range="$local_sha"
print_status $YELLOW "🆕 New branch: $(basename "$remote_ref")"
else
# Existing branch
range="$remote_sha..$local_sha"
print_status $YELLOW "📤 Updating branch: $(basename "$remote_ref")"
fi
# Check 1: Prevent direct push to protected branches
branch_name=$(basename "$remote_ref")
if [ "$branch_name" = "main" ] || [ "$branch_name" = "master" ] || [ "$branch_name" = "develop" ]; then
print_status $RED "❌ Direct push to protected branch '$branch_name' is not allowed"
print_status $YELLOW "Please use pull requests for protected branch changes"
exit 1
fi
# Check 2: Validate commit messages in the push
print_status $YELLOW "📝 Validating commit messages..."
conventional_regex='^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?!?: .{1,50}'
invalid_commits=$(git rev-list "$range" | while read commit; do
message=$(git log --format=%s -n 1 "$commit")
if ! echo "$message" | grep -qE "$conventional_regex" &&
! echo "$message" | grep -qE '^(Merge|Revert|Initial commit)'; then
echo "$commit: $message"
fi
done)
if [ -n "$invalid_commits" ]; then
print_status $RED "❌ Invalid commit messages found:"
echo "$invalid_commits"
print_status $YELLOW "Please fix commit messages or use interactive rebase"
exit 1
fi
# Check 3: Prevent force push (unless explicitly allowed)
if git merge-base --is-ancestor "$local_sha" "$remote_sha" 2>/dev/null; then
print_status $YELLOW "⚠️ Force push detected"
if [ "$ALLOW_FORCE_PUSH" != "true" ]; then
print_status $RED "❌ Force push is not allowed"
print_status $YELLOW "Use 'git push --force-with-lease' if you're sure"
print_status $YELLOW "Or set ALLOW_FORCE_PUSH=true environment variable"
exit 1
fi
fi
# Check 4: Ensure no merge commits in feature branches
if [ "$branch_name" != "main" ] && [ "$branch_name" != "master" ] && [ "$branch_name" != "develop" ]; then
merge_commits=$(git rev-list --merges "$range")
if [ -n "$merge_commits" ]; then
print_status $YELLOW "⚠️ Merge commits found in feature branch"
print_status $YELLOW "Consider rebasing to maintain linear history"
fi
fi
# Check 5: Large file detection
print_status $YELLOW "📁 Checking for large files..."
large_files=$(git rev-list "$range" | while read commit; do
git ls-tree -r -l "$commit" | awk '$4 > 1048576 {print $4, $5}' # Files > 1MB
done | sort -u)
if [ -n "$large_files" ]; then
print_status $YELLOW "⚠️ Large files detected:"
echo "$large_files"
print_status $YELLOW "Consider using Git LFS for large files"
fi
done
# Check 6: Run comprehensive tests
print_status $YELLOW "🧪 Running test suite..."
if [ -f "package.json" ]; then
if grep -q '"lint"' package.json; then
print_status $YELLOW "🔍 Running linter..."
npm run lint
if [ $? -ne 0 ]; then
print_status $RED "❌ Linting failed"
exit 1
fi
fi
if grep -q '"test"' package.json; then
print_status $YELLOW "🧪 Running tests..."
npm test
if [ $? -ne 0 ]; then
print_status $RED "❌ Tests failed"
exit 1
fi
fi
if grep -q '"build"' package.json; then
print_status $YELLOW "🔨 Running build..."
npm run build
if [ $? -ne 0 ]; then
print_status $RED "❌ Build failed"
exit 1
fi
fi
fi
# Check 7: Security scan (if tools available)
if command -v npm >/dev/null 2>&1 && [ -f "package.json" ]; then
print_status $YELLOW "🔒 Running security audit..."
npm audit --audit-level=high
if [ $? -ne 0 ]; then
print_status $YELLOW "⚠️ Security vulnerabilities found"
print_status $YELLOW "Consider running 'npm audit fix'"
fi
fi
# Check 8: Branch protection rules
if [ -f ".github/branch-protection.json" ]; then
print_status $YELLOW "🛡️ Checking branch protection rules..."
# Custom branch protection logic here
fi
print_status $GREEN "🎉 All pre-push checks passed!"
print_status $BLUE "📤 Proceeding with push..."
EOF
chmod +x .git/hooks/pre-push
Post-commit Hook
Notification and Automation
bash
# Create post-commit hook for notifications
cat > .git/hooks/post-commit << 'EOF'
#!/bin/bash
# Get commit information
commit_hash=$(git rev-parse HEAD)
commit_message=$(git log -1 --pretty=%B)
author=$(git log -1 --pretty=%an)
email=$(git log -1 --pretty=%ae)
date=$(git log -1 --pretty=%ad --date=iso)
branch=$(git symbolic-ref --short HEAD)
# Count changes
files_changed=$(git diff-tree --no-commit-id --name-only -r HEAD | wc -l)
lines_added=$(git show --numstat HEAD | awk '{add += $1} END {print add+0}')
lines_deleted=$(git show --numstat HEAD | awk '{del += $2} END {print del+0}')
echo "📝 Commit completed successfully!"
echo " Hash: $commit_hash"
echo " Author: $author"
echo " Branch: $branch"
echo " Files: $files_changed changed"
echo " Lines: +$lines_added/-$lines_deleted"
echo " Message: $commit_message"
# Optional: Send notification to team chat
if [ -n "$SLACK_WEBHOOK_URL" ]; then
curl -X POST -H 'Content-type: application/json' \
--data "{
\"text\": \"New commit by $author on $branch\\n$commit_message\\n$commit_hash\"
}" \
"$SLACK_WEBHOOK_URL"
fi
# Optional: Update issue tracker
if echo "$commit_message" | grep -qE '#[0-9]+'; then
issue_number=$(echo "$commit_message" | grep -oE '#[0-9]+' | head -1 | tr -d '#')
echo "🔗 Referenced issue: #$issue_number"
# Add issue tracker integration here
fi
# Optional: Trigger CI/CD
if [ "$branch" = "main" ] || [ "$branch" = "develop" ]; then
echo "🚀 Triggering CI/CD pipeline..."
# Add CI/CD trigger logic here
fi
EOF
chmod +x .git/hooks/post-commit
Hook Management and Sharing
Sharing Hooks with Team
bash
# Create hooks directory in repository
mkdir -p .githooks
# Move hooks to shared directory
cp .git/hooks/pre-commit .githooks/
cp .git/hooks/commit-msg .githooks/
cp .git/hooks/pre-push .githooks/
# Create installation script
cat > .githooks/install.sh << 'EOF'
#!/bin/bash
echo "Installing Git hooks..."
# Get the directory where this script is located
HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GIT_HOOKS_DIR="$(git rev-parse --git-dir)/hooks"
# Install each hook
for hook in "$HOOKS_DIR"/*; do
hook_name=$(basename "$hook")
# Skip the install script itself
if [ "$hook_name" = "install.sh" ]; then
continue
fi
echo "Installing $hook_name..."
cp "$hook" "$GIT_HOOKS_DIR/$hook_name"
chmod +x "$GIT_HOOKS_DIR/$hook_name"
done
echo "✅ Git hooks installed successfully!"
echo "To update hooks, run this script again."
EOF
chmod +x .githooks/install.sh
# Create README for hooks
cat > .githooks/README.md << 'EOF'
# Git Hooks
This directory contains shared Git hooks for the project.
## Installation
Run the installation script to set up hooks:
```bash
./.githooks/install.sh
Available Hooks
- pre-commit: Runs linting, formatting, and tests before commit
- commit-msg: Validates commit message format (Conventional Commits)
- pre-push: Runs comprehensive checks before push
- post-commit: Sends notifications and updates trackers
Bypassing Hooks
In emergency situations, you can bypass hooks:
bash
# Skip pre-commit hook
git commit --no-verify -m "Emergency fix"
# Skip pre-push hook
git push --no-verify
Note: Use bypass sparingly and only in emergencies. EOF