aboutsummaryrefslogtreecommitdiff
path: root/internal/engine/apply.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 21:33:39 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 21:33:39 +0200
commit93cf0729ec7cf3129de0808cbcc3403b86d86fff (patch)
treed04bf6825019b68a6e48f4b9e6a5dbdfa0e56c64 /internal/engine/apply.go
parent643eac4a2cba7b34b8a33af1b3dbdc54d9816a53 (diff)
downloadkrino-93cf0729ec7cf3129de0808cbcc3403b86d86fff.tar.gz
krino-93cf0729ec7cf3129de0808cbcc3403b86d86fff.zip
plan 9: an interrupted or failed undo can be finished
Diffstat (limited to 'internal/engine/apply.go')
-rw-r--r--internal/engine/apply.go19
1 files changed, 17 insertions, 2 deletions
diff --git a/internal/engine/apply.go b/internal/engine/apply.go
index 94d65e4..e109aaf 100644
--- a/internal/engine/apply.go
+++ b/internal/engine/apply.go
@@ -343,6 +343,14 @@ func (e *Engine) PlanUndo(runID string) (*UndoPlan, error) {
return nil, fmt.Errorf("engine: plan undo: run %s is itself an undo and cannot be undone", runID)
}
+ // Reversals an earlier undo of this same run already completed are not
+ // offered again (review M10): an undo that stopped part way can be
+ // finished by undoing the run once more.
+ reversed, err := journal.ReversedSteps(e.Config.LogFile(), runID)
+ if err != nil {
+ return nil, fmt.Errorf("engine: plan undo: %w", err)
+ }
+
// Entries are grouped by directory and file together (review M7): one run
// spans every directory, and two directories can each hold a file of the
// same name.
@@ -362,7 +370,7 @@ func (e *Engine) PlanUndo(runID string) (*UndoPlan, error) {
up := &UndoPlan{Run: runID}
for _, k := range order {
- uf := planUndoFile(k.dir, k.file, byFile[k])
+ uf := planUndoFile(k.dir, k.file, byFile[k], reversed)
// Critical finding, Task 8's review: a file every one of whose
// entries has Status != "ok" (declined by the ORIGINAL run's own
// review, or skipped, or failed before anything happened) yields
@@ -464,7 +472,7 @@ func isFileAffecting(action string) bool {
// two steps of one file that touch the same path, and every un-contended
// check keeps behaving exactly as before, since the projection only ever
// overrides a real occupant that this same chain is itself about to clear.
-func planUndoFile(dir, file string, ents []journal.Entry) UndoFile {
+func planUndoFile(dir, file string, ents []journal.Entry, reversed map[journal.ReversedKey]int) UndoFile {
uf := UndoFile{File: file, Dir: dir}
proj := newUndoProjection()
for i := len(ents) - 1; i >= 0; i-- {
@@ -483,6 +491,13 @@ func planUndoFile(dir, file string, ents []journal.Entry) UndoFile {
break
}
step := reverseStep(en)
+ if k := (journal.ReversedKey{Dir: dir, File: file, Action: step.Action, Src: step.Src}); reversed[k] > 0 {
+ // An earlier undo of this run already reversed this step: the
+ // disk already shows it, and it is not offered again.
+ reversed[k]--
+ proj.record(step)
+ continue
+ }
if (en.Action == "move" || en.Action == "rename") && proj.occupied[en.Dst] {
// A reversal already queued for this same file puts it back at
// en.Dst before this one runs, and that reversal was checked