-
Notifications
You must be signed in to change notification settings - Fork 0
feat(hooks): Add Git hooks installation system #248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| #!/bin/bash | ||
| # Post-commit hook to automatically push to remote and handle Git LFS | ||
| # This ensures local changes are immediately reflected on GitHub | ||
|
|
||
| # First, run Git LFS post-commit if available | ||
| command -v git-lfs >/dev/null 2>&1 && git lfs post-commit "$@" | ||
|
|
||
| # Color codes for output | ||
| RED='\033[0;31m' | ||
| GREEN='\033[0;32m' | ||
| YELLOW='\033[1;33m' | ||
| NC='\033[0m' # No Color | ||
|
|
||
| # Check if auto-push is disabled via environment variable | ||
| if [ "$DISABLE_AUTO_PUSH" = "true" ]; then | ||
| exit 0 | ||
| fi | ||
|
|
||
| echo -e "${YELLOW}π Auto-pushing to remote (Single Source of Truth)${NC}" | ||
|
|
||
| # Get current branch | ||
| BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null) | ||
| if [ -z "$BRANCH" ]; then | ||
| echo -e "${RED}β Not on a branch, skipping auto-push${NC}" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Check if we have a remote | ||
| if ! git remote | grep -q origin; then | ||
| echo -e "${RED}β No remote 'origin' found, skipping auto-push${NC}" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Push to remote with force-with-lease (safer than force) | ||
| if git push origin "$BRANCH" --force-with-lease 2>&1; then | ||
| echo -e "${GREEN}β Successfully pushed $BRANCH to origin${NC}" | ||
|
|
||
| # Update sync timestamp | ||
| SYNC_FILE=".github/sync-status/local-push.json" | ||
| mkdir -p "$(dirname "$SYNC_FILE")" | ||
| cat > "$SYNC_FILE" << EOF | ||
| { | ||
| "timestamp": "$(date -u +'%Y-%m-%d %H:%M:%S UTC')", | ||
| "branch": "$BRANCH", | ||
| "commit": "$(git rev-parse HEAD)", | ||
| "message": "$(git log -1 --pretty=%B)" | ||
| } | ||
| EOF | ||
| else | ||
| echo -e "${RED}β Failed to push to origin${NC}" | ||
| echo -e "${YELLOW}You may need to manually push with: git push origin $BRANCH --force${NC}" | ||
| fi | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,147 @@ | ||||||
| #!/bin/bash | ||||||
| # Pre-commit hook to enforce Claude Code standards and check for duplicate types | ||||||
| # Part of the hybrid workflow - enforces commit size limits and code quality | ||||||
|
|
||||||
| # Color codes | ||||||
| RED='\033[0;31m' | ||||||
| GREEN='\033[0;32m' | ||||||
| YELLOW='\033[1;33m' | ||||||
| BLUE='\033[0;34m' | ||||||
| NC='\033[0m' | ||||||
|
|
||||||
| # Always check Claude standards | ||||||
| echo -e "${BLUE}π€ Checking Claude Code commit standards...${NC}" | ||||||
|
|
||||||
| # 1. Check commit size | ||||||
| MAX_FILES=30 | ||||||
| MAX_LINES=800 | ||||||
|
|
||||||
| # Get current branch | ||||||
| CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) | ||||||
|
|
||||||
| # Count staged files | ||||||
| STAGED_FILES=$(git diff --cached --name-only | wc -l) | ||||||
| STAGED_LINES=$(git diff --cached --numstat | awk '{sum+=$1+$2} END {print sum}') | ||||||
|
|
||||||
| if [ $STAGED_FILES -gt $MAX_FILES ]; then | ||||||
| echo -e "${RED}β Too many files in commit: $STAGED_FILES (max: $MAX_FILES)${NC}" | ||||||
| echo -e "${YELLOW}π‘ Break this into smaller, incremental commits${NC}" | ||||||
| echo -e "${YELLOW} Run: git reset HEAD <file> to unstage some files${NC}" | ||||||
| exit 1 | ||||||
| fi | ||||||
|
|
||||||
| if [ ${STAGED_LINES:-0} -gt $MAX_LINES ]; then | ||||||
| echo -e "${RED}β Too many lines changed: $STAGED_LINES (max: $MAX_LINES)${NC}" | ||||||
| echo -e "${YELLOW}π‘ Make smaller, more focused changes${NC}" | ||||||
| echo -e "${YELLOW} Consider splitting this into multiple commits${NC}" | ||||||
| exit 1 | ||||||
| fi | ||||||
|
|
||||||
| # 2. Check for protected files | ||||||
| PROTECTED_PATTERNS=( | ||||||
| ".github/workflows/claude-commit-enforcement.yml" | ||||||
| ".github/CODEOWNERS" | ||||||
| "Package.resolved" | ||||||
| "project.pbxproj" | ||||||
| ) | ||||||
|
|
||||||
| for pattern in "${PROTECTED_PATTERNS[@]}"; do | ||||||
| if git diff --cached --name-only | grep -q "$pattern"; then | ||||||
| echo -e "${RED}β Attempting to modify protected file: $pattern${NC}" | ||||||
| echo -e "${YELLOW}π‘ These files should only be modified via PR review${NC}" | ||||||
| echo -e "${YELLOW} Create a PR to modify protected files${NC}" | ||||||
| exit 1 | ||||||
| fi | ||||||
| done | ||||||
|
|
||||||
| # 3. Check for tests when adding code | ||||||
| CODE_FILES=$(git diff --cached --name-only | grep -E '\.(swift|js|ts|py)$' | grep -v -E '(Test|Spec|test|spec)\.') | ||||||
| if [ -n "$CODE_FILES" ]; then | ||||||
| TEST_FILES=$(git diff --cached --name-only | grep -E '(Test|Spec|test|spec)\.') | ||||||
| if [ -z "$TEST_FILES" ]; then | ||||||
| echo -e "${YELLOW}β οΈ Adding code without tests${NC}" | ||||||
| echo -e "${YELLOW}π‘ Consider adding tests in the same commit${NC}" | ||||||
| fi | ||||||
| fi | ||||||
|
|
||||||
| # 4. Check time since last commit | ||||||
| LAST_COMMIT_TIME=$(git log -1 --format=%at 2>/dev/null || echo 0) | ||||||
| CURRENT_TIME=$(date +%s) | ||||||
| TIME_DIFF=$((CURRENT_TIME - LAST_COMMIT_TIME)) | ||||||
| THIRTY_MINUTES=1800 | ||||||
|
|
||||||
| if [ $LAST_COMMIT_TIME -ne 0 ] && [ $TIME_DIFF -gt $THIRTY_MINUTES ]; then | ||||||
| MINUTES=$((TIME_DIFF / 60)) | ||||||
| echo -e "${YELLOW}β οΈ It's been $MINUTES minutes since your last commit${NC}" | ||||||
| echo -e "${YELLOW}π‘ Remember to commit frequently (every ~30 minutes)${NC}" | ||||||
| fi | ||||||
|
|
||||||
| echo -e "${GREEN}β Claude standards check passed${NC}" | ||||||
|
|
||||||
| # Original duplicate type check | ||||||
| echo "Running duplicate type check..." | ||||||
|
|
||||||
| # Get staged Swift files | ||||||
| STAGED_SWIFT_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.swift$') | ||||||
|
|
||||||
| if [ -n "$STAGED_SWIFT_FILES" ]; then | ||||||
| # Check for new duplicate types in staged files | ||||||
| DUPLICATES="" | ||||||
|
||||||
| DUPLICATES="" | |
| DUPLICATES=() |
Copilot
AI
Jul 31, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The duplicate type check may fail when processing new files that don't exist in the current commit. Consider using git diff --cached to get staged content instead of git show ":$file".
| git show ":$file" 2>/dev/null | grep -E "^public (class|struct|enum|protocol|actor) " | \ | |
| git diff --cached -- "$file" 2>/dev/null | grep -E "^public (class|struct|enum|protocol|actor) " | \ |
Copilot
AI
Jul 31, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The grep pattern uses ^$file: but git grep -l returns only filenames, not filename: format. This grep filter will never match and won't properly exclude the current file from duplicate detection.
| existing=$(git grep -l "^public \(class\|struct\|enum\|protocol\|actor\) $type_name" -- '*.swift' | grep -v "^$file:") | |
| existing=$(git grep -l "^public \(class\|struct\|enum\|protocol\|actor\) $type_name" -- '*.swift' | awk -v current_file="$file" '$0 != current_file') |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| #!/bin/bash | ||
| # Install Git hooks for the hybrid workflow | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| # Color codes | ||
| GREEN='\033[0;32m' | ||
| YELLOW='\033[0;33m' | ||
| RED='\033[0;31m' | ||
| BLUE='\033[0;34m' | ||
| NC='\033[0m' | ||
|
|
||
| # Script directory | ||
| SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" | ||
| PROJECT_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )" | ||
| HOOKS_DIR="$PROJECT_ROOT/.git/hooks" | ||
| TEMPLATE_DIR="$PROJECT_ROOT/scripts/git-hooks" | ||
|
|
||
| # Function to install a hook | ||
| install_hook() { | ||
| local hook_name=$1 | ||
| local source_file="$TEMPLATE_DIR/$hook_name" | ||
| local dest_file="$HOOKS_DIR/$hook_name" | ||
|
|
||
| if [[ -f "$source_file" ]]; then | ||
| # Backup existing hook if it exists and is different | ||
| if [[ -f "$dest_file" ]] && ! diff -q "$source_file" "$dest_file" >/dev/null 2>&1; then | ||
| echo -e "${YELLOW}β οΈ Backing up existing $hook_name to $hook_name.backup${NC}" | ||
| cp "$dest_file" "$dest_file.backup" | ||
| fi | ||
|
|
||
| # Copy new hook | ||
| cp "$source_file" "$dest_file" | ||
| chmod +x "$dest_file" | ||
| echo -e "${GREEN}β Installed $hook_name hook${NC}" | ||
| else | ||
| echo -e "${YELLOW}β οΈ No template found for $hook_name${NC}" | ||
| fi | ||
| } | ||
|
|
||
| # Main installation | ||
| main() { | ||
| echo -e "${BLUE}π§ Installing Git hooks for hybrid workflow${NC}" | ||
| echo "" | ||
|
|
||
| # Check if we're in a git repository | ||
| if [[ ! -d "$HOOKS_DIR" ]]; then | ||
| echo -e "${RED}β Error: Not in a git repository${NC}" | ||
| echo "Run this script from the project root" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Create template directory if it doesn't exist | ||
| mkdir -p "$TEMPLATE_DIR" | ||
|
|
||
| # Install hooks | ||
| install_hook "pre-commit" | ||
| install_hook "post-commit" | ||
| install_hook "commit-msg" | ||
|
|
||
| echo "" | ||
| echo -e "${GREEN}β¨ Git hooks installed successfully!${NC}" | ||
| echo "" | ||
| echo -e "${BLUE}Hooks provide:${NC}" | ||
| echo " β’ Commit size enforcement (30 files, 800 lines)" | ||
| echo " β’ Conventional commit format checking" | ||
| echo " β’ Duplicate type prevention" | ||
| echo " β’ Auto-push to remote (post-commit)" | ||
| echo " β’ Protected file warnings" | ||
| echo "" | ||
| echo -e "${YELLOW}π‘ To bypass hooks temporarily:${NC}" | ||
| echo " git commit --no-verify" | ||
| echo "" | ||
| } | ||
|
|
||
| # Run main | ||
| main "$@" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sync file is created in the working directory but never committed or pushed. This could lead to untracked files accumulating in the repository. Consider using a different location outside the git repository or adding this to .gitignore.