From 9ab6686b98499c745024a474a71e3d99b6e14973 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 17 Sep 2026 13:58:25 +0200 Subject: a chain that deletes itself still gives back the file it displaced MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (on-conflict overwrite) trashes the file in the way; ยง7.4 promises undo restores it. Walking a file's log entries stopped dead at a permanent delete, so the displace written earlier in the same chain was never reached: the user's file stayed in the Trash, the refusal named only the file they did not care about, and krino log called the run undone. The displaced file is a different file, so it is offered as its own entry in the undo plan, keyed by its own path - the deleted file stays refused, since nothing of it can come back, and the copy or move that preceded the delete stays unreversed too (undoing a copy whose original was then deleted would destroy the last remaining copy). The accounting matched: every reversible step of a deleted file was subtracted, its displace included, so the run read (undone). Only what genuinely cannot come back is subtracted now. End to end, the scenario from the review: the only copy of a file is displaced by an incoming one that is then permanently deleted. before: archive/ empty, "(undone)", nothing offered after: archive/a.pdf restored, run reads partly undone --- internal/journal/read.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'internal/journal/read.go') diff --git a/internal/journal/read.go b/internal/journal/read.go index aa79118..a41cd8a 100644 --- a/internal/journal/read.go +++ b/internal/journal/read.go @@ -236,6 +236,7 @@ func Runs(path string, n int) ([]Run, error) { type fileOf struct{ run, dir, file string } reversibleOf := map[fileOf]int{} deletedFile := map[fileOf]bool{} + displacedOf := map[fileOf]int{} for _, line := range lines { e, ok := parseLine(line) @@ -259,6 +260,13 @@ func Runs(path string, n int) ([]Run, error) { if e.Action == "delete" { deletedFile[fileOf{e.Run, e.Dir, e.File}] = true } + if e.Action == "displace" { + // Counted apart from the rest of its file: a permanent + // delete ends the file it deleted, but the file that chain + // trashed to make room is a different file and can still + // come back. + displacedOf[fileOf{e.Run, e.Dir, e.File}]++ + } } if e.Action == "run-start" { if orig, ok := strings.CutPrefix(e.Detail, undoOfPrefix); ok && orig != "" { @@ -298,9 +306,16 @@ func Runs(path string, n int) ([]Run, error) { if r.Undone { took := 0 for f, n := range reversibleOf { - if f.run == r.ID && !deletedFile[f] { - took += n + if f.run != r.ID { + continue + } + if deletedFile[f] { + // Nothing of a permanently deleted file comes back + // except what it displaced. + took += displacedOf[f] + continue } + took += n } r.PartlyUndone = reversed[r.ID] < took } -- cgit v1.3