fix(backup): fix duplicate detection, add push + rich summary table
Bug fix: cut -d'/' -f2 truncated 'origin/backup/main-*' to just 'backup', so rev-parse always failed and duplicate detection never worked. Fixed with sed to strip 'origin/' prefix properly. New features: - Push unpushed main commits before backing up - Fetch remote backup refs before comparing - Rich summary table: repo, status, commit count, push status, last msg - Color-coded output with dim/bold formatting - Proper cleanup using sed instead of broken cut
This commit is contained in:
parent
f2a5dcecda
commit
f82afb3872
@ -2,383 +2,78 @@
|
|||||||
|
|
||||||
**Description**: Smart backup of main branches across repositories with duplicate detection.
|
**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
|
## What It Does
|
||||||
- Before risky operations (rebases, large refactors)
|
|
||||||
- Regular safety backups
|
|
||||||
- When working with critical repositories
|
|
||||||
|
|
||||||
## Prerequisites
|
For each of the 3 repositories:
|
||||||
|
|
||||||
- Git access to repositories
|
1. Switches to `main` and pulls latest
|
||||||
- Write permissions to backup branch
|
2. **Pushes any unpushed main commits** to origin
|
||||||
- Clean working directory
|
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
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# From any repo root
|
bash /Users/sd9235/code/mygh/learning_ai_common_plat/scripts/backup-main.sh
|
||||||
/windsurf-workflow backup-main-branch
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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 SUMMARY 2026-02-12 20:15 │
|
||||||
BACKUP_BRANCH="backup/main-$(date +%Y-%m-%d)"
|
├──────────────────────────┬──────────────────┬─────────┬────────────┬────────────┤
|
||||||
LATEST_BACKUP=$(git branch -r --sort=-committerdate | grep "origin/backup/main-" | head -1 | cut -d'/' -f2)
|
│ Repository │ Backup Status │ Commits │ Main Push │ Last Commit│
|
||||||
|
├──────────────────────────┼──────────────────┼─────────┼────────────┼────────────┤
|
||||||
if [ -n "$LATEST_BACKUP" ]; then
|
│ learning_ai_common_plat │ ✅ New backup │ 142 │ ✅ 2 pushed│ feat(docs) │
|
||||||
# Compare main with latest backup
|
│ learning_voice_ai_agent │ ✅ Already backed│ 387 │ up to date │ fix(blob) │
|
||||||
MAIN_COMMIT=$(git rev-parse origin/main)
|
│ learning_multimodal_... │ ✅ New backup │ 98 │ ✅ 4 pushed│ feat(azure)│
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Restoration
|
## Restoration
|
||||||
|
|
||||||
### From Backup Branch
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# List available backups
|
# List available backups
|
||||||
git branch -r | grep "backup/main-"
|
git branch -r | grep "backup/main-"
|
||||||
|
|
||||||
# Switch to specific backup
|
# Restore from a specific backup
|
||||||
git checkout -b restore-from-backup origin/backup/main-2024-01-15-143022
|
git checkout -b restore-from-backup origin/backup/main-2026-02-12-201500
|
||||||
|
|
||||||
# Merge changes to main if needed
|
|
||||||
git switch main
|
git switch main
|
||||||
git merge restore-from-backup
|
git merge restore-from-backup
|
||||||
```
|
```
|
||||||
|
|
||||||
### Create Hotfix from Backup
|
## Customization
|
||||||
|
|
||||||
```bash
|
- **Add repos**: Edit `REPOS` array in `scripts/backup-main.sh`
|
||||||
# Create hotfix branch from backup
|
- **Change retention**: Modify `tail -n +8` (keep last N) in the cleanup section
|
||||||
git checkout -b hotfix/critical-bug origin/backup/main-2024-01-15-143022
|
- **Cron**: `0 18 * * * bash /Users/sd9235/code/mygh/learning_ai_common_plat/scripts/backup-main.sh`
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
## Related Skills
|
## Related Skills
|
||||||
|
|
||||||
- [Git Workflow](./git-workflow.md) - General Git operations
|
- [Debug Service](./debug-service.md) — Backup before major changes
|
||||||
- [Production Readiness](./production-readiness.md) - Backup before releases
|
- [Production Readiness](./production-readiness.md) — Backup before releases
|
||||||
- [Debug Service](./debug-service.md) - Backup before major changes
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Backup main branch with smart duplicate detection
|
# Backup main branch with smart duplicate detection
|
||||||
|
# Also pushes any unpushed main commits before backing up
|
||||||
|
|
||||||
set -e # Exit on any error
|
set -e # Exit on any error
|
||||||
|
|
||||||
@ -7,58 +8,115 @@ set -e # Exit on any error
|
|||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
DIM='\033[2m'
|
||||||
NC='\033[0m' # No Color
|
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
|
# Function to backup a single repository
|
||||||
backup_repo() {
|
backup_repo() {
|
||||||
local repo_path=$1
|
local repo_path=$1
|
||||||
local repo_name=$(basename "$repo_path")
|
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"
|
cd "$repo_path"
|
||||||
|
|
||||||
# Check if this is a git repository
|
# Check if this is a git repository
|
||||||
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
||||||
echo -e "${RED}❌ $repo_name: Not a git repository!${NC}"
|
echo -e "${RED}❌ Not a git repository!${NC}"
|
||||||
cd ..
|
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
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ensure we're on main
|
# Ensure we're on main
|
||||||
CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
|
CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
|
||||||
if [ "$CURRENT_BRANCH" != "main" ]; then
|
if [ "$CURRENT_BRANCH" != "main" ]; then
|
||||||
echo "Switching to main branch..."
|
echo " Switching to main branch..."
|
||||||
git switch main 2>/dev/null || git checkout main
|
git switch main 2>/dev/null || git checkout main
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Pull latest changes
|
# Pull latest changes
|
||||||
echo "Pulling latest changes..."
|
echo -e " ${DIM}Pulling latest...${NC}"
|
||||||
if ! git pull origin main 2>/dev/null; then
|
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}"
|
echo -e " ${YELLOW}⚠️ Could not pull (might be offline)${NC}"
|
||||||
# Continue anyway with local state
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if working directory is clean
|
# Check if working directory is clean
|
||||||
if [ -n "$(git status --porcelain)" ]; then
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
echo -e "${RED}⚠️ Working directory not clean in $repo_name!${NC}"
|
echo -e " ${RED}⚠️ Working directory not clean — skipping${NC}"
|
||||||
echo "Skipping backup for this repository"
|
SUMMARY_REPO[$SUMMARY_IDX]="$repo_name"
|
||||||
cd ..
|
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
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if backup is needed
|
# Gather repo stats
|
||||||
LATEST_BACKUP=$(git branch -r --sort=-committerdate 2>/dev/null | grep "origin/backup/main-" | head -1 | cut -d'/' -f2 || true)
|
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
|
if [ -n "$LATEST_BACKUP" ]; then
|
||||||
MAIN_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "")
|
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
|
if [ -n "$MAIN_COMMIT" ] && [ "$MAIN_COMMIT" = "$BACKUP_COMMIT" ]; then
|
||||||
echo -e "${GREEN}✅ $repo_name: Already backed up in $LATEST_BACKUP${NC}"
|
echo -e " ${GREEN}✅ Already backed up → $LATEST_BACKUP${NC}"
|
||||||
cd ..
|
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
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -66,28 +124,40 @@ backup_repo() {
|
|||||||
# Create new backup
|
# Create new backup
|
||||||
BACKUP_BRANCH="backup/main-$(date +%Y-%m-%d-%H%M%S)"
|
BACKUP_BRANCH="backup/main-$(date +%Y-%m-%d-%H%M%S)"
|
||||||
|
|
||||||
echo "Creating backup branch: $BACKUP_BRANCH"
|
echo -e " Creating: $BACKUP_BRANCH"
|
||||||
git checkout -b $BACKUP_BRANCH
|
git checkout -b "$BACKUP_BRANCH"
|
||||||
|
|
||||||
# Try to push, but continue even if it fails
|
local backup_status=""
|
||||||
if git push -u origin $BACKUP_BRANCH 2>/dev/null; then
|
if git push -u origin "$BACKUP_BRANCH" 2>/dev/null; then
|
||||||
echo -e "${GREEN}✅ Backup pushed to remote${NC}"
|
backup_status="✅ New backup"
|
||||||
|
echo -e " ${GREEN}✅ Pushed to remote${NC}"
|
||||||
else
|
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
|
fi
|
||||||
|
|
||||||
git checkout main
|
git checkout main 2>/dev/null
|
||||||
|
|
||||||
echo -e "${GREEN}✅ $repo_name: Backup created successfully${NC}"
|
|
||||||
|
|
||||||
# Cleanup old backups (keep last 7)
|
# 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
|
local old_branches=$(git branch -r --sort=-committerdate 2>/dev/null | grep 'origin/backup/main-' | tail -n +8 || true)
|
||||||
BACKUP_NAME=$(echo $branch | cut -d'/' -f2)
|
if [ -n "$old_branches" ]; then
|
||||||
echo "Deleting old backup: $BACKUP_NAME"
|
echo -e " ${DIM}Cleaning up old backups...${NC}"
|
||||||
git push origin --delete $BACKUP_NAME 2>/dev/null || true
|
while IFS= read -r branch; do
|
||||||
done
|
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
|
||||||
|
|
||||||
cd ..
|
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
|
# Backup all repositories
|
||||||
@ -97,13 +167,37 @@ REPOS=(
|
|||||||
"/Users/sd9235/code/mygh/learning_multimodal_memory_agents"
|
"/Users/sd9235/code/mygh/learning_multimodal_memory_agents"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check which repos exist
|
|
||||||
for repo in "${REPOS[@]}"; do
|
for repo in "${REPOS[@]}"; do
|
||||||
if [ -d "$repo" ]; then
|
if [ -d "$repo" ]; then
|
||||||
backup_repo "$repo"
|
backup_repo "$repo"
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}Repository not found: $repo${NC}"
|
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
|
fi
|
||||||
done
|
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<SUMMARY_IDX; i++)); do
|
||||||
|
printf "│ %-32s │ %-20s │ %7s │ %-14s │ %-32s │\n" \
|
||||||
|
"${SUMMARY_REPO[$i]}" \
|
||||||
|
"${SUMMARY_STATUS[$i]}" \
|
||||||
|
"${SUMMARY_COMMITS[$i]}" \
|
||||||
|
"${SUMMARY_PUSHED[$i]}" \
|
||||||
|
"${SUMMARY_LAST_MSG[$i]:0:32}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -e "${BOLD}└──────────────────────────────────┴──────────────────────┴─────────┴────────────────┴──────────────────────────────────┘${NC}"
|
||||||
|
echo -e "\n${GREEN}✨ Backup complete!${NC}"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user