Fundamentals 9 min read

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.

Coder Trainee
Coder Trainee
Coder Trainee
Mastering Advanced Git Hooks: Turning Scripts into Team Automation

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 node

1.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": "'
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Coder Trainee
Written by

Coder Trainee

Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.