aboutsummaryrefslogtreecommitdiff
path: root/internal/engine
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-16 09:39:59 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-16 09:39:59 +0200
commit6ef83d6bdfb6120f9e1fbd145e0bc463196103d1 (patch)
tree8eee1f2c29dcbef4ed96b23b08273d42ba0b5d49 /internal/engine
parent27eb6353030953e91a45b0104bd0567e53622090 (diff)
downloadkrino-6ef83d6bdfb6120f9e1fbd145e0bc463196103d1.tar.gz
krino-6ef83d6bdfb6120f9e1fbd145e0bc463196103d1.zip
gui: History and undo tab; each plan and undo is its own run
Diffstat (limited to 'internal/engine')
-rw-r--r--internal/engine/apply.go14
-rw-r--r--internal/engine/apply_test.go43
2 files changed, 55 insertions, 2 deletions
diff --git a/internal/engine/apply.go b/internal/engine/apply.go
index 46026ba..0d894c6 100644
--- a/internal/engine/apply.go
+++ b/internal/engine/apply.go
@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"os"
+ "path"
"path/filepath"
"sort"
"strings"
@@ -990,7 +991,7 @@ func (e *Engine) declineUndoFile(f UndoFile, j *journal.Writer, run string) (Fil
return FileResult{}, err
}
}
- return FileResult{Steps: steps}, nil
+ return FileResult{File: undoFileIdentity(f), Steps: steps}, nil
}
// undoFile executes every step of f in order (already last-original-step
@@ -1034,7 +1035,16 @@ func (e *Engine) undoFile(f UndoFile, j *journal.Writer, run string) (FileResult
return FileResult{}, err
}
}
- return FileResult{Steps: steps}, nil
+ return FileResult{File: undoFileIdentity(f), Steps: steps}, nil
+}
+
+// undoFileIdentity is all an undo result can honestly say about the file it
+// reversed: the log records its path relative to the directory root and
+// nothing else - the file's size, mode and times belong to a state that no
+// longer exists. A front end showing a row per file matches on Rel (GUI
+// design §4); it must not read the rest.
+func undoFileIdentity(f UndoFile) scan.File {
+ return scan.File{Rel: f.File, Name: path.Base(f.File)}
}
// runUndoStep actually carries out one reversal. It reuses apply.StepResult
diff --git a/internal/engine/apply_test.go b/internal/engine/apply_test.go
index 104fb73..142e4d2 100644
--- a/internal/engine/apply_test.go
+++ b/internal/engine/apply_test.go
@@ -5,6 +5,7 @@ package engine
import (
"context"
"os"
+ "path"
"path/filepath"
"strings"
"testing"
@@ -461,6 +462,48 @@ func TestApplyUndoRestoresMovedFile(t *testing.T) {
}
}
+// TestApplyUndoNamesItsFiles: every file in an undo result says which file
+// it is, reversed or declined, so a front end showing a row per file can
+// put each outcome on the right row (GUI design §4). The log is the only
+// source of that name, so Rel and Name are all an undo result can carry.
+func TestApplyUndoNamesItsFiles(t *testing.T) {
+ h, e, dp, j, run := applyFixture(t)
+ if _, err := e.Apply(context.Background(), dp, map[string]bool{"a.pdf": true}, j, run); err != nil {
+ t.Fatal(err)
+ }
+ j.Close()
+
+ j2, err := journal.Open(filepath.Join(h, ".local", "state", "krino", "krino.log"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer j2.Close()
+
+ // Declined first - nothing is reversed, so the run is still undoable -
+ // then reversed for real. Both paths must name the file.
+ for _, declined := range []bool{true, false} {
+ up, err := e.PlanUndo(run)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(up.Files) != 1 || up.Files[0].Refused != "" {
+ t.Fatalf("undo plan = %+v", up.Files)
+ }
+ up.Files[0].Declined = declined
+ res, err := e.ApplyUndo(context.Background(), up, j2, journal.NewRunID(time.Now()))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(res.Files) != 1 {
+ t.Fatalf("declined=%v: result files = %+v, want one", declined, res.Files)
+ }
+ fr := res.Files[0]
+ if fr.File.Rel != "a.pdf" || fr.File.Name != path.Base("a.pdf") {
+ t.Errorf("declined=%v: file = %+v, want a.pdf", declined, fr.File)
+ }
+ }
+}
+
// TestApplyUndoSkipsRefusedFiles: rule 4 enforced at execution time too - a
// refused file must come back from ApplyUndo untouched.
func TestApplyUndoSkipsRefusedFiles(t *testing.T) {