Git Hooks¶
This document describes the git hooks setup for Forgather development.
Overview¶
Forgather uses git hooks to automatically format Python code before commits. This ensures consistent code style across the project without manual intervention.
Quick Start¶
That's it! The hooks will now run automatically on every commit.
What the Hooks Do¶
Pre-commit Hook¶
The pre-commit hook runs before each commit and:
- Finds staged Python files - Only processes files you're committing
- Applies formatting - Runs
isortandblackon those files - Re-stages changes - Automatically adds formatted files back to the commit
- Respects exclusions - Skips files matching patterns in
.formatting-ignore
Example workflow:
# You edit some Python files with messy formatting
vim src/forgather/ml/trainer/trainer.py
# Add them to staging
git add src/forgather/ml/trainer/trainer.py
# Commit - hook runs automatically
git commit -m "Fix trainer bug"
# Output shows:
# Running isort and black on staged Python files...
# Running isort...
# Running black...
# Files were reformatted by isort and/or black:
# - src/forgather/ml/trainer/trainer.py
# Re-staging modified files...
# Formatting complete!
Configuration¶
Formatting Exclusions (.formatting-ignore)¶
Some files should not be formatted (e.g., Jinja2 templates that look like Python). These are listed in .formatting-ignore at the repository root.
Format:
# Comments start with #
# One pattern per line
# Supports glob patterns
# Exclude specific file
modelsrc/templates/hf_causal.py
# Exclude directory pattern
modelsrc/templates/*.py
experimental/**/*.py
# Exclude by path component
**/test_fixtures/*.py
Common exclusions:
- Jinja2 templates with .py extension
- Generated code that shouldn't be reformatted
- Third-party code vendored into the repository
- Test fixtures that intentionally have specific formatting
Git Configuration¶
The setup script configures git to use .githooks/ instead of .git/hooks/:
This is a per-repository setting. Each developer runs the setup script once after cloning.
To verify your configuration:
Usage¶
Normal Development¶
Just commit as usual:
Bypassing the Hook¶
Sometimes you need to commit without formatting (rare, but possible):
When to bypass: - Committing deliberately unformatted code for testing - Emergency hotfixes where formatting might introduce bugs - Working with code that can't be formatted (syntax errors)
Note: Bypassing should be rare. CI/CD may reject unformatted code.
Troubleshooting¶
Hook doesn't run:
-
Check configuration:
-
If not set, run setup script:
-
Verify hook is executable:
Formatting fails:
-
Check if
isortandblackare installed: -
Install if missing:
-
Test manually:
File should be excluded but isn't:
- Check
.formatting-ignoresyntax - Ensure pattern matches the file path (relative to repository root)
- Test pattern matching:
Hook runs but doesn't format:
Check if the file is staged:
Updating Hooks¶
When hooks are updated in the repository:
No reinstall needed - hooks are tracked in .githooks/ and git is configured to use that directory.
For Maintainers¶
Modifying Hooks¶
-
Edit hooks in
.githooks/directory: -
Test changes locally:
-
Commit hook changes:
-
Push changes:
All developers will get the updated hook on their next git pull.
Adding New Hooks¶
-
Create new hook in
.githooks/: -
Add documentation to
.githooks/README.md -
Update this document if needed
-
Commit and push:
Hook Script Guidelines¶
When writing hooks:
- Use bash - More portable than shell-specific features
- Set -e - Exit on first error
- Provide clear output - Users should know what's happening
- Handle edge cases - Empty file lists, no Python files, etc.
- Exit with correct codes - 0 for success, non-zero for failure
- Document behavior - Add comments explaining complex logic
Example hook structure:
#!/usr/bin/env bash
# Description of what this hook does
set -e # Exit on error
# Get relevant files
FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.py$' || true)
if [ -z "$FILES" ]; then
# No Python files staged, exit successfully
exit 0
fi
# Do the work
echo "Processing files..."
for file in $FILES; do
# Process file
process_file "$file"
done
echo "Hook complete!"
exit 0
Alternative: Pre-commit Framework¶
For projects needing more sophisticated hook management, consider the pre-commit framework:
Pros:
- Large ecosystem of pre-built hooks
- Automatic updates via pre-commit autoupdate
- Standardized configuration
- Language-agnostic
Cons: - Requires Python package installation - Additional dependency to manage - Less transparent than bash scripts
Example config is provided in .pre-commit-config.yaml.example for reference.
See Also¶
.githooks/README.md- Quick reference for hooks.formatting-ignore- Exclusion patternsscripts/setup-hooks.sh- Setup script- Black documentation
- isort documentation