Mastering Advanced Git Hooks: Turning Scripts into Team Automation
This article explains that Git hooks are executable scripts, describes their execution environment, and provides concrete Bash implementations for pre‑commit, commit‑msg, pre‑push, and post‑commit hooks—including quality checks, commit‑message enforcement, automated testing, and notifications—plus a complete Husky + lint‑staged configuration and common scenarios such as blocking key files or validating JIRA tickets.
1. Hooks basics
1.1 Hook scripts
Git hooks are executable scripts that can be written in any language (Shell, Python, Ruby, Node.js) and reside in .git/hooks. The script’s exit status (0 = success, non‑zero = failure) determines whether the Git operation proceeds.
# .git/hooks/pre-commit
#!/bin/bash
# any language works
#!/usr/bin/env python3
#!/usr/bin/env node1.2 Execution environment
Working directory: repository root
Environment variables: inherited from Git
Return value: 0 = success, non‑zero = failure (blocks operation)
2. Advanced hook implementations
2.1 pre-commit – comprehensive quality checks
# .git/hooks/pre-commit
#!/bin/bash
echo "🔍 Running pre-commit quality checks..."
# 1. Unresolved conflict markers
if git diff --cached --check | grep -q "conflict"; then
echo "❌ Unresolved conflict markers detected"
git diff --cached --check
exit 1
fi
# 2. Java code style (Spotless) if Maven project
if [ -f "pom.xml" ]; then
echo "📦 Running Spotless formatting check..."
mvn spotless:check 2>/dev/null
if [ $? -ne 0 ]; then
echo "❌ Code format does not meet standards"
echo "Run 'mvn spotless:apply' to auto‑fix"
exit 1
fi
fi
# 3. Disallow console.log additions
if git diff --cached | grep -E "^\+.*console\.log"; then
echo "❌ console.log found, please remove before committing"
exit 1
fi
# 4. Warn on TODO comments
if git diff --cached | grep -E "^\+.*TODO"; then
echo "⚠️ TODO found, consider handling before committing"
fi
# 5. Reject files larger than 1 MiB
if git diff --cached --name-only | while read file; do
if [ -f "$file" ]; then
size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null)
if [ "$size" -gt 1048576 ]; then
echo "❌ File $file exceeds 1 MiB, not suitable for Git"
exit 1
fi
fi
done; then
echo "✅ File size check passed"
fi
echo "✅ All pre-commit checks passed"2.2 commit-msg – enforce commit‑message conventions
# .git/hooks/commit-msg
#!/bin/bash
commit_msg=$(cat "$1")
# Enforce <type>(<scope>): <subject> pattern
pattern="^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\([a-z0-9-]+\))?: .{1,100}$"
if ! echo "$commit_msg" | head -1 | grep -qE "$pattern"; then
echo "❌ Commit message format error!"
echo "Correct format: <type>(<scope>): <subject>"
echo "Allowed types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert"
exit 1
fi
# Warn if body exceeds 20 lines
body=$(echo "$commit_msg" | tail -n +3)
if [ -n "$body" ] && [ "$(echo "$body" | wc -l)" -gt 20 ]; then
echo "⚠️ Commit description too long (over 20 lines)"
fi
echo "✅ Commit message format correct"2.3 pre-push – automated testing and build
# .git/hooks/pre-push
#!/bin/bash
echo "🔍 Running pre-push checks..."
remote="$1"
current_branch=$(git branch --show-current)
# 1. Protect main/master branches
if [ "$current_branch" = "main" ] || [ "$current_branch" = "master" ]; then
echo "❌ Direct push to $current_branch is not allowed; use PR"
exit 1
fi
# 2. Run Maven unit tests if present
if [ -f "pom.xml" ]; then
echo "📦 Running Maven tests..."
mvn test -DskipTests=false -q
if [ $? -ne 0 ]; then
echo "❌ Unit tests failed, aborting push"
exit 1
fi
fi
# 3. Run Maven package (skip tests)
if [ -f "pom.xml" ]; then
echo "📦 Running Maven package..."
mvn package -DskipTests -q
if [ $? -ne 0 ]; then
echo "❌ Build failed, aborting push"
exit 1
fi
fi
# 4. Ensure local branch is up‑to‑date with remote
git fetch "$remote" "$current_branch" 2>/dev/null
local_commit=$(git rev-parse HEAD)
remote_commit=$(git rev-parse "$remote/$current_branch" 2>/dev/null)
if [ -n "$remote_commit" ] && [ "$local_commit" != "$remote_commit" ]; then
base=$(git merge-base HEAD "$remote/$current_branch")
if [ "$base" != "$remote_commit" ]; then
echo "❌ Local branch diverged from remote; pull and resolve conflicts"
exit 1
fi
fi
echo "✅ All pre-push checks passed"2.4 post-commit – automatic notifications
# .git/hooks/post-commit
#!/bin/bash
commit_hash=$(git log -1 --format="%H")
commit_msg=$(git log -1 --format="%s")
author=$(git log -1 --format="%an")
webhook="https://your-webhook-url"
message="Git commit notification
Author: $author
Message: $commit_msg
Hash: ${commit_hash:0:8}"
curl -X POST "$webhook" -H "Content-Type: application/json" -d '{
"msgtype": "text",
"text": {"content": "'Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Coder Trainee
Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
