Skip to content

Merge remote-tracking branch 'origin/personal' into personal #158

Merge remote-tracking branch 'origin/personal' into personal

Merge remote-tracking branch 'origin/personal' into personal #158

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