Skip to main content

Git Hooks Guide

Overview

This guide covers the Git hooks used in the Construction Code Expert project for automated code quality checks and documentation deployment.

Related Issues:

  • #203 - Static Code Analysis Integration (Phase 2)
  • #180 - Wiki Implementation

Quick Start

# Install all hooks (recommended for most developers)
./.git-hooks/install.sh

# Install only specific hooks
./.git-hooks/install.sh pre-commit # Error Prone only
./.git-hooks/install.sh post-commit # Wiki sync only

Available Hooks

1. Pre-commit Hook (Error Prone)

Purpose: Catch bugs before they're committed

File: .git-hooks/pre-commit-java.sh

What it does:

  1. Detects staged Java files
  2. Runs mvn compile (includes Error Prone)
  3. Blocks commit if violations found
  4. Shows clear error messages

When to use:

  • ✅ Always (unless you're debugging the hook itself)
  • ✅ Prevents bad code from entering version control
  • ✅ Catches bugs at the earliest possible moment

When to bypass:

  • 🟡 Debugging the hook itself
  • 🟡 Emergency hotfix (very rare - use --no-verify)
  • ❌ Never bypass just because you're in a hurry!

Example output:

$ git commit -m "Add feature"
🔍 Running Error Prone on staged Java files...

Staged files:
- src/main/java/org/codetricks/MyService.java

[ERROR] ImpossibleNullComparison detected at line 42
❌ Error Prone found issues in staged Java files.

2. Post-commit Hook (Wiki Deployment)

Purpose: Automatically keep wiki up-to-date when docs change

File: .git-hooks/post-commit-wiki.sh

What it does:

  1. Detects if docs/ folder changed in commit
  2. Syncs docs to wiki/docs/ with MDX escaping
  3. Smart build (only rebuilds if docs changed)
  4. Deploys to Firebase Hosting automatically

When to use:

  • ✅ If you frequently edit documentation
  • ✅ If you want instant wiki updates
  • 🟡 Optional for backend-only developers

When to skip:

  • If you rarely edit docs
  • If you prefer manual wiki deployment
  • If you're working offline

Example output:

$ git commit -m "Update architecture docs"

📚 Documentation changes detected in commit
🔄 Syncing docs to wiki...
📄 Syncing docs to wiki (with MDX auto-escape)...
✅ Docs synced and processed
🔨 Building Docusaurus site...
✅ Build complete
📤 Deploying to Firebase (hosting only)...
✅ Smart deployment complete!
🌐 Wiki URL: https://construction-code-expert-dev-wiki.web.app

How Git Hooks Work

Git only executes hooks from .git/hooks/, but that directory is not version controlled. The symlink pattern solves this:

Version Controlled (committed):
.git-hooks/
├── pre-commit-java.sh ← Actual script (shared via Git)
├── post-commit-wiki.sh ← Actual script (shared via Git)
└── install.sh ← Creates symlinks

NOT Version Controlled (local):
.git/hooks/
├── pre-commit → ../../.git-hooks/pre-commit-java.sh (symlink)
└── post-commit → ../../.git-hooks/post-commit-wiki.sh (symlink)

Flow when you commit:

  1. You run git commit
  2. Git looks for .git/hooks/pre-commit
  3. Finds symlink → follows to .git-hooks/pre-commit-java.sh
  4. Executes the version-controlled script
  5. Script passes/fails → commit proceeds/blocked

Why This Pattern?

This is the industry standard used by:

  • React - Code formatting and linting
  • Node.js - Pre-commit checks
  • Kubernetes - Code generation verification
  • Prettier - Auto-formatting
  • Husky - Actually automates this exact pattern!

Benefits:

  1. ✅ All developers run the same hook code
  2. ✅ Updates distributed via git pull
  3. ✅ No manual sync required
  4. ✅ Easy to review (in PRs like any other code)
  5. ✅ Easy to customize per-hook

Installation Details

First-Time Setup

Every developer needs to run this once after cloning:

cd /path/to/construction-code-expert
./.git-hooks/install.sh

What Gets Installed

The installer creates symlinks:

# These are created:
.git/hooks/pre-commit → ../../.git-hooks/pre-commit-java.sh
.git/hooks/post-commit → ../../.git-hooks/post-commit-wiki.sh

Selective Installation

Install only what you need:

# Backend developers might want:
./.git-hooks/install.sh pre-commit

# Documentation maintainers might want:
./.git-hooks/install.sh post-commit

# Full-stack developers probably want:
./.git-hooks/install.sh # Both

Customizing Hooks

Modifying Hook Behavior

Edit the scripts in .git-hooks/:

# Edit pre-commit behavior
vim .git-hooks/pre-commit-java.sh

# Edit post-commit behavior
vim .git-hooks/post-commit-wiki.sh

# Commit your changes - all developers will get them!
git add .git-hooks/
git commit -m "Update pre-commit hook to skip tests"

After git pull, the updated logic runs automatically (symlinks make this seamless).

Adding New Hooks

  1. Create the script in .git-hooks/:

    vim .git-hooks/pre-push-tests.sh
    chmod +x .git-hooks/pre-push-tests.sh
  2. Update install.sh to include it:

    # Add to the HOOKS array
    HOOKS[pre-push]="pre-push-tests.sh"
    DESCRIPTIONS[pre-push]="Run unit tests before push"
  3. Commit the changes:

    git add .git-hooks/
    git commit -m "Add pre-push hook for unit tests"
  4. Reinstall hooks:

    ./.git-hooks/install.sh

Troubleshooting

Hook Not Running

Symptom: Commits go through without hook execution

Solutions:

# 1. Check if hooks are installed
ls -la .git/hooks/pre-commit .git/hooks/post-commit

# 2. Verify they're symlinks (should show ->)
file .git/hooks/pre-commit

# 3. Reinstall
./.git-hooks/install.sh

# 4. Check permissions
ls -l .git-hooks/*.sh # Should all be executable (-rwxr-xr-x)
chmod +x .git-hooks/*.sh

Hook Fails Unexpectedly

Test manually:

# Run pre-commit hook manually
./.git-hooks/pre-commit-java.sh

# Run post-commit hook manually
./.git-hooks/post-commit-wiki.sh

JAVA_HOME Issues

The pre-commit hook sets JAVA_HOME for the dev container:

export JAVA_HOME=/usr/lib/jvm/temurin-23-jdk-arm64

If you're on a different environment, edit .git-hooks/pre-commit-java.sh.

Wiki Deploy Fails

The post-commit hook requires:

  • Firebase CLI installed and authenticated
  • env/dev/setvars.sh configured
  • wiki/ directory initialized

Fix:

# Initialize wiki
./cli/sdlc/wiki/init-docusaurus.sh

# Test deploy manually
./cli/sdlc/wiki/deploy-to-firebase-smart.sh

Bypassing Hooks

When It's Acceptable

  • 🟢 Temporary local commits you'll squash later
  • 🟡 Emergency hotfixes (rare)
  • 🟡 Debugging the hooks themselves

When It's NOT Acceptable

  • ❌ "I'm in a hurry" - Fix the issue properly!
  • ❌ "This is too annoying" - The hook caught a real problem
  • ❌ "CI will catch it" - Hooks exist to catch issues earlier

How to Bypass

# Bypass ALL hooks for this commit
git commit --no-verify -m "Your message"

# Or use the shorthand
git commit -n -m "Your message"

Note: --no-verify bypasses all hooks (pre-commit, commit-msg, pre-push, etc.)


Integration with CI/CD

Git hooks are your first line of defense. They complement CI/CD:

StageToolSpeedPurpose
Local (Pre-commit)Error Prone~10sFast feedback on staged files
Local (Post-commit)Wiki deploy~30sKeep docs updated
CI/CDFull build + tests~5minComprehensive validation

Philosophy:

  • Hooks catch obvious problems fast (locally)
  • CI catches comprehensive problems (remotely)
  • Both are important!

Best Practices

Do's ✅

  1. Install hooks on every workstation

    ./.git-hooks/install.sh
  2. Keep hooks fast (<30 seconds)

    • Pre-commit only checks staged files
    • Uses incremental compilation
  3. Provide clear error messages

    • Hooks show what failed and how to fix
  4. Make hooks optional for some scenarios

    • Granular install/uninstall by hook name
    • --no-verify available for emergencies
  5. Version control hook logic

    • Hooks in .git-hooks/ are committed
    • Updates distributed via git pull

Don'ts ❌

  1. Don't put hooks directly in .git/hooks/

    • Not version controlled
    • Won't be shared with team
  2. Don't make hooks slow (>60 seconds)

    • Developers will bypass them
    • Use incremental checks only
  3. Don't run full test suite in pre-commit

    • Too slow (use pre-push instead)
    • Pre-commit should be <30s
  4. Don't skip hook failures habitually

    • Defeats the purpose
    • Pushes problems to CI (slower feedback)

Adding Hooks to CI/CD

The hooks should mirror your CI pipeline. Example GitHub Actions:

name: Static Analysis
on: [push, pull_request]

jobs:
error-prone:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up JDK 23
uses: actions/setup-java@v4
with:
java-version: '23'
distribution: 'temurin'

- name: Run Error Prone
run: |
export JAVA_HOME=$JAVA_HOME_23_X64
mvn clean compile -DskipTests

This ensures:

  • ✅ Pre-commit hook matches CI (same Error Prone config)
  • ✅ If hook passes locally, CI should pass too
  • ✅ Consistent experience across local/remote

Advanced Topics

Multiple Pre-commit Checks

You can chain multiple checks in pre-commit-java.sh:

# Error Prone (current)
mvn compile -DskipTests

# Add Checkstyle (future)
mvn checkstyle:check

# Add quick tests (future)
mvn test -Dtest=QuickTests

Conditional Hook Execution

Skip hooks based on branch or environment:

# In pre-commit-java.sh
CURRENT_BRANCH=$(git branch --show-current)

if [[ "$CURRENT_BRANCH" == "experimental" ]]; then
echo "ℹ️ Skipping Error Prone on experimental branch"
exit 0
fi

Hook Performance Optimization

Current optimization:

  • Only compiles staged files (incremental)
  • Maven's incremental compilation
  • Skips tests (-DskipTests)

Future ideas:

  • Cache compilation results
  • Parallel analysis
  • Smart detection (only run if Java files changed)

Migration from Old Hooks

If you had hooks directly in .git/hooks/:

# Old hooks are automatically backed up
# Located at: .git/hooks/<hook-name>.backup

# To restore old behavior (not recommended):
mv .git/hooks/pre-commit.backup .git/hooks/pre-commit

FAQ

Q: Do I have to install hooks?

A: Not mandatory, but highly recommended:

  • ✅ Catches bugs earlier (before commit vs. after push)
  • ✅ Faster feedback loop
  • ✅ Prevents embarrassing "oops, forgot to compile" commits

Q: Can I modify hooks locally without committing?

A: Not with symlinks. To experiment:

# 1. Copy hook to .git/hooks/ (breaks symlink)
cp .git-hooks/pre-commit-java.sh .git/hooks/pre-commit

# 2. Edit the copy
vim .git/hooks/pre-commit

# 3. Test your changes

# 4. When happy, update the source and reinstall symlink
vim .git-hooks/pre-commit-java.sh
./.git-hooks/install.sh pre-commit

Q: What if I work on multiple branches?

Hooks work per repository, not per branch. They run on all branches.

To skip on certain branches, add logic to the hook script (see "Conditional Hook Execution" above).

Q: Are hooks required for CI/CD to pass?

No, but they mirror CI checks:

  • Hooks = fast feedback (10s locally)
  • CI = comprehensive feedback (5min remotely)

If hooks pass locally, CI should pass too (assuming same config).

Q: What about performance impact?

Pre-commit (Error Prone):

  • ~10-20 seconds for typical commit
  • Only compiles changed files
  • Acceptable for the bug prevention value

Post-commit (Wiki):

  • ~30-60 seconds if docs changed
  • Runs AFTER commit completes (non-blocking)
  • Smart build (skips if docs unchanged)

Q: Can I use these with GUI Git clients?

Yes! GUI clients (GitKraken, SourceTree, GitHub Desktop, IntelliJ) all support Git hooks.

The hooks are transparent - just install once with the script.


Hook Development

Testing Hook Changes

# 1. Make changes to hook
vim .git-hooks/pre-commit-java.sh

# 2. Test manually (without committing)
./.git-hooks/pre-commit-java.sh

# 3. Test with fake staged files
git add some-file.java
./.git-hooks/pre-commit-java.sh
git restore --staged some-file.java

# 4. Once working, commit the updated hook
git add .git-hooks/pre-commit-java.sh
git commit -m "Improve pre-commit hook performance"

Debugging Hooks

# Add debug output to hook
set -x # Enable bash debug mode

# Or add explicit logging
echo "DEBUG: STAGED_FILES = $STAGED_FILES"

Hook Exit Codes

  • 0 = Success, allow operation to proceed
  • 1 (or non-zero) = Failure, block operation
# Example
if [ "$VIOLATIONS_FOUND" = true ]; then
echo "❌ Violations found"
exit 1 # Block commit
fi

echo "✅ All checks passed"
exit 0 # Allow commit

Comparison with Other Solutions

Husky (npm package)

Husky:

  • ✅ Automates symlink creation
  • ✅ Popular in Node.js ecosystem
  • ❌ Adds npm dependency
  • ❌ Overkill for simple projects

Our approach:

  • ✅ Zero dependencies (pure bash)
  • ✅ Same symlink pattern Husky uses
  • ✅ Explicit and educational
  • ✅ Full control

Lefthook

Lefthook:

  • YAML configuration for hooks
  • Parallel execution
  • Language-agnostic

Our approach:

  • Simpler (just bash scripts)
  • Better for small teams
  • Easy to understand and modify

Examples

Example 1: Install Everything

$ ./.git-hooks/install.sh

🔧 Installing all Git hooks...

✅ Installed pre-commit hook (Error Prone static analysis for Java)
.git/hooks/pre-commit -> .git-hooks/pre-commit-java.sh

✅ Installed post-commit hook (Wiki documentation sync and deploy)
.git/hooks/post-commit -> .git-hooks/post-commit-wiki.sh

✅ Successfully installed 2 hook(s)!

Example 2: Install Only Pre-commit

$ ./.git-hooks/install.sh pre-commit

🔧 Installing specified Git hooks: pre-commit

✅ Installed pre-commit hook (Error Prone static analysis for Java)
.git/hooks/pre-commit -> .git-hooks/pre-commit-java.sh

✅ Successfully installed 1 hook(s)!

Example 3: Uninstall Wiki Hook

$ ./.git-hooks/uninstall.sh post-commit

🔧 Uninstalling specified Git hooks: post-commit

✅ Removed post-commit hook (Wiki sync and deploy)

✅ Uninstalled 1 hook(s)

Example 4: Commit with Pre-commit Hook Active

$ git commit -m "Add new feature"

🔍 Running Error Prone on staged Java files...

Staged files:
- src/main/java/org/codetricks/NewFeature.java

[INFO] BUILD SUCCESS

✅ Error Prone checks passed! Proceeding with commit.
[web-ui-ngm3 abc123] Add new feature
1 file changed, 10 insertions(+)

See Also

  • Local README: .git-hooks/README.md - Quick reference
  • Hook Scripts:
    • .git-hooks/pre-commit-java.sh - Error Prone hook
    • .git-hooks/post-commit-wiki.sh - Wiki sync hook
  • Related Playbooks:

Summary

Git hooks in this project:

  • ✅ Use industry-standard symlink pattern
  • ✅ Version controlled and shared across team
  • ✅ Granular install/uninstall by hook name
  • ✅ Fast feedback (<30s for pre-commit)
  • ✅ Well-documented and easy to customize

Next steps:

  1. Install hooks: ./.git-hooks/install.sh
  2. Start committing code
  3. Let hooks catch bugs automatically
  4. Enjoy faster development workflow!