Spaces:
Sleeping
Sleeping
File size: 6,591 Bytes
5a03a94 f3e893e 5a03a94 c138f41 5a03a94 c138f41 5a03a94 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | #!/usr/bin/env python3
"""Restore project files from a saved checkpoint.
Supports restoring individual checkpoints, rolling back to the latest checkpoint
for a specific phase, or listing available checkpoints.
Usage:
python3 scripts/checkpoint_restore.py --id ckpt-20260523-220000 # Restore specific checkpoint
python3 scripts/checkpoint_restore.py --latest # Restore latest checkpoint
python3 scripts/checkpoint_restore.py --phase 0 # Restore latest Phase 0 checkpoint
python3 scripts/checkpoint_restore.py --list # List available checkpoints
python3 scripts/checkpoint_restore.py --id ckpt-xxx --dry-run # Preview without restoring
Checkpoints stored in: .checkpoints/<checkpoint_id>/
"""
import argparse
import json
import os
import shutil
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).parent.parent
CHECKPOINT_DIR = PROJECT_ROOT / ".checkpoints"
def load_index() -> dict:
"""Load the checkpoint index file."""
index_path = CHECKPOINT_DIR / "index.json"
if not index_path.exists():
print("ERROR: No checkpoints found (index.json missing)", file=sys.stderr)
sys.exit(1)
with open(index_path) as f:
return json.load(f)
def load_checkpoint_meta(checkpoint_id: str) -> dict:
"""Load metadata for a specific checkpoint."""
meta_path = CHECKPOINT_DIR / checkpoint_id / "checkpoint.json"
if not meta_path.exists():
print(f"ERROR: Checkpoint '{checkpoint_id}' not found", file=sys.stderr)
sys.exit(1)
with open(meta_path) as f:
return json.load(f)
def list_checkpoints():
"""List all checkpoints with details."""
index = load_index()
ckpts = index.get("checkpoints", [])
if not ckpts:
print("No checkpoints found")
return
print(f"Available checkpoints ({len(ckpts)}):")
print(f"{'ID':<30} {'Phase':<12} {'Files':<6} {'Message'}")
print("-" * 80)
for c in ckpts:
cid = c["id"]
phase = c.get("phase", "?")
count = c.get("files_count", "?")
msg = c.get("message", "")[:40]
print(f"{cid:<30} {phase:<12} {count:<6} {msg}")
def find_checkpoint(checkpoint_id: str = None, phase: str = None, latest: bool = False) -> str:
"""Find a checkpoint ID based on search criteria."""
index = load_index()
ckpts = index.get("checkpoints", [])
if checkpoint_id:
return checkpoint_id
if latest:
if not ckpts:
print("ERROR: No checkpoints available", file=sys.stderr)
sys.exit(1)
return ckpts[0]["id"]
if phase:
phase_ckpts = [c for c in ckpts if c.get("phase") == phase]
if not phase_ckpts:
print(f"ERROR: No checkpoints found for phase '{phase}'", file=sys.stderr)
sys.exit(1)
return phase_ckpts[0]["id"]
return ckpts[0]["id"]
def restore_checkpoint(checkpoint_id: str, dry_run: bool = False):
"""Restore files from a checkpoint.
Args:
checkpoint_id: The checkpoint ID to restore from.
dry_run: If True, only show what would be restored without copying.
"""
meta = load_checkpoint_meta(checkpoint_id)
ckpt_dir = CHECKPOINT_DIR / checkpoint_id
print(f"Checkpoint: {checkpoint_id}")
print(f" Phase: {meta['phase']}")
print(f" Timestamp: {meta['timestamp']}")
print(f" Message: {meta.get('message', '')}")
print()
if dry_run:
print("DRY RUN — No files will be modified")
print()
files_to_restore = meta.get("files_backed_up", [])
restored = []
skipped = []
for rel_path in files_to_restore:
src = ckpt_dir / rel_path
dst = PROJECT_ROOT / rel_path
if not src.exists():
skipped.append((rel_path, "source missing in checkpoint"))
continue
if dry_run:
dst_exists = dst.exists()
dst_size = os.path.getsize(dst) if dst_exists else 0
src_size = os.path.getsize(src)
print(f" Would restore: {rel_path}")
print(f" From: {src_size} bytes (checkpoint)")
print(f" To: {dst_size} bytes {'(existing)' if dst_exists else '(new file)'}")
restored.append(rel_path)
continue
# Make backup of current file before overwriting?
# No — this is a restore, the checkpoint is the reference.
# If user wants to keep current state, they should have saved a checkpoint first.
os.makedirs(dst.parent, exist_ok=True)
try:
shutil.copy2(src, dst)
restored.append(rel_path)
print(f" ✓ {rel_path}")
except Exception as e:
skipped.append((rel_path, str(e)))
if not dry_run:
print()
print(f"Restore complete: {len(restored)} files restored")
if skipped:
print(f" {len(skipped)} files skipped:")
for f, reason in skipped:
print(f" ✗ {f} ({reason})")
# Update current checkpoint metadata
print()
print(f"Restored from checkpoint: {checkpoint_id}")
print(f" Phase: {meta['phase']}")
print(f" Original timestamp: {meta['timestamp']}")
# If there are app files, suggest restart
has_app = any(f.startswith("app/") for f in restored)
if has_app:
print()
print("⚠️ App files were restored. Restart the API server:")
else:
print()
print(f"Dry run complete: {len(restored)} files would be restored")
if skipped:
print(f" {len(skipped)} files would be skipped")
def main():
parser = argparse.ArgumentParser(
description="Restore EUR-Lex AI Chat project from a saved checkpoint"
)
parser.add_argument("--id", help="Checkpoint ID to restore")
parser.add_argument("--latest", action="store_true", help="Restore latest checkpoint")
parser.add_argument("--phase", help="Restore latest checkpoint for a specific phase")
parser.add_argument("--list", action="store_true", help="List available checkpoints")
parser.add_argument("--dry-run", action="store_true", help="Preview without restoring")
args = parser.parse_args()
if args.list:
list_checkpoints()
return
checkpoint_id = find_checkpoint(
checkpoint_id=args.id,
phase=args.phase,
latest=args.latest,
)
restore_checkpoint(checkpoint_id, dry_run=args.dry_run)
if __name__ == "__main__":
main()
|