diff options
Diffstat (limited to 'internal/engine/apply.go')
| -rw-r--r-- | internal/engine/apply.go | 61 |
1 files changed, 61 insertions, 0 deletions
diff --git a/internal/engine/apply.go b/internal/engine/apply.go index e2d4db8..f54169c 100644 --- a/internal/engine/apply.go +++ b/internal/engine/apply.go @@ -344,6 +344,18 @@ type UndoFile struct { Declined bool } +// Label names the file for display: the directory it belongs to and the +// path within it, except for a file that lies outside any sorted directory +// - one displaced into the Trash by a chain that then deleted itself - whose +// path is absolute and stands alone. Dir stays set on those either way: it +// is what the undo locks. +func (uf UndoFile) Label() string { + if filepath.IsAbs(uf.File) { + return xdg.Abbrev(uf.File) + } + return uf.Dir + "/" + uf.File +} + // UndoStep is the reversal of one logged step. type UndoStep struct { Original journal.Entry // the step being reversed @@ -420,6 +432,13 @@ func (e *Engine) PlanUndo(runID string) (*UndoPlan, error) { if len(uf.Steps) == 0 && uf.Refused == "" { continue } + // A file refused outright still displaced someone else's file into + // the Trash, and that file is a different file - ยง7.4 promises undo + // restores it. Offered on its own, since the refusal is about the + // file that cannot come back, not about this one. + if rest, ok := displacedUndoFile(k.dir, byFile[k], reversed); ok { + up.Files = append(up.Files, rest) + } if uf.Refused == "" && onlyOccupiedDirectoryRemovals(uf.Steps) { // Nothing of the file itself is left to reverse, only directories // the run made that something else still occupies: offering them @@ -585,6 +604,48 @@ func planUndoFile(dir, file string, ents []journal.Entry, reversed map[journal.R return uf } +// displacedUndoFile offers the file a refused chain trashed to make room. +// It applies only to a chain planUndoFile refuses as a whole - a reversible +// chain reverses its own displace as one of its steps - and only to a +// refusal the displaced file is not itself the cause of: a permanent +// delete, which ends the deleted file for good while leaving the file it +// displaced sitting in the Trash, recoverable, with its original path now +// free. +// +// It is keyed by the displaced path rather than the chain's file, because +// that is what it puts back; a plan that named the chain's file twice would +// read as one file being reversed in two places. +func displacedUndoFile(dir string, ents []journal.Entry, reversed map[journal.ReversedKey]int) (UndoFile, bool) { + deleted := false + for _, en := range ents { + if en.Status == "ok" && en.Action == "delete" { + deleted = true + } + } + if !deleted { + return UndoFile{}, false + } + var out UndoFile + for i := len(ents) - 1; i >= 0; i-- { + en := ents[i] + if en.Status != "ok" || en.Action != "displace" { + continue + } + step := reverseStep(en) + if k := (journal.ReversedKey{Dir: dir, File: en.File, Action: step.Action, Src: step.Src}); reversed[k] > 0 { + reversed[k]-- + continue + } + out.Dir = dir + out.File = en.Src + out.Steps = append(out.Steps, step) + if step.Refused != "" && out.Refused == "" { + out.Refused = step.Refused + } + } + return out, len(out.Steps) > 0 +} + // undoProjection tracks what the reversal steps planUndoFile has already // queued (in the order they will execute) will do to the filesystem, so a // later step's occupancy check can tell a real, external occupant from a |
