Merge remote-tracking branch 'origin/personal' into personal #158
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Sync Personal to Master (Sanitized) | |
| # Automatically sync personal branch to master, stripping personal information | |
| # This allows you to work on personal branch while keeping master public-ready | |
| on: | |
| push: | |
| branches: | |
| - personal | |
| workflow_dispatch: # Allow manual trigger | |
| jobs: | |
| sync-sanitized: | |
| runs-on: ubuntu-latest | |
| env: | |
| # Personal info patterns to sanitize (defined as env vars for safety) | |
| PERSONAL_USERNAME: "guyfawkes" | |
| PERSONAL_HOME: "/home/guyfawkes" | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Check for new commits | |
| id: check | |
| run: | | |
| COMMIT_COUNT=$(git rev-list --count origin/master..origin/personal) | |
| echo "commit_count=$COMMIT_COUNT" >> "$GITHUB_OUTPUT" | |
| if [ "$COMMIT_COUNT" -eq 0 ]; then | |
| echo "No new commits to sync" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Found $COMMIT_COUNT commits to sync" | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Analyze and filter files | |
| if: steps.check.outputs.skip != 'true' | |
| id: filter | |
| run: | | |
| echo "## Sync Analysis" >> "$GITHUB_STEP_SUMMARY" | |
| # Files to always skip (personal-only) | |
| # NOTE: Use \. to match literal dots, not any character | |
| # (^|/)\.env matches .env files but not devenv.lock | |
| SKIP_PATTERNS=".claude/CLAUDE.local.md|.claude/settings.local.json|.claude/learning/|secrets/|(^|/)\.env|\.local\." | |
| SYNC_LIST="" | |
| SANITIZE_LIST="" | |
| DELETE_LIST="" | |
| # Find merge base for proper comparison of diverged branches | |
| MERGE_BASE=$(git merge-base origin/master origin/personal) | |
| echo "Merge base: $MERGE_BASE" | |
| # Get files that differ between personal and master (comparing branch tips) | |
| # Use three-dot diff to compare personal against merge-base | |
| CHANGED=$(git diff --name-only "$MERGE_BASE" origin/personal) | |
| echo "Changed files from merge-base: $CHANGED" | |
| for file in $CHANGED; do | |
| # Skip personal-only files | |
| if echo "$file" | grep -qE "$SKIP_PATTERNS"; then | |
| echo "- Skip: $file (personal-only)" >> "$GITHUB_STEP_SUMMARY" | |
| continue | |
| fi | |
| # Check if file contains personal paths (but allow workflow files through) | |
| if git show "origin/personal:$file" 2>/dev/null | grep -q "$PERSONAL_HOME"; then | |
| if [[ "$file" == *.md ]] || [[ "$file" == *.nix ]]; then | |
| SANITIZE_LIST="$SANITIZE_LIST $file" | |
| echo "- Sanitize: $file" >> "$GITHUB_STEP_SUMMARY" | |
| elif [[ "$file" == .github/workflows/*.yml ]]; then | |
| # Workflow files contain PERSONAL_HOME as env var - sync them directly | |
| SYNC_LIST="$SYNC_LIST $file" | |
| echo "- Sync: $file (workflow)" >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "- Skip: $file (personal paths)" >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| else | |
| SYNC_LIST="$SYNC_LIST $file" | |
| echo "- Sync: $file" >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| done | |
| # Get deleted files - two sources: | |
| # 1. Files deleted since merge-base | |
| DELETED_SINCE_BASE=$(git diff --name-only --diff-filter=D "$MERGE_BASE" origin/personal) | |
| # 2. Files on master but not on personal (handles files created after merge-base) | |
| FILES_ONLY_ON_MASTER=$(comm -23 <(git ls-tree -r --name-only origin/master | sort) <(git ls-tree -r --name-only origin/personal | sort)) | |
| # Combine both lists | |
| DELETED=$(echo -e "$DELETED_SINCE_BASE\n$FILES_ONLY_ON_MASTER" | sort -u | grep -v '^$') | |
| echo "Files to delete: $DELETED" | |
| for file in $DELETED; do | |
| # Skip personal-only files (don't delete what shouldn't exist on master anyway) | |
| if echo "$file" | grep -qE "$SKIP_PATTERNS"; then | |
| continue | |
| fi | |
| DELETE_LIST="$DELETE_LIST $file" | |
| echo "- Delete: $file" >> "$GITHUB_STEP_SUMMARY" | |
| done | |
| echo "sync_files=$SYNC_LIST" >> "$GITHUB_OUTPUT" | |
| echo "sanitize_files=$SANITIZE_LIST" >> "$GITHUB_OUTPUT" | |
| echo "delete_files=$DELETE_LIST" >> "$GITHUB_OUTPUT" | |
| - name: Sync files to master | |
| if: steps.check.outputs.skip != 'true' | |
| env: | |
| SYNC_FILES: ${{ steps.filter.outputs.sync_files }} | |
| SANITIZE_FILES: ${{ steps.filter.outputs.sanitize_files }} | |
| DELETE_FILES: ${{ steps.filter.outputs.delete_files }} | |
| RUN_NUMBER: ${{ github.run_number }} | |
| run: | | |
| # Checkout master | |
| git checkout master | |
| git pull origin master | |
| # Create working branch | |
| git checkout -b "sync-personal-${RUN_NUMBER}" | |
| # Copy files that don't need sanitization | |
| for file in $SYNC_FILES; do | |
| if [ -n "$file" ] && [ "$file" != " " ]; then | |
| git checkout origin/personal -- "$file" 2>/dev/null || true | |
| fi | |
| done | |
| # Copy and sanitize files with personal content | |
| for file in $SANITIZE_FILES; do | |
| if [ -n "$file" ] && [ "$file" != " " ]; then | |
| git checkout origin/personal -- "$file" 2>/dev/null || continue | |
| # Replace personal paths with placeholders | |
| sed -i "s|${PERSONAL_HOME}|/home/YOUR_USERNAME|g" "$file" 2>/dev/null || true | |
| fi | |
| done | |
| # Delete files that were removed on personal | |
| for file in $DELETE_FILES; do | |
| if [ -n "$file" ] && [ "$file" != " " ]; then | |
| git rm "$file" 2>/dev/null || true | |
| fi | |
| done | |
| # Always sanitize CLAUDE.md USER_MEMORY section (regardless of sync path) | |
| if [ -f "CLAUDE.md" ]; then | |
| sed -i '/<!-- USER_MEMORY_START -->/,/<!-- USER_MEMORY_END -->/{ | |
| /<!-- USER_MEMORY_START -->/b | |
| /<!-- USER_MEMORY_END -->/b | |
| /<!-- This section preserves/b | |
| /<!-- Add your content below/b | |
| d | |
| }' "CLAUDE.md" 2>/dev/null || true | |
| fi | |
| - name: Commit and push | |
| if: steps.check.outputs.skip != 'true' | |
| env: | |
| RUN_NUMBER: ${{ github.run_number }} | |
| run: | | |
| # Check for changes | |
| git add -A | |
| if git diff --staged --quiet; then | |
| echo "No changes after filtering" | |
| echo "### Result: No syncable changes" >> "$GITHUB_STEP_SUMMARY" | |
| exit 0 | |
| fi | |
| # Get recent commit subjects for reference (safely) | |
| RECENT_COMMITS=$(git log origin/master..origin/personal --format="%h %s" | head -5) | |
| # Build commit message with printf to avoid YAML parsing issues | |
| COMMIT_MSG=$(printf "chore: sync from personal branch (sanitized)\n\nRecent commits synced:\n%s\n\nAuto-synced by GitHub Actions (personal info stripped)" "$RECENT_COMMITS") | |
| git commit -m "$COMMIT_MSG" | |
| # Push to master | |
| git push origin HEAD:master | |
| echo "### Success: Synced to master" >> "$GITHUB_STEP_SUMMARY" | |
| - name: Cleanup | |
| if: always() | |
| env: | |
| RUN_NUMBER: ${{ github.run_number }} | |
| run: | | |
| git checkout personal 2>/dev/null || true | |
| git branch -D "sync-personal-${RUN_NUMBER}" 2>/dev/null || true |