diff options
Diffstat (limited to 'internal/engine')
| -rw-r--r-- | internal/engine/apply.go | 14 | ||||
| -rw-r--r-- | internal/engine/apply_test.go | 43 |
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) { |
