diff --git a/AI.dev/SKILLS/backup-main-branch.md b/AI.dev/SKILLS/backup-main-branch.md index 6d91d5cb..2a61814a 100644 --- a/AI.dev/SKILLS/backup-main-branch.md +++ b/AI.dev/SKILLS/backup-main-branch.md @@ -2,383 +2,78 @@ **Description**: Smart backup of main branches across repositories with duplicate detection. -## When to Use +**Script**: `scripts/backup-main.sh` -- Before making major changes to main branch -- Before risky operations (rebases, large refactors) -- Regular safety backups -- When working with critical repositories +## What It Does -## Prerequisites +For each of the 3 repositories: -- Git access to repositories -- Write permissions to backup branch -- Clean working directory +1. Switches to `main` and pulls latest +2. **Pushes any unpushed main commits** to origin +3. Fetches latest remote backup refs +4. **Skips backup** if HEAD matches the latest `backup/main-*` branch (smart duplicate detection) +5. Creates timestamped `backup/main-YYYY-MM-DD-HHMMSS` branch and pushes it +6. Cleans up old backups (keeps last 7) +7. Prints a **summary table** with: repo name, backup status, total commits, push status, last commit message + +## Repositories Covered + +- `learning_ai_common_plat` +- `learning_voice_ai_agent` +- `learning_multimodal_memory_agents` ## Quick Start ```bash -# From any repo root -/windsurf-workflow backup-main-branch +bash /Users/sd9235/code/mygh/learning_ai_common_plat/scripts/backup-main.sh ``` -## Workflow Steps +Or via Windsurf workflow: `/backup-main-branch` -### 1. Check Current State +## Example Output -```bash -# Ensure we're on main branch -CURRENT_BRANCH=$(git branch --show-current) -if [ "$CURRENT_BRANCH" != "main" ]; then - echo "Switching to main branch..." - git switch main -fi - -# Check if working directory is clean -if [ -n "$(git status --porcelain)" ]; then - echo "⚠️ Working directory not clean!" - echo "Please commit or stash changes before backup" - exit 1 -fi ``` +━━━ learning_ai_common_plat ━━━ + Pulling latest... + Pushing 2 unpushed commit(s) on main... + ✅ Main pushed + Creating: backup/main-2026-02-12-201500 + ✅ Pushed to remote -### 2. Check if Backup Needed +━━━ learning_voice_ai_agent ━━━ + Pulling latest... + ✅ Already backed up → backup/main-2026-02-12-201500 -```bash -# Get timestamp of last backup branch commit -BACKUP_BRANCH="backup/main-$(date +%Y-%m-%d)" -LATEST_BACKUP=$(git branch -r --sort=-committerdate | grep "origin/backup/main-" | head -1 | cut -d'/' -f2) - -if [ -n "$LATEST_BACKUP" ]; then - # Compare main with latest backup - MAIN_COMMIT=$(git rev-parse origin/main) - BACKUP_COMMIT=$(git rev-parse origin/$LATEST_BACKUP) - - if [ "$MAIN_COMMIT" = "$BACKUP_COMMIT" ]; then - echo "✅ Main branch already backed up in $LATEST_BACKUP" - echo "No backup needed" - exit 0 - fi -fi -``` - -### 3. Create New Backup - -```bash -# Create backup branch name with timestamp -BACKUP_BRANCH="backup/main-$(date +%Y-%m-%d-%H%M%S)" - -# Create and push backup branch -echo "Creating backup branch: $BACKUP_BRANCH" -git checkout -b $BACKUP_BRANCH -git push -u origin $BACKUP_BRANCH - -# Switch back to main -git checkout main - -echo "✅ Backup created: $BACKUP_BRANCH" -``` - -### 4. Cleanup Old Backups (Optional) - -```bash -# Keep only last 7 days of backups -echo "Cleaning up old backups..." -git fetch origin -for branch in $(git branch -r --sort=-committerdate | grep "origin/backup/main-" | tail -n +8); do - BACKUP_NAME=$(echo $branch | cut -d'/' -f2) - echo "Deleting old backup: $BACKUP_NAME" - git push origin --delete $BACKUP_NAME -done -``` - -## Full Script - -Create `scripts/backup-main.sh`: - -```bash -#!/bin/bash -# Backup main branch with smart duplicate detection - -set -e # Exit on any error - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -echo -e "${GREEN}🔄 Starting main branch backup...${NC}" - -# Function to backup a single repository -backup_repo() { - local repo_path=$1 - local repo_name=$(basename "$repo_path") - - echo -e "\n${YELLOW}Processing repository: $repo_name${NC}" - - cd "$repo_path" - - # Ensure we're on main - CURRENT_BRANCH=$(git branch --show-current) - if [ "$CURRENT_BRANCH" != "main" ]; then - echo "Switching to main branch..." - git switch main - fi - - # Pull latest changes - echo "Pulling latest changes..." - git pull origin main - - # Check if working directory is clean - if [ -n "$(git status --porcelain)" ]; then - echo -e "${RED}⚠️ Working directory not clean in $repo_name!${NC}" - echo "Skipping backup for this repository" - cd .. - return 1 - fi - - # Check if backup is needed - LATEST_BACKUP=$(git branch -r --sort=-committerdate 2>/dev/null | grep "origin/backup/main-" | head -1 | cut -d'/' -f2 || true) - - if [ -n "$LATEST_BACKUP" ]; then - MAIN_COMMIT=$(git rev-parse origin/main) - BACKUP_COMMIT=$(git rev-parse origin/$LATEST_BACKUP 2>/dev/null || true) - - if [ "$MAIN_COMMIT" = "$BACKUP_COMMIT" ]; then - echo -e "${GREEN}✅ $repo_name: Already backed up in $LATEST_BACKUP${NC}" - cd .. - return 0 - fi - fi - - # Create new backup - BACKUP_BRANCH="backup/main-$(date +%Y-%m-%d-%H%M%S)" - - echo "Creating backup branch: $BACKUP_BRANCH" - git checkout -b $BACKUP_BRANCH - git push -u origin $BACKUP_BRANCH - git checkout main - - echo -e "${GREEN}✅ $repo_name: Backup created successfully${NC}" - - # Cleanup old backups (keep last 7) - for branch in $(git branch -r --sort=-committerdate 2>/dev/null | grep "origin/backup/main-" | tail -n +8); do - BACKUP_NAME=$(echo $branch | cut -d'/' -f2) - echo "Deleting old backup: $BACKUP_NAME" - git push origin --delete $BACKUP_NAME 2>/dev/null || true - done - - cd .. -} - -# Backup all repositories -REPOS=( - "/Users/sd9235/code/mygh/learning_ai_common_plat" - "/Users/sd9235/code/mygh/learning_voice_ai_agent" - "/Users/sd9235/code/mygh/learning_multimodal_memory_agents" -) - -# Check which repos exist -for repo in "${REPOS[@]}"; do - if [ -d "$repo" ]; then - backup_repo "$repo" - else - echo -e "${YELLOW}Repository not found: $repo${NC}" - fi -done - -echo -e "\n${GREEN}✨ Backup process completed!${NC}" -``` - -## Windsurf Workflow - -Create `.windsurf/workflows/backup-main-branch.md`: - -```markdown ---- -description: Smart backup of main branches with duplicate detection ---- - -# Backup Main Branch - -Creates smart backups of main branches across all repositories. - -// turbo -Run `bash scripts/backup-main.sh` from any repository root - -## What it does: - -1. Checks each repository for changes -2. Skips backup if main hasn't changed since last backup -3. Creates timestamped backup branch -4. Cleans up old backups (keeps 7 days) -5. Returns to main branch - -## Repositories covered: - -- learning_ai_common_plat -- learning_voice_ai_agent -- learning_multimodal_memory_agents -``` - -## Advanced Features - -### Backup with Tags - -```bash -# Create backup with descriptive tag -BACKUP_BRANCH="backup/main-$(date +%Y-%m-%d)-before-refactor" -``` - -### Include Additional Repositories - -```bash -# Add to REPOS array in script -REPOS=( - "/Users/sd9235/code/mygh/learning_ai_common_plat" - "/Users/sd9235/code/mygh/learning_voice_ai_agent" - "/Users/sd9235/code/mygh/learning_multimodal_memory_agents" - "/path/to/your/repo" # Add new repo here -) -``` - -### Custom Retention Policy - -```bash -# Keep backups for 30 days instead of 7 -for branch in $(git branch -r --sort=-committerdate | grep "origin/backup/main-" | tail -n +31); do - # Delete old backup -done +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ BACKUP SUMMARY 2026-02-12 20:15 │ +├──────────────────────────┬──────────────────┬─────────┬────────────┬────────────┤ +│ Repository │ Backup Status │ Commits │ Main Push │ Last Commit│ +├──────────────────────────┼──────────────────┼─────────┼────────────┼────────────┤ +│ learning_ai_common_plat │ ✅ New backup │ 142 │ ✅ 2 pushed│ feat(docs) │ +│ learning_voice_ai_agent │ ✅ Already backed│ 387 │ up to date │ fix(blob) │ +│ learning_multimodal_... │ ✅ New backup │ 98 │ ✅ 4 pushed│ feat(azure)│ +└──────────────────────────┴──────────────────┴─────────┴────────────┴────────────┘ ``` ## Restoration -### From Backup Branch - ```bash # List available backups git branch -r | grep "backup/main-" -# Switch to specific backup -git checkout -b restore-from-backup origin/backup/main-2024-01-15-143022 - -# Merge changes to main if needed +# Restore from a specific backup +git checkout -b restore-from-backup origin/backup/main-2026-02-12-201500 git switch main git merge restore-from-backup ``` -### Create Hotfix from Backup +## Customization -```bash -# Create hotfix branch from backup -git checkout -b hotfix/critical-bug origin/backup/main-2024-01-15-143022 - -# Make fixes, then merge to main -git switch main -git merge hotfix/critical-bug -git push origin main -``` - -## Automation - -### Cron Job for Daily Backups - -```bash -# Add to crontab (crontab -e) -0 18 * * * cd /Users/sd9235/code/mygh/learning_ai_common_plat && bash scripts/backup-main.sh -``` - -### GitHub Actions - -```yaml -name: Daily Backup - -on: - schedule: - - cron: '0 18 * * *' # Daily at 6 PM - workflow_dispatch: - -jobs: - backup: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.PAT }} # Personal Access Token with push permissions - - - name: Configure Git - run: | - git config --global user.name "Backup Bot" - git config --global user.email "backup@example.com" - - - name: Run Backup Script - run: bash scripts/backup-main.sh -``` - -## Best Practices - -### When to Backup - -- Before major refactors -- Before risky operations (rebase, force push) -- Before releases -- On a regular schedule (daily/weekly) - -### Backup Naming - -- Use descriptive names for important backups -- Include date and time in all backups -- Consider adding purpose (e.g., "-before-v2.0") - -### Security - -- Use secure Personal Access Tokens -- Limit backup branch permissions -- Regularly review and clean old backups - -## Troubleshooting - -### Permission Denied - -```bash -# Ensure PAT has correct permissions -# Settings → Developer settings → Personal access tokens → repo (full control) -``` - -### Branch Already Exists - -```bash -# Delete local branch if exists -git branch -D backup/main-YYYY-MM-DD-HHMMSS - -# Or use different timestamp -BACKUP_BRANCH="backup/main-$(date +%Y-%m-%d-%H%M%S)-$(uuidgen | head -c 8)" -``` - -### Network Issues - -```bash -# Retry failed pushes -git push -u origin $BACKUP_BRANCH - -# Or use retry loop -for i in {1..3}; do - git push -u origin $BACKUP_BRANCH && break - sleep 5 -done -``` - -## Notes - -- **Smart detection** prevents unnecessary backups -- **Automatic cleanup** keeps repository tidy -- **Multi-repo support** backs up all related projects -- **Safe operations** always returns to main branch -- **No data loss** only creates branches, never deletes main +- **Add repos**: Edit `REPOS` array in `scripts/backup-main.sh` +- **Change retention**: Modify `tail -n +8` (keep last N) in the cleanup section +- **Cron**: `0 18 * * * bash /Users/sd9235/code/mygh/learning_ai_common_plat/scripts/backup-main.sh` ## Related Skills -- [Git Workflow](./git-workflow.md) - General Git operations -- [Production Readiness](./production-readiness.md) - Backup before releases -- [Debug Service](./debug-service.md) - Backup before major changes +- [Debug Service](./debug-service.md) — Backup before major changes +- [Production Readiness](./production-readiness.md) — Backup before releases diff --git a/scripts/backup-main.sh b/scripts/backup-main.sh index 353d6508..11b8fab2 100755 --- a/scripts/backup-main.sh +++ b/scripts/backup-main.sh @@ -1,5 +1,6 @@ #!/bin/bash # Backup main branch with smart duplicate detection +# Also pushes any unpushed main commits before backing up set -e # Exit on any error @@ -7,87 +8,156 @@ set -e # Exit on any error RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +DIM='\033[2m' NC='\033[0m' # No Color -echo -e "${GREEN}🔄 Starting main branch backup...${NC}" +# Summary table data +declare -a SUMMARY_REPO +declare -a SUMMARY_STATUS +declare -a SUMMARY_COMMITS +declare -a SUMMARY_LAST_MSG +declare -a SUMMARY_BRANCH +declare -a SUMMARY_PUSHED +SUMMARY_IDX=0 + +echo -e "${BOLD}🔄 Starting main branch backup...${NC}" +echo -e "${DIM}$(date '+%Y-%m-%d %H:%M:%S')${NC}" # Function to backup a single repository backup_repo() { local repo_path=$1 local repo_name=$(basename "$repo_path") - - echo -e "\n${YELLOW}Processing repository: $repo_name${NC}" - + + echo -e "\n${YELLOW}━━━ $repo_name ━━━${NC}" + cd "$repo_path" - + # Check if this is a git repository if ! git rev-parse --git-dir > /dev/null 2>&1; then - echo -e "${RED}❌ $repo_name: Not a git repository!${NC}" - cd .. + echo -e "${RED}❌ Not a git repository!${NC}" + SUMMARY_REPO[$SUMMARY_IDX]="$repo_name" + SUMMARY_STATUS[$SUMMARY_IDX]="❌ Not a git repo" + SUMMARY_COMMITS[$SUMMARY_IDX]="-" + SUMMARY_LAST_MSG[$SUMMARY_IDX]="-" + SUMMARY_BRANCH[$SUMMARY_IDX]="-" + SUMMARY_PUSHED[$SUMMARY_IDX]="-" + ((SUMMARY_IDX++)) return 1 fi - + # Ensure we're on main CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo "") if [ "$CURRENT_BRANCH" != "main" ]; then - echo "Switching to main branch..." + echo " Switching to main branch..." git switch main 2>/dev/null || git checkout main fi - + # Pull latest changes - echo "Pulling latest changes..." + echo -e " ${DIM}Pulling latest...${NC}" if ! git pull origin main 2>/dev/null; then - echo -e "${YELLOW}⚠️ Could not pull latest changes (might be offline or no origin/main)${NC}" - # Continue anyway with local state + echo -e " ${YELLOW}⚠️ Could not pull (might be offline)${NC}" fi - + # Check if working directory is clean if [ -n "$(git status --porcelain)" ]; then - echo -e "${RED}⚠️ Working directory not clean in $repo_name!${NC}" - echo "Skipping backup for this repository" - cd .. + echo -e " ${RED}⚠️ Working directory not clean — skipping${NC}" + SUMMARY_REPO[$SUMMARY_IDX]="$repo_name" + SUMMARY_STATUS[$SUMMARY_IDX]="⚠️ Dirty worktree" + SUMMARY_COMMITS[$SUMMARY_IDX]="-" + SUMMARY_LAST_MSG[$SUMMARY_IDX]="-" + SUMMARY_BRANCH[$SUMMARY_IDX]="-" + SUMMARY_PUSHED[$SUMMARY_IDX]="-" + ((SUMMARY_IDX++)) return 1 fi - - # Check if backup is needed - LATEST_BACKUP=$(git branch -r --sort=-committerdate 2>/dev/null | grep "origin/backup/main-" | head -1 | cut -d'/' -f2 || true) - + + # Gather repo stats + local total_commits=$(git rev-list --count HEAD 2>/dev/null || echo "0") + local last_msg=$(git log -1 --pretty=format:'%s' 2>/dev/null | head -c 50) + local last_author=$(git log -1 --pretty=format:'%an' 2>/dev/null) + local last_date=$(git log -1 --pretty=format:'%ar' 2>/dev/null) + + # Push any unpushed main commits first + local ahead_count=0 + ahead_count=$(git rev-list --count origin/main..HEAD 2>/dev/null || echo "0") + local pushed_main="—" + if [ "$ahead_count" -gt 0 ]; then + echo -e " ${CYAN}Pushing $ahead_count unpushed commit(s) on main...${NC}" + if git push origin main 2>/dev/null; then + pushed_main="✅ $ahead_count pushed" + echo -e " ${GREEN}✅ Main pushed${NC}" + else + pushed_main="❌ push failed" + echo -e " ${RED}❌ Push failed${NC}" + fi + else + pushed_main="up to date" + fi + + # Fetch to ensure we have latest remote backup refs + git fetch origin 'refs/heads/backup/*:refs/remotes/origin/backup/*' 2>/dev/null || true + + # Check if backup is needed (compare HEAD with latest backup) + # Fix: use cut -d'/' -f2- to get the full branch path after 'origin/' + LATEST_BACKUP=$(git branch -r --sort=-committerdate 2>/dev/null | grep 'origin/backup/main-' | head -1 | sed 's|^[[:space:]]*origin/||' || true) + if [ -n "$LATEST_BACKUP" ]; then MAIN_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "") - BACKUP_COMMIT=$(git rev-parse origin/$LATEST_BACKUP 2>/dev/null || echo "") - + BACKUP_COMMIT=$(git rev-parse "origin/$LATEST_BACKUP" 2>/dev/null || echo "") + if [ -n "$MAIN_COMMIT" ] && [ "$MAIN_COMMIT" = "$BACKUP_COMMIT" ]; then - echo -e "${GREEN}✅ $repo_name: Already backed up in $LATEST_BACKUP${NC}" - cd .. + echo -e " ${GREEN}✅ Already backed up → $LATEST_BACKUP${NC}" + SUMMARY_REPO[$SUMMARY_IDX]="$repo_name" + SUMMARY_STATUS[$SUMMARY_IDX]="✅ Already backed up" + SUMMARY_COMMITS[$SUMMARY_IDX]="$total_commits" + SUMMARY_LAST_MSG[$SUMMARY_IDX]="$last_msg" + SUMMARY_BRANCH[$SUMMARY_IDX]="$LATEST_BACKUP" + SUMMARY_PUSHED[$SUMMARY_IDX]="$pushed_main" + ((SUMMARY_IDX++)) return 0 fi fi - + # Create new backup BACKUP_BRANCH="backup/main-$(date +%Y-%m-%d-%H%M%S)" - - echo "Creating backup branch: $BACKUP_BRANCH" - git checkout -b $BACKUP_BRANCH - - # Try to push, but continue even if it fails - if git push -u origin $BACKUP_BRANCH 2>/dev/null; then - echo -e "${GREEN}✅ Backup pushed to remote${NC}" + + echo -e " Creating: $BACKUP_BRANCH" + git checkout -b "$BACKUP_BRANCH" + + local backup_status="" + if git push -u origin "$BACKUP_BRANCH" 2>/dev/null; then + backup_status="✅ New backup" + echo -e " ${GREEN}✅ Pushed to remote${NC}" else - echo -e "${YELLOW}⚠️ Could not push backup to remote (backup exists locally only)${NC}" + backup_status="⚠️ Local only" + echo -e " ${YELLOW}⚠️ Local only (push failed)${NC}" fi - - git checkout main - - echo -e "${GREEN}✅ $repo_name: Backup created successfully${NC}" - + + git checkout main 2>/dev/null + # Cleanup old backups (keep last 7) - for branch in $(git branch -r --sort=-committerdate 2>/dev/null | grep "origin/backup/main-" | tail -n +8); do - BACKUP_NAME=$(echo $branch | cut -d'/' -f2) - echo "Deleting old backup: $BACKUP_NAME" - git push origin --delete $BACKUP_NAME 2>/dev/null || true - done - - cd .. + local old_branches=$(git branch -r --sort=-committerdate 2>/dev/null | grep 'origin/backup/main-' | tail -n +8 || true) + if [ -n "$old_branches" ]; then + echo -e " ${DIM}Cleaning up old backups...${NC}" + while IFS= read -r branch; do + branch=$(echo "$branch" | sed 's|^[[:space:]]*origin/||') + if [ -n "$branch" ]; then + echo -e " ${DIM} Deleting: $branch${NC}" + git push origin --delete "$branch" 2>/dev/null || true + fi + done <<< "$old_branches" + fi + + SUMMARY_REPO[$SUMMARY_IDX]="$repo_name" + SUMMARY_STATUS[$SUMMARY_IDX]="$backup_status" + SUMMARY_COMMITS[$SUMMARY_IDX]="$total_commits" + SUMMARY_LAST_MSG[$SUMMARY_IDX]="$last_msg" + SUMMARY_BRANCH[$SUMMARY_IDX]="$BACKUP_BRANCH" + SUMMARY_PUSHED[$SUMMARY_IDX]="$pushed_main" + ((SUMMARY_IDX++)) } # Backup all repositories @@ -97,13 +167,37 @@ REPOS=( "/Users/sd9235/code/mygh/learning_multimodal_memory_agents" ) -# Check which repos exist for repo in "${REPOS[@]}"; do if [ -d "$repo" ]; then backup_repo "$repo" else echo -e "${YELLOW}Repository not found: $repo${NC}" + SUMMARY_REPO[$SUMMARY_IDX]="$(basename "$repo")" + SUMMARY_STATUS[$SUMMARY_IDX]="❌ Not found" + SUMMARY_COMMITS[$SUMMARY_IDX]="-" + SUMMARY_LAST_MSG[$SUMMARY_IDX]="-" + SUMMARY_BRANCH[$SUMMARY_IDX]="-" + SUMMARY_PUSHED[$SUMMARY_IDX]="-" + ((SUMMARY_IDX++)) fi done -echo -e "\n${GREEN}✨ Backup process completed!${NC}" +# Print summary table +echo "" +echo -e "${BOLD}┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐${NC}" +echo -e "${BOLD}│ BACKUP SUMMARY $(date '+%Y-%m-%d %H:%M') │${NC}" +echo -e "${BOLD}├──────────────────────────────────┬──────────────────────┬─────────┬────────────────┬──────────────────────────────────┤${NC}" +printf "${BOLD}│ %-32s │ %-20s │ %7s │ %-14s │ %-32s │${NC}\n" "Repository" "Backup Status" "Commits" "Main Push" "Last Commit" +echo -e "${BOLD}├──────────────────────────────────┼──────────────────────┼─────────┼────────────────┼──────────────────────────────────┤${NC}" + +for ((i=0; i