aboutsummaryrefslogtreecommitdiff
path: root/internal/engine
diff options
context:
space:
mode:
Diffstat (limited to 'internal/engine')
-rw-r--r--internal/engine/apply.go8
-rw-r--r--internal/engine/undo_identity_test.go42
2 files changed, 50 insertions, 0 deletions
diff --git a/internal/engine/apply.go b/internal/engine/apply.go
index cc87f30..ed2fbe5 100644
--- a/internal/engine/apply.go
+++ b/internal/engine/apply.go
@@ -496,6 +496,14 @@ func isFileAffecting(action string) bool {
// overrides a real occupant that this same chain is itself about to clear.
func planUndoFile(dir, file string, ents []journal.Entry, reversed map[journal.ReversedKey]int) UndoFile {
uf := UndoFile{File: file, Dir: dir}
+ for _, en := range ents {
+ if en.Action == "damaged" {
+ // A log line of this file is cut or damaged (journal.Entries): a
+ // step may be missing from its chain, so none of it is reversed.
+ uf.Refused = fmt.Sprintf("its log is damaged (%s); a step may not be recorded", en.Detail)
+ return uf
+ }
+ }
proj := newUndoProjection()
for i := len(ents) - 1; i >= 0; i-- {
en := ents[i]
diff --git a/internal/engine/undo_identity_test.go b/internal/engine/undo_identity_test.go
index 23e0bf9..5040ece 100644
--- a/internal/engine/undo_identity_test.go
+++ b/internal/engine/undo_identity_test.go
@@ -317,3 +317,45 @@ func TestApplyReportsAStepThatCouldNotBeLogged(t *testing.T) {
}
}
}
+
+// TestUndoRefusesOnlyTheFileWithADamagedLine: a crash that cuts one file's
+// log line refuses that file; the other files of the run are still undone
+// (re-review N1).
+func TestUndoRefusesOnlyTheFileWithADamagedLine(t *testing.T) {
+ e, run, h, logPath := appliedRun(t, map[string]map[string]string{"dl": {"a.pdf": "one", "b.pdf": "two"}},
+ map[string]string{"dl": "(path \"~/dl\")\n(rule \"r\" (move \"Out\"))\n"})
+ raw, err := os.ReadFile(logPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ lines := strings.Split(strings.TrimRight(string(raw), "\n"), "\n")
+ for i, l := range lines {
+ if strings.Contains(l, "\tb.pdf\t") && strings.Contains(l, "\tmove\t") {
+ lines[i] = l[:len(l)/2] // cut mid-write
+ }
+ }
+ if err := os.WriteFile(logPath, []byte(strings.Join(lines, "\n")+"\n"), 0o644); err != nil {
+ t.Fatal(err)
+ }
+ up, err := e.PlanUndo(run)
+ if err != nil {
+ t.Fatalf("PlanUndo refused the whole run: %v", err)
+ }
+ if f := undoFileNamed(t, up, "dl", "b.pdf"); !strings.Contains(f.Refused, "damaged") {
+ t.Errorf("b.pdf: Refused %q; want its damaged log named", f.Refused)
+ }
+ if f := undoFileNamed(t, up, "dl", "a.pdf"); f.Refused != "" {
+ t.Errorf("a.pdf refused: %q", f.Refused)
+ }
+ j, err := journal.Open(logPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer j.Close()
+ if _, err := e.ApplyUndo(context.Background(), up, j, journal.NewRunID(time.Now())); err != nil {
+ t.Fatal(err)
+ }
+ if b, err := os.ReadFile(filepath.Join(h, "dl", "a.pdf")); err != nil || string(b) != "one" {
+ t.Errorf("a.pdf not restored: %q %v", b, err)
+ }
+}