diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-14 21:43:23 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-14 21:43:23 +0200 |
| commit | c497d173b24b1b8247fac9e996e5c0fe690c1769 (patch) | |
| tree | 2318ff1cf393e3a6c2a7d54c89e205ac6a9a5c73 /cmd/krino/undo.go | |
| parent | 360591d6e18d8676a2f86185ed42f46852387f85 (diff) | |
| download | krino-c497d173b24b1b8247fac9e996e5c0fe690c1769.tar.gz krino-c497d173b24b1b8247fac9e996e5c0fe690c1769.zip | |
plan 9: undo review matches review, --min-age validated, future mtimes, rule names, conflict enum, dependency gate, absolute tool paths
Diffstat (limited to 'cmd/krino/undo.go')
| -rw-r--r-- | cmd/krino/undo.go | 55 |
1 files changed, 40 insertions, 15 deletions
diff --git a/cmd/krino/undo.go b/cmd/krino/undo.go index 4c85185..4cefc82 100644 --- a/cmd/krino/undo.go +++ b/cmd/krino/undo.go @@ -47,6 +47,9 @@ func cmdUndo(g *globals, args []string, stdout, stderr io.Writer) int { if code, ok := parse(fs, args, stdout, stderr); !ok { return code } + if g.minAgeSet { + return usageError(stderr, "--min-age applies only to sorting and explain") + } if g.yes && g.dry { return usageError(stderr, "-y and -n cannot be used together") } @@ -187,7 +190,7 @@ func cmdUndo(g *globals, args []string, stdout, stderr io.Writer) int { return 0 } - toApply := finalizeUndoPlan(up, approved) + toApply, notReviewed := finalizeUndoPlan(up, approved, action) // Ruling 2 (Task 7), carried over: journal.Open creates the state // directory and the log file as a side effect of merely being called, @@ -220,7 +223,7 @@ func cmdUndo(g *globals, args []string, stdout, stderr io.Writer) int { fmt.Fprintf(stderr, "krino: %v\n", aerr) return 1 } - fmt.Fprintln(stdout, outcome(p, res.Applied, res.Failed, res.Declined)) + fmt.Fprintln(stdout, withNotReviewed(outcome(p, res.Applied, res.Failed, res.Declined), notReviewed)) if ctx.Err() != nil { return 130 @@ -306,15 +309,26 @@ func releaseUndoLocks(locks []*lock.Lock) []error { // though nothing happens to it, the same as the forward path already does, // so it is kept with Declined set, which tells ApplyUndo to log its steps // as declined rather than reverse them. -func finalizeUndoPlan(up *engine.UndoPlan, approved map[int]bool) *engine.UndoPlan { +// +// After [w] (action 'w'), a reversible file the review never reached is +// left out of the plan entirely, as review's [w] leaves a forward file +// unlogged, and counted in notReviewed (review cli F2). +func finalizeUndoPlan(up *engine.UndoPlan, approved map[int]bool, action rune) (plan *engine.UndoPlan, notReviewed int) { out := &engine.UndoPlan{Run: up.Run} for i, f := range up.Files { - if f.Refused == "" && !approved[i] { - f.Declined = true + if f.Refused == "" { + yes, decided := approved[i] + switch { + case !decided && action == 'w': + notReviewed++ + continue + case !yes: + f.Declined = true + } } out.Files = append(out.Files, f) } - return out + return out, notReviewed } // reviewUndoDir drives the interactive review over the real terminal, @@ -358,12 +372,15 @@ func reviewUndoFiles(in io.Reader, out io.Writer, files []engine.UndoFile, p pal case 'q': return map[int]bool{}, 'q', nil case 'c': - approved, quit, err := reviewUndoPerFile(in, out, files, p) + approved, end, err := reviewUndoPerFile(in, out, files, p) if err != nil { return nil, 0, err } - if quit { + switch end { + case 'q': return map[int]bool{}, 'q', nil + case 'w': + return approved, 'w', nil } return approved, 'c', nil default: @@ -376,8 +393,11 @@ func reviewUndoFiles(in io.Reader, out io.Writer, files []engine.UndoFile, p pal // refused at planning time is never asked about - spec §10 shows it with // its reason and reverses nothing of it regardless of anything chosen here // - but it still gets its own [i/N] line, so the numbering accounts for -// every file in the plan, not just the reversible ones. -func reviewUndoPerFile(in io.Reader, out io.Writer, files []engine.UndoFile, p palette) (approved map[int]bool, quit bool, err error) { +// every file in the plan, not just the reversible ones. It behaves as +// review.go's reviewPerFile does (review cli F2): a no is recorded as false, +// each choice is echoed in red, [w] ends with 'w' leaving unreached files +// out of approved, and [q] ends with 'q'. +func reviewUndoPerFile(in io.Reader, out io.Writer, files []engine.UndoFile, p palette) (approved map[int]bool, end rune, err error) { approved = map[int]bool{} yesRest := false for i, f := range files { @@ -400,30 +420,35 @@ func reviewUndoPerFile(in io.Reader, out io.Writer, files []engine.UndoFile, p p for { key, kerr := readKey(in) if kerr != nil { - return nil, false, kerr + return nil, 0, kerr } + var choice string switch key { case '\r', '\n': continue case 'y': approved[i] = true + choice = "yes" case 'n': - // leave unapproved + approved[i] = false + choice = "no" case 'a': approved[i] = true yesRest = true + choice = "yes, and all remaining" case 'w': - return approved, false, nil + return approved, 'w', nil case 'q': - return nil, true, nil + return nil, 'q', nil default: fmt.Fprintf(out, "%q is not y, n, a, w or q\n", key) continue } + fmt.Fprintln(out, " "+p.bad("→ "+choice)) break } } - return approved, false, nil + return approved, 0, nil } // undoPerFileKeys is undo's per-file prompt: review's without [t] and [d], |
