2 Commits
Author SHA1 Message Date
sneak 5b7a611113 Open files to hash without blocking and reject non-regular ones (closes #100)
check / check (push) Canceled after 0s
A regular file replaced by a FIFO after the walk statted it made the
hash phase's open wait forever for a writer, so the scan never ended
and kept the lock. The hash and content phases now open each file with
O_NONBLOCK and check with fstat that it is still a regular file;
anything else is a per-path error, warned about and skipped. The new
test replaces a walked file with a FIFO before the hash phase reads it.

Model: opus-5-5
2026-10-14 18:03:52 +00:00
clawbot a4d76af8fe Redraw a finished bar below a warning for its last file (closes #102)
check / check (push) Canceled after 0s
On a terminal, warnf clears the bar's line before it prints a warning.
When the warning is for the last item of a phase, the bar has already
drawn its finished frame and the progress library draws nothing more
for a finished bar, so the warning took the bar's place for good. warnf
now draws the finished bar's last frame again below the warning.
Output when stderr is not a terminal does not change.

Model: opus-5-5
2026-10-14 19:55:58 +02:00
3 changed files with 33 additions and 0 deletions
+4
View File
@@ -34,6 +34,10 @@
statted is warned about and skipped instead of hanging the scan (2026-10-14,
https://git.eeqj.de/sneak/sfdupes/issues/100)
- on a terminal, a warning for the last file of a phase no longer erases the
phase's finished bar: the bar is drawn again below the warning (2026-10-14,
https://git.eeqj.de/sneak/sfdupes/issues/102)
- `scan` records mtime to the nanosecond, as whole seconds in `mtime` plus
`mtime_nsec`, and compares it at that resolution, so a same-size rewrite
within the same second is re-hashed (2026-10-07,
+7
View File
@@ -139,6 +139,13 @@ func (p *progress) warnf(format string, args ...any) {
}
fmt.Fprintln(os.Stderr, msg)
// The library never draws a finished bar again, so the warning for
// a pass's last item would erase it for good: draw its last frame
// again below the warning.
if p.bar != nil && p.bar.IsFinished() {
fmt.Fprint(os.Stderr, p.bar.String())
}
}
// finish terminates the pass's display. A bar whose pass stopped short
+22
View File
@@ -187,3 +187,25 @@ func TestBarStoppedShortKeepsCount(t *testing.T) {
t.Errorf("terminal shows %q, want a count of 2 of 10", shown)
}
}
// TestWarningAfterLastItemKeepsBar checks that a warning printed after
// a pass's last item, as for a failed read of the last file, leaves the
// pass's finished bar on the terminal below the warning.
//
//nolint:paralleltest // captureStderr replaces the process-wide os.Stderr
func TestWarningAfterLastItemKeepsBar(t *testing.T) {
stderr := captureStderr(t)
p := &progress{
label: "hash", total: 2, start: time.Now(),
bar: newBar("hash", 2),
}
p.increment()
p.increment()
p.warnf("warning")
p.finish()
if shown := lastFrame(stderr()); !strings.Contains(shown, "(2/2,") {
t.Errorf("terminal shows %q, want a count of 2 of 2", shown)
}
}