Files
gh-rknall-claude-skills-sta…/git-hooks-guide.md
2025-11-30 08:52:07 +08:00

736 lines
17 KiB
Markdown

# Git Hooks and Validation Scripts Guide
This guide provides comprehensive examples and guidelines for git hooks and validation scripts used in GitLab Stack projects.
## Overview
Git hooks are scripts that run automatically at specific points in the git workflow. For GitLab Stack projects, we use hooks to ensure validation before commits.
## Directory Structure
```
project-name/
├── .git/
│ └── hooks/
│ └── pre-commit # Installed hook
└── scripts/
├── pre-commit # Source hook script
├── validate-stack.sh # Full validation
└── setup-hooks.sh # Hook installer
```
## Core Scripts
### 1. scripts/pre-commit
The pre-commit hook runs before each commit to validate the stack.
**Location**: `scripts/pre-commit`
**Installed to**: `.git/hooks/pre-commit`
**When it runs**: Before `git commit`
```bash
#!/usr/bin/env bash
#
# Pre-commit hook for GitLab Stack validation
# Prevents commits that violate stack patterns
#
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo "========================================="
echo "Pre-commit validation"
echo "========================================="
echo
ERRORS=0
# Check 1: Secrets in staged files
echo "1. Checking for secrets in staged files..."
if git diff --cached --name-only | grep -qE "secrets/.*[^.gitkeep]|\.env$"; then
echo -e "${RED}✗ ERROR: Attempting to commit secrets or .env file!${NC}"
echo " Secrets should NEVER be committed to git."
echo " Files detected:"
git diff --cached --name-only | grep -E "secrets/.*[^.gitkeep]|\.env$" | sed 's/^/ /'
((ERRORS++))
else
echo -e "${GREEN}✓ No secrets in staged files${NC}"
fi
echo
# Check 2: Root-owned files
echo "2. Checking for root-owned files..."
if find . -user root -not -path "./.git/*" 2>/dev/null | grep -q .; then
echo -e "${RED}✗ ERROR: Root-owned files detected!${NC}"
echo " All files should be owned by the user running Docker."
echo " Files detected:"
find . -user root -not -path "./.git/*" 2>/dev/null | sed 's/^/ /'
echo
echo " Fix with: sudo chown -R \$USER:\$USER ."
((ERRORS++))
else
echo -e "${GREEN}✓ No root-owned files${NC}"
fi
echo
# Check 3: Secrets in file content (basic check)
echo "3. Checking for hardcoded secrets in code..."
SUSPICIOUS_PATTERNS=(
"password\s*=\s*['\"][^'\"]+['\"]"
"api[_-]?key\s*=\s*['\"][^'\"]+['\"]"
"secret\s*=\s*['\"][^'\"]+['\"]"
"token\s*=\s*['\"][^'\"]+['\"]"
)
FOUND_SECRETS=false
for pattern in "${SUSPICIOUS_PATTERNS[@]}"; do
if git diff --cached | grep -iE "$pattern" | grep -v "\.example" | grep -q .; then
if [ "$FOUND_SECRETS" = false ]; then
echo -e "${YELLOW}⚠ WARNING: Possible hardcoded secrets detected:${NC}"
FOUND_SECRETS=true
fi
git diff --cached | grep -iE "$pattern" | grep -v "\.example" | sed 's/^/ /'
fi
done
if [ "$FOUND_SECRETS" = true ]; then
echo
echo " Review these carefully. Use environment variables or Docker secrets instead."
echo " If these are false positives, you can proceed."
else
echo -e "${GREEN}✓ No obvious hardcoded secrets${NC}"
fi
echo
# Check 4: Full stack validation (if available)
if [ -x "./scripts/validate-stack.sh" ]; then
echo "4. Running full stack validation..."
if ! ./scripts/validate-stack.sh; then
echo -e "${RED}✗ ERROR: Stack validation failed!${NC}"
echo " Fix all issues before committing."
echo " Or use 'git commit --no-verify' to skip (NOT recommended)."
((ERRORS++))
else
echo -e "${GREEN}✓ Stack validation passed${NC}"
fi
else
echo -e "${YELLOW}⚠ Skipping stack validation (./scripts/validate-stack.sh not found)${NC}"
fi
echo
# Final result
echo "========================================="
if [ $ERRORS -gt 0 ]; then
echo -e "${RED}Pre-commit validation FAILED with $ERRORS error(s)${NC}"
echo "========================================="
echo
echo "To skip this validation (NOT recommended):"
echo " git commit --no-verify"
exit 1
fi
echo -e "${GREEN}Pre-commit validation PASSED!${NC}"
echo "========================================="
exit 0
```
**Usage**:
```bash
# Automatic - runs on every commit
git commit -m "message"
# Skip validation (emergency only)
git commit --no-verify -m "message"
```
---
### 2. scripts/validate-stack.sh
Comprehensive validation script that runs all validators.
**Location**: `scripts/validate-stack.sh`
**When to run**: Before deployment, in CI/CD, or manually
```bash
#!/usr/bin/env bash
#
# Full GitLab Stack validation
# Runs all validators to ensure stack compliance
#
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo
echo -e "${BLUE}========================================"
echo "GitLab Stack Validation"
echo "========================================${NC}"
echo
ERRORS=0
WARNINGS=0
# Check if we're in a stack directory
if [ ! -f "docker-compose.yml" ]; then
echo -e "${RED}✗ ERROR: docker-compose.yml not found!${NC}"
echo " Are you in a stack directory?"
exit 1
fi
# Validation 1: Stack Validator
echo -e "${BLUE}[1/4] Running stack-validator...${NC}"
if command -v claude-code >/dev/null 2>&1; then
if claude-code run stack-validator 2>&1 | tee /tmp/stack-validator.log; then
echo -e "${GREEN}✓ Stack validation passed${NC}"
else
echo -e "${RED}✗ Stack validation failed${NC}"
echo " Review output above for details"
((ERRORS++))
fi
else
echo -e "${YELLOW}⚠ Claude Code not available, skipping stack-validator${NC}"
((WARNINGS++))
fi
echo
# Validation 2: Secrets Manager
echo -e "${BLUE}[2/4] Running secrets-manager validation...${NC}"
if command -v claude-code >/dev/null 2>&1; then
if claude-code run secrets-manager --validate 2>&1 | tee /tmp/secrets-manager.log; then
echo -e "${GREEN}✓ Secrets validation passed${NC}"
else
echo -e "${RED}✗ Secrets validation failed${NC}"
echo " Review output above for details"
((ERRORS++))
fi
else
echo -e "${YELLOW}⚠ Claude Code not available, running basic secrets check${NC}"
# Basic secrets check without Claude Code
if find secrets/ -type f ! -name ".gitkeep" 2>/dev/null | grep -q .; then
if grep -r "DOCKER_SECRET" docker-compose.yml >/dev/null 2>&1; then
echo -e "${GREEN}✓ Basic secrets check passed${NC}"
else
echo -e "${YELLOW}⚠ Secrets files found but not referenced in docker-compose.yml${NC}"
((WARNINGS++))
fi
else
echo -e "${GREEN}✓ No secrets configured${NC}"
fi
fi
echo
# Validation 3: Docker Validator
echo -e "${BLUE}[3/4] Running docker-validation...${NC}"
if command -v claude-code >/dev/null 2>&1; then
if claude-code run docker-validation 2>&1 | tee /tmp/docker-validation.log; then
echo -e "${GREEN}✓ Docker validation passed${NC}"
else
echo -e "${RED}✗ Docker validation failed${NC}"
echo " Review output above for details"
((ERRORS++))
fi
else
echo -e "${YELLOW}⚠ Claude Code not available, running basic Docker checks${NC}"
# Basic docker-compose syntax check
if docker compose config >/dev/null 2>&1; then
echo -e "${GREEN}✓ docker-compose.yml syntax valid${NC}"
else
echo -e "${RED}✗ docker-compose.yml syntax invalid${NC}"
((ERRORS++))
fi
fi
echo
# Validation 4: File Ownership
echo -e "${BLUE}[4/4] Checking file ownership...${NC}"
if find . -user root -not -path "./.git/*" 2>/dev/null | grep -q .; then
echo -e "${RED}✗ Root-owned files detected:${NC}"
find . -user root -not -path "./.git/*" 2>/dev/null | sed 's/^/ /'
echo
echo " Fix with: sudo chown -R \$USER:\$USER ."
((ERRORS++))
else
echo -e "${GREEN}✓ No root-owned files${NC}"
fi
echo
# Additional checks
echo -e "${BLUE}Additional checks:${NC}"
# Check .env vs .env.example sync
if [ -f ".env.example" ]; then
ENV_KEYS=$(grep -v '^#' .env.example 2>/dev/null | grep '=' | cut -d= -f1 | sort)
if [ -f ".env" ]; then
ACTUAL_KEYS=$(grep -v '^#' .env 2>/dev/null | grep '=' | cut -d= -f1 | sort)
if [ "$ENV_KEYS" != "$ACTUAL_KEYS" ]; then
echo -e "${YELLOW}⚠ .env and .env.example keys don't match${NC}"
((WARNINGS++))
else
echo -e "${GREEN}✓ .env synced with .env.example${NC}"
fi
else
echo -e "${YELLOW}⚠ .env file not found (expected from .env.example)${NC}"
((WARNINGS++))
fi
fi
# Check git setup
if [ -d ".git" ]; then
# Check default branch
DEFAULT_BRANCH=$(git config init.defaultBranch 2>/dev/null || echo "")
if [ "$DEFAULT_BRANCH" = "main" ]; then
echo -e "${GREEN}✓ Git default branch: main${NC}"
else
echo -e "${YELLOW}⚠ Git default branch not set to 'main'${NC}"
((WARNINGS++))
fi
# Check merge strategy
MERGE_FF=$(git config merge.ff 2>/dev/null || echo "")
if [ "$MERGE_FF" = "only" ]; then
echo -e "${GREEN}✓ Git merge strategy: ff-only${NC}"
else
echo -e "${YELLOW}⚠ Git merge strategy not set to 'ff-only'${NC}"
((WARNINGS++))
fi
else
echo -e "${YELLOW}⚠ Not a git repository${NC}"
((WARNINGS++))
fi
echo
# Final report
echo -e "${BLUE}========================================"
echo "Validation Summary"
echo "========================================${NC}"
if [ $ERRORS -gt 0 ]; then
echo -e "${RED}FAILED: $ERRORS error(s) found${NC}"
if [ $WARNINGS -gt 0 ]; then
echo -e "${YELLOW}$WARNINGS warning(s) found${NC}"
fi
echo "========================================${NC}"
exit 1
elif [ $WARNINGS -gt 0 ]; then
echo -e "${YELLOW}PASSED with $WARNINGS warning(s)${NC}"
echo "========================================${NC}"
exit 0
else
echo -e "${GREEN}ALL VALIDATIONS PASSED!${NC}"
echo "========================================${NC}"
exit 0
fi
```
**Usage**:
```bash
# Run full validation
./scripts/validate-stack.sh
# In CI/CD
./scripts/validate-stack.sh || exit 1
```
---
### 3. scripts/setup-hooks.sh
Script to install git hooks from scripts/ to .git/hooks/
**Location**: `scripts/setup-hooks.sh`
**When to run**: After cloning, during stack creation
```bash
#!/usr/bin/env bash
#
# Install git hooks from scripts/ to .git/hooks/
#
set -euo pipefail
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "Installing git hooks..."
echo
# Check if .git exists
if [ ! -d ".git" ]; then
echo -e "${RED}✗ ERROR: Not a git repository!${NC}"
echo " Initialize git first: git init"
exit 1
fi
# Ensure hooks directory exists
mkdir -p .git/hooks
INSTALLED=0
FAILED=0
# Install pre-commit hook
if [ -f "scripts/pre-commit" ]; then
if cp scripts/pre-commit .git/hooks/pre-commit; then
chmod +x .git/hooks/pre-commit
echo -e "${GREEN}✓ Installed pre-commit hook${NC}"
((INSTALLED++))
else
echo -e "${RED}✗ Failed to install pre-commit hook${NC}"
((FAILED++))
fi
else
echo -e "${YELLOW}⚠ scripts/pre-commit not found, skipping${NC}"
fi
# Install pre-push hook (if exists)
if [ -f "scripts/pre-push" ]; then
if cp scripts/pre-push .git/hooks/pre-push; then
chmod +x .git/hooks/pre-push
echo -e "${GREEN}✓ Installed pre-push hook${NC}"
((INSTALLED++))
else
echo -e "${RED}✗ Failed to install pre-push hook${NC}"
((FAILED++))
fi
fi
echo
echo "========================================="
if [ $FAILED -gt 0 ]; then
echo -e "${RED}Installation completed with $FAILED error(s)${NC}"
echo "========================================="
exit 1
elif [ $INSTALLED -eq 0 ]; then
echo -e "${YELLOW}No hooks installed${NC}"
echo "========================================="
exit 0
else
echo -e "${GREEN}Successfully installed $INSTALLED hook(s)!${NC}"
echo "========================================="
echo
echo "Hooks will now run automatically:"
echo " - pre-commit: Before each commit"
echo
echo "To skip hooks (emergency only):"
echo " git commit --no-verify"
fi
```
**Usage**:
```bash
# Install hooks
./scripts/setup-hooks.sh
# Verify installation
ls -la .git/hooks/
```
---
## Optional Hooks
### scripts/pre-push (Optional)
Runs before `git push` to ensure remote-ready state.
```bash
#!/usr/bin/env bash
#
# Pre-push hook for GitLab Stack
# Runs before pushing to remote
#
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "========================================="
echo "Pre-push validation"
echo "========================================="
echo
ERRORS=0
# Run full validation before push
echo "Running full stack validation..."
if [ -x "./scripts/validate-stack.sh" ]; then
if ! ./scripts/validate-stack.sh; then
echo -e "${RED}✗ Stack validation failed!${NC}"
echo " Fix issues before pushing."
((ERRORS++))
fi
else
echo -e "${YELLOW}⚠ ./scripts/validate-stack.sh not found${NC}"
fi
# Check for uncommitted changes
if ! git diff-index --quiet HEAD --; then
echo -e "${YELLOW}⚠ WARNING: You have uncommitted changes${NC}"
echo " Consider committing them before pushing."
fi
echo
if [ $ERRORS -gt 0 ]; then
echo -e "${RED}Pre-push validation FAILED${NC}"
echo "To skip: git push --no-verify"
exit 1
fi
echo -e "${GREEN}Pre-push validation PASSED${NC}"
exit 0
```
---
## CI/CD Integration
### GitLab CI (.gitlab-ci.yml)
```yaml
stages:
- validate
- build
- deploy
validate:
stage: validate
image: docker:latest
services:
- docker:dind
before_script:
- apk add --no-cache bash findutils
script:
- chmod +x ./scripts/validate-stack.sh
- ./scripts/validate-stack.sh
only:
- merge_requests
- main
```
### GitHub Actions (.github/workflows/validate.yml)
```yaml
name: Stack Validation
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run stack validation
run: |
chmod +x ./scripts/validate-stack.sh
./scripts/validate-stack.sh
```
---
## Best Practices
### 1. Always Make Scripts Executable
```bash
chmod +x scripts/*.sh
chmod +x scripts/pre-commit
```
### 2. Test Hooks Before Committing
```bash
# Test pre-commit manually
./scripts/pre-commit
# Test validation
./scripts/validate-stack.sh
```
### 3. Document Hook Behavior
Include in README.md:
```markdown
## Git Hooks
This project uses git hooks for validation:
- **pre-commit**: Validates before each commit
- To skip: `git commit --no-verify` (emergency only)
```
### 4. Provide Skip Option
Always allow users to skip in emergencies:
```bash
git commit --no-verify -m "emergency fix"
```
### 5. Keep Hooks Fast
- Pre-commit should run in < 10 seconds
- Use quick checks when possible
- Defer expensive checks to CI/CD
### 6. Clear Error Messages
```bash
echo -e "${RED}✗ ERROR: Clear description${NC}"
echo " Explanation of what went wrong"
echo " How to fix it"
```
### 7. Exit Codes
```bash
# Success
exit 0
# Failure
exit 1
# Always use set -e to catch errors
set -euo pipefail
```
---
## Troubleshooting
### Hook Not Running
```bash
# Check if hook is installed
ls -la .git/hooks/pre-commit
# Check if executable
chmod +x .git/hooks/pre-commit
# Reinstall hooks
./scripts/setup-hooks.sh
```
### Hook Fails Unexpectedly
```bash
# Run hook manually to see output
./scripts/pre-commit
# Check validation separately
./scripts/validate-stack.sh
# Debug with set -x
bash -x scripts/pre-commit
```
### Skip Hook Temporarily
```bash
# Skip pre-commit
git commit --no-verify -m "message"
# Skip pre-push
git push --no-verify
```
### Permission Denied
```bash
# Make script executable
chmod +x scripts/pre-commit
chmod +x scripts/validate-stack.sh
# Reinstall hooks
./scripts/setup-hooks.sh
```
---
## Customization
### Adding Custom Checks
Edit `scripts/pre-commit`:
```bash
# Add custom check
echo "5. Running custom validation..."
if ! ./scripts/my-custom-check.sh; then
echo -e "${RED}✗ Custom validation failed${NC}"
((ERRORS++))
else
echo -e "${GREEN}✓ Custom validation passed${NC}"
fi
echo
```
### Adjusting Validation Strictness
**Strict Mode** (recommended for production):
```bash
# Fail on any error
set -euo pipefail
```
**Lenient Mode** (development only):
```bash
# Continue on errors, just report
set -uo pipefail
```
### Environment-Specific Hooks
```bash
# Check environment
if [ "${ENV:-}" = "production" ]; then
# Strict validation for production
./scripts/validate-stack.sh
else
# Lenient for development
echo "Development environment, skipping some checks"
fi
```
---
## Summary
Git hooks ensure:
- ✅ No secrets committed
- ✅ No root-owned files
- ✅ Full stack validation before commit
- ✅ Consistent code quality
- ✅ Automated validation in workflow
All scripts are:
- Executable (`chmod +x`)
- Well-documented
- Provide clear error messages
- Support emergency skip (`--no-verify`)
- Integrate with CI/CD