1 Commits
Author SHA1 Message Date
sneak 34e73c1527 Add sfdupes dwim: scan / then print duplicate trees (closes #154)
check / check (push) Canceled after 0s
dwim runs what "sfdupes scan /" runs with no flags, then what
"sfdupes trees" runs, and ends with a stderr line saying that
"sfdupes trees" prints the report again. An interrupt or fatal scan
error ends it before the report, as scan ends.

Model: opus-5-5
2026-11-06 19:10:23 +00:00
4 changed files with 15 additions and 21 deletions
+4 -7
View File
@@ -256,7 +256,7 @@ sfdupes scan [--workers N] [-x] [--no-default-excludes] PATH...
sfdupes report > dupes.tsv
sfdupes trees > dupetrees.tsv
sfdupes info
sfdupes dwim
sfdupes dwim > dupetrees.tsv
sfdupes --version
sfdupes [command] --help
```
@@ -828,8 +828,7 @@ even after the terminal is gone.
`SIGINT` or `SIGTERM` stops the scan as it stops `scan` (see "Stopping a scan").
`dwim` then prints no report and exits 1. A fatal scan error also ends it before
the report, with the same message and exit code as `scan`. Warnings for skipped
files do not stop it. `dwim` stops catching signals when the scan ends, so a
signal during the report ends it at once, as it ends `trees`.
files do not stop it.
### `info` mode
@@ -887,8 +886,6 @@ scan: new database /var/lib/sfdupes/db.sqlite (default)
trees: database /srv/tmp/sfdupes/db.db (from SFDUPES_DATABASE)
```
`dwim` prints the `scan` line first, and the `trees` line after the scan.
A relative path is printed with the working directory in front of it. The
working directory is printed as `pwd -P` prints it, with its symlinks resolved.
The rest of the path is printed as set, so a `..` in it stays. The line names
@@ -1172,8 +1169,8 @@ showing the count it stopped at.
### Error handling and exit codes
- `0`: success, even when files were skipped with warnings.
- `1`: a fatal error, a `scan` stopped by `SIGINT` or `SIGTERM`, or a `dwim`
stopped by either during its scan. A fatal error is one of these:
- `1`: a fatal error, or a `scan` or `dwim` stopped by `SIGINT` or `SIGTERM`. A
fatal error is one of these:
- an operand that does not exist, or whose real path `scan` cannot find;
- a list of mounted filesystems that `scan` cannot read;
- another `scan` holding the lock (see "The scan lock");
+3 -9
View File
@@ -11,16 +11,10 @@ import (
// runDwim implements the dwim subcommand: it runs what "sfdupes scan
// root" runs with no flags, then what "sfdupes trees" runs, and ends
// with a line on stderr saying how to print the report again. The dwim
// command passes "/" as root. It catches SIGINT and SIGTERM as scan
// does, but only during the scan. An error from the scan, an interrupt
// included, ends it before the report. During the report, a signal
// ends it at once, as it ends trees.
// command passes "/" as root. An error from the scan, an interrupt
// included, ends it before the report.
func runDwim(ctx context.Context, root string, stdout io.Writer) error {
scanCtx, stop := interruptContext(ctx)
err := runScan(scanCtx, []string{root}, runtime.NumCPU(), false, false)
stop()
err := runScan(ctx, []string{root}, runtime.NumCPU(), false, false)
if err != nil {
return err
}
+4 -4
View File
@@ -16,12 +16,12 @@ func TestRunDwimPrintsLargestTreesFirst(t *testing.T) {
root := realTempDir(t)
small := []string{
filepath.Dir(writeFile(t, root, "a1/f.bin", pattern(1, 300))),
filepath.Dir(writeFile(t, root, "a2/f.bin", pattern(1, 300))),
filepath.Dir(writeFile(t, root, "small1/f.bin", pattern(1, 300))),
filepath.Dir(writeFile(t, root, "small2/f.bin", pattern(1, 300))),
}
large := []string{
filepath.Dir(writeFile(t, root, "b1/f.bin", pattern(2, 5000))),
filepath.Dir(writeFile(t, root, "b2/f.bin", pattern(2, 5000))),
filepath.Dir(writeFile(t, root, "large1/f.bin", pattern(2, 5000))),
filepath.Dir(writeFile(t, root, "large2/f.bin", pattern(2, 5000))),
}
var stdout bytes.Buffer
+4 -1
View File
@@ -14,7 +14,7 @@
// sfdupes report > dupes.tsv
// sfdupes trees > dupetrees.tsv
// sfdupes info
// sfdupes dwim
// sfdupes dwim > dupetrees.tsv
// sfdupes --version
//
// See README.md for the complete specification.
@@ -208,6 +208,9 @@ func newRootCommand(stdout, stderr io.Writer) *cobra.Command {
Short: "Scan / with the default excludes, then print the duplicate-tree report",
Args: cobra.NoArgs,
RunE: runE(func(ctx context.Context, _ []string) error {
ctx, stop := interruptContext(ctx)
defer stop()
return runDwim(ctx, "/", stdout)
}),
}