aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/design.md3
-rw-r--r--internal/journal/read.go18
-rw-r--r--internal/journal/read_test.go31
-rw-r--r--man/krino.13
4 files changed, 51 insertions, 4 deletions
diff --git a/docs/design.md b/docs/design.md
index 744ef05..e8dd1af 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -664,7 +664,8 @@ not logged; declined files are.
- `krino log` lists recent runs: id, time, directories, counts, and
`(undone)` once undo has reversed every reversible step of a run, or
- `(partly undone)` while some are not (declined, refused or failed).
+ `(partly undone)` while some are not (declined, refused or failed). A file
+ whose chain ends in a permanent delete is never undoable and not counted.
- `krino undo` reverses the most recent run, in every directory it
touched. An older run is undone by naming it: `krino undo RUN`. Undo runs
cannot themselves be undone: naming one is refused.
diff --git a/internal/journal/read.go b/internal/journal/read.go
index 438e6ba..5a1de04 100644
--- a/internal/journal/read.go
+++ b/internal/journal/read.go
@@ -230,6 +230,12 @@ func Runs(path string, n int) ([]Run, error) {
order := make([]string, 0)
byID := make(map[string]*Run)
pendingUndo := make(map[string]string) // undo run ID -> the run ID it claims to undo
+ // A file whose chain ended in a permanent delete is never undone, so its
+ // reversible steps do not count toward what a run took (plan 11 review
+ // L8).
+ type fileOf struct{ run, dir, file string }
+ reversibleOf := map[fileOf]int{}
+ deletedFile := map[fileOf]bool{}
for _, line := range lines {
e, ok := parseLine(line)
@@ -247,6 +253,12 @@ func Runs(path string, n int) ([]Run, error) {
}
if e.Status == "ok" {
r.Counts[e.Action]++
+ if _, ok := reversible[e.Action]; ok {
+ reversibleOf[fileOf{e.Run, e.Dir, e.File}]++
+ }
+ if e.Action == "delete" {
+ deletedFile[fileOf{e.Run, e.Dir, e.File}] = true
+ }
}
if e.Action == "run-start" {
if orig, ok := strings.CutPrefix(e.Detail, undoOfPrefix); ok && orig != "" {
@@ -285,8 +297,10 @@ func Runs(path string, n int) ([]Run, error) {
r.UndoOf = pendingUndo[r.ID]
if r.Undone {
took := 0
- for action := range reversible {
- took += r.Counts[action]
+ for f, n := range reversibleOf {
+ if f.run == r.ID && !deletedFile[f] {
+ took += n
+ }
}
r.PartlyUndone = reversed[r.ID] < took
}
diff --git a/internal/journal/read_test.go b/internal/journal/read_test.go
index efb5a15..4f1c902 100644
--- a/internal/journal/read_test.go
+++ b/internal/journal/read_test.go
@@ -779,3 +779,34 @@ func TestRunsMarksAPartlyUndoneRun(t *testing.T) {
t.Errorf("after both reversals: %+v, want undone in full", a)
}
}
+
+// TestRunsIgnoresAPermanentlyDeletedFilesSteps: a file whose chain ended in
+// a permanent delete can never be undone, so its earlier steps do not keep
+// the run partly undone forever (plan 11 review L8).
+func TestRunsIgnoresAPermanentlyDeletedFilesSteps(t *testing.T) {
+ path := filepath.Join(t.TempDir(), "krino.log")
+ w, _ := Open(path)
+ t0 := time.Date(2026, 9, 11, 9, 0, 0, 0, time.UTC)
+ for _, e := range []Entry{
+ {Time: t0, Run: "A", Action: "run-start", Status: "ok"},
+ {Time: t0, Run: "A", Dir: "dl", File: "x.pdf", Step: 1, Action: "move", Status: "ok", Src: "/a/x.pdf", Dst: "/b/x.pdf"},
+ {Time: t0, Run: "A", Dir: "dl", File: "y.pdf", Step: 1, Action: "rename", Status: "ok", Src: "/a/y.pdf", Dst: "/a/z.pdf"},
+ {Time: t0, Run: "A", Dir: "dl", File: "y.pdf", Step: 2, Action: "delete", Status: "ok", Src: "/a/z.pdf"},
+ {Time: t0, Run: "A", Action: "run-end", Status: "ok"},
+ {Time: t0.Add(time.Hour), Run: "B", Action: "run-start", Status: "ok", Detail: "undo of A"},
+ {Time: t0.Add(time.Hour), Run: "B", Dir: "dl", File: "x.pdf", Step: 1, Action: "undo-move", Status: "ok", Src: "/b/x.pdf", Dst: "/a/x.pdf"},
+ {Time: t0.Add(time.Hour), Run: "B", Action: "run-end", Status: "ok"},
+ } {
+ w.Append(e)
+ }
+ w.Close()
+ runs, err := Runs(path, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, r := range runs {
+ if r.ID == "A" && (!r.Undone || r.PartlyUndone) {
+ t.Errorf("run A = %+v; want undone in full: y.pdf could never be reversed", r)
+ }
+ }
+}
diff --git a/man/krino.1 b/man/krino.1
index aa981cc..be370cd 100644
--- a/man/krino.1
+++ b/man/krino.1
@@ -359,7 +359,8 @@ while some of its reversible steps are not reversed \(em declined during the
undo's own review, refused or failed \(em and
.Pq undone
once every one is.
-A permanent delete is not reversible and counts toward neither.
+A file whose steps end in a permanent delete can never be undone, and none
+of its steps count toward either.
.Sh ENVIRONMENT
.Bl -tag -width Ds
.It Ev XDG_CONFIG_HOME