aboutsummaryrefslogtreecommitdiff
path: root/cmd/krino/undo.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 21:43:23 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 21:43:23 +0200
commitc497d173b24b1b8247fac9e996e5c0fe690c1769 (patch)
tree2318ff1cf393e3a6c2a7d54c89e205ac6a9a5c73 /cmd/krino/undo.go
parent360591d6e18d8676a2f86185ed42f46852387f85 (diff)
downloadkrino-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.go55
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],