Отмена последней операции
undo восстанавливает файлы из журнала — и отказывается затирать то, что успело появиться на исходном месте.
Команда undo находит последний манифест, читает список
выполненных перемещений и возвращает файлы туда, откуда они были взяты. Здесь действует
то же правило, что и во всей программе: ничего не перезаписывать молча.
def undo(manifest: OperationManifest) -> UndoResult:
restored, conflicts = [], []
for move in manifest.moves:
if not move.completed:
continue
source, destination = move.source, move.destination
if source.exists() or source.is_symlink():
conflicts.append(UndoConflict(source, destination,
"a file already exists at the original location"))
continue
shutil.move(str(destination), str(source))
restored.append(CompletedMove(destination, source, completed=True))
return UndoResult(restored=tuple(restored), conflicts=tuple(conflicts))
if not move.completed: continue — перемещения, которые не удались во время apply, никогда не касались диска, и отменять для них нечего.Сначала — обычный успешный откат:
Конфликт при отмене — реальный сценарий
Что произойдёт, если запустить undo ещё раз, когда файлы
уже на исходных местах? Простое перемещение назад стёрло бы то, что там уже есть. Вместо
этого SafeSort проверяет исходное место перед восстановлением каждого файла и,
если там уже что-то есть, отказывается восстанавливать именно этот файл:
Отказ — а не тихая перезапись. Каждый конфликт обрабатывается независимо: файлы без конфликта восстановились бы, даже если часть списка отказала.
Тест-чекпойнт: обратная операция
def test_undo_restores_original_location(tmp_path):
source = tmp_path / "report.pdf"
source.write_bytes(b"report")
plan = build_plan(scan(tmp_path, Config()), tmp_path, Config())
moves = apply_plan(plan)
manifest, _ = write_manifest(tmp_path, moves)
result = undo(manifest)
assert len(result.restored) == 1
assert source.read_bytes() == b"report"