9.8 KiB
sc-pre-commit-setup-2: Streamlined Pre-commit Framework Setup
Purpose: Set up pre-commit framework with repository-specific hooks and intelligent auto-restaging behavior for seamless development workflow.
Step 1: Verify Pre-commit Installation
Before configuring hooks, ensure pre-commit is properly installed:
# Check if pre-commit is available
if ! command -v pre-commit >/dev/null; then
echo "Installing pre-commit via Homebrew..."
brew install pre-commit
fi
# Verify installation
pre-commit --version
# Install git hooks (creates .git/hooks/pre-commit)
pre-commit install
Expected Output: pre-commit installed at .git/hooks/pre-commit
Step 2: Repository Analysis
Use GitHub CLI to detect repository technologies and appropriate hooks:
# Verify required tools are installed
if ! command -v gh &> /dev/null || ! command -v jq &> /dev/null; then
echo "Error: Missing dependencies. Please install 'gh' and 'jq'" >&2
exit 1
fi
# Get repository languages and suggest appropriate hooks
REPO_LANGS=$(gh repo view --json languages | jq -r '.languages | map(.node.name) | join(" ")')
echo "Detected languages: $REPO_LANGS"
echo ""
echo "Recommended hooks based on detected languages:"
# Universal (always include)
echo " - pre-commit/pre-commit-hooks (file hygiene, always include)"
# Python
if echo "$REPO_LANGS" | grep -q "Python"; then
echo " - astral-sh/ruff-pre-commit (Python linting & formatting)"
fi
# JavaScript/TypeScript
if echo "$REPO_LANGS" | grep -qE "JavaScript|TypeScript"; then
echo " - pre-commit/mirrors-prettier (JS/TS formatting)"
echo " - pre-commit/mirrors-eslint (JS/TS linting)"
fi
# Ruby
if echo "$REPO_LANGS" | grep -q "Ruby"; then
echo " - pre-commit/mirrors-rubocop (Ruby linting & formatting)"
fi
# Shell
if echo "$REPO_LANGS" | grep -q "Shell"; then
echo " - koalaman/shellcheck-precommit (Shell linting)"
echo " - scop/pre-commit-shfmt (Shell formatting)"
fi
echo ""
echo "Configuration files present:"
find . -maxdepth 2 \( -name ".git" -o -name "node_modules" \) -prune -o -type f \( -name "package.json" -o -name "requirements.txt" -o -name "Gemfile" -o -name "pyproject.toml" -o -name "tsconfig.json" -o -name ".pre-commit-config.yaml" \) -print
Select appropriate hook repositories from the recommendations above.
Step 3: Create Configuration
⚠️ Important: The version numbers in the examples below are illustrative only and may be outdated. Always fetch the latest versions using the commands shown at the end of this section before creating your configuration.
Generate .pre-commit-config.yaml based on detected languages:
# Pre-commit configuration for [REPOSITORY_NAME]
repos:
# Core file hygiene (always include)
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0 # Use latest from: gh api repos/pre-commit/pre-commit-hooks/releases/latest
hooks:
- id: trailing-whitespace
exclude: '\.md$'
- id: end-of-file-fixer
- id: check-merge-conflict
- id: check-added-large-files
args: [--maxkb=1024]
- id: check-yaml
- id: check-json
- id: detect-private-key
# Language-specific hooks (add based on repository analysis)
# Example for Python (modern approach using Ruff):
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.4
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
# Example for Ruby:
- repo: https://github.com/pre-commit/mirrors-rubocop
rev: v1.70.0
hooks:
- id: rubocop
args: [--auto-correct]
# Example for Shell scripts:
- repo: https://github.com/koalaman/shellcheck-precommit
rev: v0.10.0
hooks:
- id: shellcheck
args: [--severity=warning]
- repo: https://github.com/scop/pre-commit-shfmt
rev: v3.12.0-2
hooks:
- id: shfmt
args: [-w, -i, "2"] # 2-space indentation
Get latest versions:
# Core hooks
gh api repos/pre-commit/pre-commit-hooks/releases/latest | jq -r '.tag_name'
# Python
gh api repos/astral-sh/ruff-pre-commit/releases/latest | jq -r '.tag_name'
# Ruby
gh api repos/pre-commit/mirrors-rubocop/tags | jq -r '.[0].name'
# Shell
gh api repos/koalaman/shellcheck-precommit/tags | jq -r '.[0].name'
gh api repos/scop/pre-commit-shfmt/tags | jq -r '.[0].name'
Step 4: Test Configuration
Validate and test the configuration incrementally:
# Validate YAML syntax
pre-commit validate-config
# Test core hooks first
pre-commit run trailing-whitespace --all-files
pre-commit run end-of-file-fixer --all-files
# Run all hooks
pre-commit run --all-files
If hooks modify files: They will exit with code 1 and require manual re-staging.
Step 5: Add Auto-restaging Wrapper (Optional)
Pre-commit does not have built-in auto-restaging. If your hooks modify files (formatters, fixers), add a custom wrapper to preserve staging intent.
Detection: Your config contains file-modifying hooks like:
shfmt,black,prettier(formatters)trailing-whitespace,end-of-file-fixer(fixers)- Hooks with
--fixarguments
Implementation:
-
Ensure
pre-commit installhas been run (from Step 1). This generates.git/hooks/pre-commitwhich we'll now enhance with auto-restaging capability. -
Extract system-specific values from the generated hook:
# Backup the generated hook
cp .git/hooks/pre-commit .git/hooks/pre-commit.original
# Extract the templated values (these are system-specific)
grep "INSTALL_PYTHON=" .git/hooks/pre-commit.original
grep "ARGS=(" .git/hooks/pre-commit.original
- Create the staging-aware wrapper using YOUR extracted values:
#!/usr/bin/env bash
# Staging-aware pre-commit wrapper
# Preserves original staging intent while using pre-commit framework
# start templated (REPLACE these lines with YOUR extracted values from step 2)
INSTALL_PYTHON=/usr/local/opt/pre-commit/libexec/bin/python3.13
ARGS=(hook-impl --config=.pre-commit-config.yaml --hook-type=pre-commit)
# end templated
HERE="$(cd "$(dirname "$0")" && pwd)"
ARGS+=(--hook-dir "$HERE" -- "$@")
# Check if this is an initial commit
if git rev-parse --verify HEAD >/dev/null 2>&1; then
against=HEAD
else
against=$(git hash-object -t tree /dev/null)
fi
# Store originally staged files BEFORE pre-commit runs
original_staged_files=$(git diff --cached --name-only --diff-filter=ACDMRT "$against")
# Run original pre-commit framework logic
if [ -x "$INSTALL_PYTHON" ]; then
"$INSTALL_PYTHON" -mpre_commit "${ARGS[@]}"
result=$?
elif command -v pre-commit >/dev/null; then
pre-commit "${ARGS[@]}"
result=$?
else
echo '`pre-commit` not found. Did you forget to activate your virtualenv?' 1>&2
exit 1
fi
# If pre-commit failed due to file modifications (exit code 1),
# restore proper staging to only include originally staged files.
# Exit codes: 0=success, 1=failures/fixes applied, >1=actual errors
if [ $result -eq 1 ] && [ -n "$original_staged_files" ]; then
# Reset staging area completely
git reset HEAD --quiet
# Re-stage only originally staged files (which now include hook modifications)
echo "$original_staged_files" | while IFS= read -r file; do
git add "$file"
done
echo "Pre-commit: Auto-formatted files and preserved original staging intent"
exit 0
fi
# Pass through original exit code for other scenarios
exit $result
- Write the complete wrapper to
.git/hooks/pre-commitand make it executable:
# Copy your complete wrapper script to .git/hooks/pre-commit
# (After editing the templated section with your extracted values)
chmod +x .git/hooks/pre-commit
Step 6: Final Verification
Test the complete setup:
# Create test scenario
echo "test content " > test-format.txt # Trailing space
git add test-format.txt
# Test commit (should auto-format and succeed with wrapper, or fail without)
git commit -m "Test pre-commit setup"
# Cleanup
git restore --staged test-format.txt
rm test-format.txt
Common Hook Repositories
Research latest versions using GitHub CLI:
# Core hooks
gh api repos/pre-commit/pre-commit-hooks/releases/latest # Basic file checks
# Python (2025 recommended: Ruff replaces Black, isort, Flake8, pyupgrade)
gh api repos/astral-sh/ruff-pre-commit/releases/latest # Python linting & formatting
gh api repos/RobertCraigie/pyright-python/releases/latest # Python type checking
# Ruby
gh api repos/pre-commit/mirrors-rubocop/tags | jq -r '.[0].name' # Ruby linting & formatting
# JavaScript/TypeScript
gh api repos/pre-commit/mirrors-prettier/tags | jq -r '.[0].name' # JS/TS formatting
gh api repos/pre-commit/mirrors-eslint/tags | jq -r '.[0].name' # JS/TS linting
# Shell
gh api repos/koalaman/shellcheck-precommit/tags | jq -r '.[0].name' # Shell linting
gh api repos/scop/pre-commit-shfmt/tags | jq -r '.[0].name' # Shell formatting
# Security & General
gh api repos/zricethezav/gitleaks/releases/latest # Secret detection
gh api repos/codespell-project/codespell/releases/latest # Typo detection
Success Criteria
- ✅
pre-commit --versionworks - ✅
pre-commit installcompletes without errors - ✅
pre-commit validate-configpasses - ✅
pre-commit run --all-filesexecutes (may modify files) - ✅ Repository-specific hooks included for detected languages
- ✅ Auto-restaging wrapper installed if needed
- ✅ Test commit workflow succeeds
Troubleshooting
Config validation fails: Check YAML syntax and hook repository accessibility
Hooks fail to run: Verify repository URLs and versions with pre-commit try-repo <url>
Performance issues: Add file exclusions or remove problematic hooks
Staging issues: Verify auto-restaging wrapper is correctly installed and executable
Maintenance
# Update hook versions
pre-commit autoupdate
# Run hooks manually
pre-commit run --all-files
# Update specific hook
pre-commit autoupdate --repo https://github.com/astral-sh/ruff-pre-commit