Skip to content

Enhancements — Git Hook Collection

Extend the hook collection with more checks and better developer experience.

Enhancement 1 — prepare-commit-msg Auto-Formatting

Auto-prepend the branch name as a scope or ticket ID:

#!/usr/bin/env bash
# prepare-commit-msg — Prepend branch name / ticket ID to message

MSG_FILE="$1"
COMMIT_SOURCE="${2:-}"

# Don't modify merge/squash/amend commits
[[ "$COMMIT_SOURCE" =~ ^(merge|squash|commit)$ ]] && exit 0

# Extract ticket from branch name: feature/JIRA-123-description → JIRA-123
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
TICKET=$(echo "$BRANCH" | grep -oE '[A-Z]+-[0-9]+' | head -1)

if [[ -n "$TICKET" ]]; then
    CURRENT=$(cat "$MSG_FILE")
    # Only prepend if not already present
    if ! grep -q "$TICKET" "$MSG_FILE"; then
        echo "${TICKET}: ${CURRENT}" > "$MSG_FILE"
    fi
fi

Enhancement 2 — post-commit Notification

Print a summary after a successful commit:

#!/usr/bin/env bash
# post-commit — Show a summary after each commit

HASH=$(git log -1 --format="%h")
SUBJECT=$(git log -1 --format="%s")
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "detached")
FILES=$(git diff-tree --no-commit-id -r --name-only HEAD | wc -l)

printf "\n✓ Committed on %s\n" "$BRANCH"
printf "  %s  %s\n" "$HASH" "$SUBJECT"
printf "  %d file(s) changed\n\n" "$FILES"

Enhancement 3 — Check for Secrets in pre-commit

Block commits that contain API keys, passwords, or tokens:

check_secrets() {
    local patterns=(
        'password\s*=\s*["\x27][^\x27"]{6,}'
        'api[_-]?key\s*=\s*["\x27][^\x27"]{16,}'
        'secret\s*=\s*["\x27][^\x27"]{6,}'
        'AKIA[0-9A-Z]{16}'                          # AWS access key
        'sk-[a-zA-Z0-9]{32,}'                       # OpenAI key pattern
        'ghp_[a-zA-Z0-9]{36}'                       # GitHub PAT
    )

    local found=false
    for pattern in "${patterns[@]}"; do
        local matches
        matches=$(git diff --cached | grep -iE "$pattern" || true)
        if [[ -n "$matches" ]]; then
            echo "[pre-commit] Possible secret detected (pattern: $pattern):" >&2
            echo "$matches" | head -3 >&2
            found=true
        fi
    done

    [[ "$found" == true ]] && return 1
    return 0
}

False positives are common

Secret scanning generates false positives on test fixtures, documentation, and example configs. Add a # noqa-secret comment convention for intentional exceptions, and have the hook skip lines containing that marker.

Enhancement 4 — Branch Naming Convention

Enforce branch names like feat/, fix/, chore/ in a pre-push check:

check_branch_name() {
    local branch
    branch=$(git symbolic-ref --short HEAD)
    local pattern='^(feat|fix|docs|chore|refactor|test|ci|release)/.+'

    if [[ "$branch" == "main" || "$branch" == "develop" ]]; then
        return 0   # protected branches are exempt
    fi

    if ! echo "$branch" | grep -qE "$pattern"; then
        echo "[pre-push] Branch '$branch' does not follow naming convention." >&2
        echo "[pre-push] Expected: <type>/<description>  e.g. feat/user-auth" >&2
        return 1
    fi
}

Enhancement 5 — Run Tests Before Push

Run your test suite before pushing — catch failures before CI does:

# Add to pre-push:
run_tests() {
    local test_cmd=""

    # Auto-detect test framework
    if [[ -f "package.json" ]]; then
        test_cmd="npm test"
    elif [[ -f "Makefile" ]] && grep -q "^test:" Makefile; then
        test_cmd="make test"
    elif [[ -f "pytest.ini" || -f "setup.py" ]]; then
        test_cmd="python -m pytest"
    fi

    [[ -z "$test_cmd" ]] && return 0

    echo "[pre-push] Running: $test_cmd"
    if $test_cmd; then
        echo "[pre-push] Tests passed"
    else
        echo "[pre-push] Tests failed — push aborted" >&2
        echo "[pre-push] Use --no-verify to skip (not recommended)" >&2
        return 1
    fi
}

Enhancement 6 — Hook Manager with Enable/Disable

Allow individual hooks to be toggled without deleting them:

# In install.sh — add enable/disable commands:
case "${1:-install}" in
    install)
        for hook in "${HOOKS[@]}"; do
            ln -sf "${SCRIPT_DIR}/${hook}" "${HOOKS_DIR}/${hook}"
        done
        ;;
    disable)
        hook="${2:?disable requires a hook name}"
        mv "${HOOKS_DIR}/${hook}" "${HOOKS_DIR}/${hook}.disabled"
        echo "Disabled: $hook"
        ;;
    enable)
        hook="${2:?enable requires a hook name}"
        mv "${HOOKS_DIR}/${hook}.disabled" "${HOOKS_DIR}/${hook}"
        echo "Enabled: $hook"
        ;;
    status)
        for hook in "${HOOKS[@]}"; do
            if [-L "${HOOKS_DIR}/${hook}"](<../../-L "${HOOKS_DIR}/${hook}".md>); then
                echo "  [ON]  $hook"
            elif [-f "${HOOKS_DIR}/${hook}.disabled"](<../../-f "${HOOKS_DIR}/${hook}.disabled">); then
                echo "  [OFF] $hook"
            else
                echo "  [--]  $hook (not installed)"
            fi
        done
        ;;
esac

testing | solution