Restore workspace from backup during resume using rsync. Falls back to shutil.copytree if rsync is not available. Args: backup_dir: The backup directory to restore from. target_dir: The destination directory (e.g., HOST_WORKSPACE). additional_ignores: List of pa
(
backup_dir: Path,
target_dir: Path,
additional_ignores: Optional[List[str]] = None,
logger: Optional[Callable[[str], None]] = None,
)
| 134 | |
| 135 | |
| 136 | def restore_workspace( |
| 137 | backup_dir: Path, |
| 138 | target_dir: Path, |
| 139 | additional_ignores: Optional[List[str]] = None, |
| 140 | logger: Optional[Callable[[str], None]] = None, |
| 141 | ) -> bool: |
| 142 | """ |
| 143 | Restore workspace from backup during resume using rsync. |
| 144 | Falls back to shutil.copytree if rsync is not available. |
| 145 | |
| 146 | Args: |
| 147 | backup_dir: The backup directory to restore from. |
| 148 | target_dir: The destination directory (e.g., HOST_WORKSPACE). |
| 149 | additional_ignores: List of patterns to ignore in addition to defaults. |
| 150 | logger: Optional logger callable for status messages. |
| 151 | |
| 152 | Returns: |
| 153 | True if restore succeeded, False otherwise. |
| 154 | """ |
| 155 | |
| 156 | def _log(msg: str): |
| 157 | if logger: |
| 158 | logger(msg) |
| 159 | else: |
| 160 | print(msg) |
| 161 | |
| 162 | backup_dir = Path(backup_dir) |
| 163 | target_dir = Path(target_dir) |
| 164 | |
| 165 | if not backup_dir.exists(): |
| 166 | _log(f"[Checkpoint] Restore skipped: backup {backup_dir} does not exist") |
| 167 | return False |
| 168 | |
| 169 | # Check if backup has any content |
| 170 | if not any(backup_dir.iterdir()): |
| 171 | _log(f"[Checkpoint] Restore skipped: backup {backup_dir} is empty") |
| 172 | return False |
| 173 | |
| 174 | # Ensure target exists |
| 175 | target_dir.mkdir(parents=True, exist_ok=True) |
| 176 | |
| 177 | # Build ignore patterns |
| 178 | ignores = list(DEFAULT_IGNORES) |
| 179 | if additional_ignores: |
| 180 | ignores.extend(additional_ignores) |
| 181 | |
| 182 | # Try rsync first |
| 183 | try: |
| 184 | # Use --checksum to force sync based on content, not just timestamps |
| 185 | # This ensures backed-up content overwrites modified files |
| 186 | # Use --no-owner --no-group to avoid permission errors |
| 187 | cmd = ["rsync", "-a", "--checksum", "--no-owner", "--no-group"] |
| 188 | # Note: We don't use --delete on restore to preserve any new files in target |
| 189 | for pattern in ignores: |
| 190 | cmd.extend(["--exclude", pattern]) |
| 191 | cmd.extend([f"{backup_dir}/", f"{target_dir}/"]) |
| 192 | |
| 193 | result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) |