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:
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:
- Detects staged Java files
- Runs
mvn compile(includes Error Prone) - Blocks commit if violations found
- 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:
- Detects if
docs/folder changed in commit - Syncs docs to
wiki/docs/with MDX escaping - Smart build (only rebuilds if docs changed)
- 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
The Symlink Pattern
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:
- You run
git commit - Git looks for
.git/hooks/pre-commit - Finds symlink → follows to
.git-hooks/pre-commit-java.sh - Executes the version-controlled script
- 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:
- ✅ All developers run the same hook code
- ✅ Updates distributed via
git pull - ✅ No manual sync required
- ✅ Easy to review (in PRs like any other code)
- ✅ 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
-
Create the script in
.git-hooks/:vim .git-hooks/pre-push-tests.sh
chmod +x .git-hooks/pre-push-tests.sh -
Update
install.shto include it:# Add to the HOOKS array
HOOKS[pre-push]="pre-push-tests.sh"
DESCRIPTIONS[pre-push]="Run unit tests before push" -
Commit the changes:
git add .git-hooks/
git commit -m "Add pre-push hook for unit tests" -
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.shconfiguredwiki/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:
| Stage | Tool | Speed | Purpose |
|---|---|---|---|
| Local (Pre-commit) | Error Prone | ~10s | Fast feedback on staged files |
| Local (Post-commit) | Wiki deploy | ~30s | Keep docs updated |
| CI/CD | Full build + tests | ~5min | Comprehensive validation |
Philosophy:
- Hooks catch obvious problems fast (locally)
- CI catches comprehensive problems (remotely)
- Both are important!
Best Practices
Do's ✅
-
Install hooks on every workstation
./.git-hooks/install.sh -
Keep hooks fast (
<30 seconds)- Pre-commit only checks staged files
- Uses incremental compilation
-
Provide clear error messages
- Hooks show what failed and how to fix
-
Make hooks optional for some scenarios
- Granular install/uninstall by hook name
--no-verifyavailable for emergencies
-
Version control hook logic
- Hooks in
.git-hooks/are committed - Updates distributed via
git pull
- Hooks in
Don'ts ❌
-
Don't put hooks directly in
.git/hooks/- Not version controlled
- Won't be shared with team
-
Don't make hooks slow (
>60 seconds)- Developers will bypass them
- Use incremental checks only
-
Don't run full test suite in pre-commit
- Too slow (use pre-push instead)
- Pre-commit should be
<30s
-
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:
- Developer Playbook - General development workflow
- Static Analysis - Error Prone details
- Wiki Workflow - Documentation deployment
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:
- Install hooks:
./.git-hooks/install.sh - Start committing code
- Let hooks catch bugs automatically
- Enjoy faster development workflow!