Rozdział 23 · Część IV · Wdrażamy SafeSort

Cofnij ostatnią operację

undo przywraca pliki z dziennika — i odmawia usunięcia tego, co pojawiło się w pierwotnym miejscu.

SafeSort · Część 4 z 6Wdrożenie
GitHubIssue #8 · Project „SafeSort — pierwsze wydanie”
Add undo
Area: SafetyPriority: High
git switch -c feat/manifest-and-undo

Komenda undo znajduje ostatni manifest, odczytuje lista wykonanych przeniesień i zwraca pliki tam, skąd zostały wzięte. Tutaj obowiązuje ta sama zasada co w całym programie: nic nie nadpisywać milcząco.

manifest.jsonostatni wpislistaruchydestination → sourceundoprzywraca pliki
tą samą ścieżką co apply, ale w przeciwnym kierunku.
src/safesort/manifest.py
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))
Cofnięcie nie przywraca tego, czego nie zostało zrobione
Pętla sprawdza if not move.completed: continue – Ruchy, które zawiodły podczas apply, nigdy nie dotykały dysku i nie ma czego cofnąć.

Najpierw standardowe skuteczne cofnięcie się:

~/safesort $ safesort undo ~/Downloads
Restored 9 moves.

Konflikt anulowania – rzeczywisty scenariusz

Co się stanie, jeśli uruchomimy undo ponownie, gdy już na swoich pierwotnych miejscach? Samo cofanie się wymaże to, co już tam jest. Zamiast ten SafeSort sprawdza oryginalną lokalizację wcześniej odzyskiwaniu każdego pliku i, Jeśli coś już tam jest, odmawia przywrócenia tego konkretnego pliku:

~/safesort $ safesort undo ~/Downloads
ERROR:safesort.manifest:Refusing to undo Sorted/other/bigfile_b.dat -> bigfile_b.dat: a file already exists at the original location: bigfile_b.dat
ERROR:safesort.manifest:Refusing to undo Sorted/documents/notes.txt -> notes.txt: a file already exists at the original location: notes.txt
... (Jeszcze 7 takich linijek)
Restored 0 moves.
9 moves could not be restored:
Sorted/other/bigfile_b.dat -> bigfile_b.dat: a file already exists at the original location: bigfile_b.dat
... (Jeszcze 8 linijek)

Odmowa — a nie ciche nadpisanie. Każdy konflikt jest przetwarzany niezależnie: pliki bez konfliktu zostałyby przywrócone, nawet jeśli część listy odmówiła.

Punkt kontrolny testowy: Operacja odwrotna

tests/test_manifest.py
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"
Praktyka: Cofnij i przywróć konflikt
Potrzebny jest dostęp do prawdziwego systemu plików – uruchamianego lokalnie w VS Code, PyCharm lub Jupyter
Praktyka kursuje lokalnie
Otwórz praktykę →
Punkt kontrolny · Issue #8
git commit -m "feat: record operation manifest and add undo"
Ta sama gałąź i ten sam Pull Request #19, co zamknął Issue #7 na poprzedniej stronie.
Status prawdziwego Project: Done