diff options
Diffstat (limited to 'internal/journal/read.go')
| -rw-r--r-- | internal/journal/read.go | 39 |
1 files changed, 32 insertions, 7 deletions
diff --git a/internal/journal/read.go b/internal/journal/read.go index de06422..48cd6b0 100644 --- a/internal/journal/read.go +++ b/internal/journal/read.go @@ -81,9 +81,12 @@ func ReversedSteps(path, runID string) (map[ReversedKey]int, error) { return out, nil } -// Entries returns every entry belonging to runID, in file order. A line -// that fails to parse is skipped, but Entries fails closed within the run's -// own window - from its run-start line to its run-end line, or to end of +// Entries returns every entry belonging to runID, in file order. Since plan +// 10 (re-review N1), an unparsable line whose run column names another run +// is ignored, and one of this run whose directory and file columns are still +// readable is returned as a "damaged" entry for that file, so undo refuses +// that file alone. Otherwise a line that fails to parse is skipped, but +// Entries fails closed within the run's own window - from its run-start line to its run-end line, or to end of // file when there is no run-end (a crashed run, which is precisely when // corruption is likely): any unparsable line found inside that window sets // the returned error, whether or not the line's own Run column can still be @@ -136,14 +139,25 @@ func Entries(path, runID string) ([]Entry, error) { } continue } - if badLine != 0 { + run, runFound := runFieldOf(line) + if runFound && run != runID { + // Another run's damaged line: runs of different directories can + // interleave, and it says nothing about this one (re-review N1). continue } - if inWindow { - badLine = i + 1 + ours := inWindow || (runFound && run == runID) + if !ours { + continue + } + if dir, file, ok := fileFieldsOf(line); ok { + // A line of this run cut or damaged where its file is still + // readable: that file's chain may be missing a step, so it is + // returned as damaged and PlanUndo refuses just that file; the + // rest of the run stays undoable (re-review N1). + out = append(out, Entry{Run: runID, Dir: dir, File: file, Action: "damaged", Status: "damaged", Detail: fmt.Sprintf("line %d", i+1)}) continue } - if run, found := runFieldOf(line); found && run == runID { + if badLine == 0 { badLine = i + 1 } } @@ -167,6 +181,17 @@ func runFieldOf(line string) (string, bool) { return unescape(f[1]), true } +// fileFieldsOf best-effort extracts a line's directory and file columns when +// the line otherwise fails to parse; ok is false when the line is cut before +// them or names no file (a run-start or run-end line). +func fileFieldsOf(line string) (dir, file string, ok bool) { + f := strings.SplitN(line, "\t", 5) + if len(f) < 5 || f[3] == "" { + return "", "", false + } + return unescape(f[2]), unescape(f[3]), true +} + // Runs summarises every run found in the log, newest first. n <= 0 means // all. As with Entries, an unparsable line is skipped rather than failing // the read - here silently and always, even when it belonged to the run |
