1 Commits
Author SHA1 Message Date
clawbot da75b713aa Add sfdupes dwim: scan / then print duplicate trees (closes #154)
check / check (push) Waiting to run
sfdupes dwim, as sneak ordered: with no arguments it runs what scan / runs with no flags, default excludes on, then what trees runs, printing the duplicate trees largest first on stdout. Its last line, on stderr, says that sfdupes trees prints the report again. It needs no setup beyond what scan creates. SIGINT or SIGTERM during the scan stops it as it stops scan, exit 1, with no report; during the report a signal ends it at once, as it ends trees. A positional argument is a usage error, exit 2.

Model: opus-5-5
2026-11-06 20:27:24 +01:00
4 changed files with 21 additions and 15 deletions
+7 -4
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 > dupetrees.tsv
sfdupes dwim
sfdupes --version
sfdupes [command] --help
```
@@ -828,7 +828,8 @@ 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.
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`.
### `info` mode
@@ -886,6 +887,8 @@ 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
@@ -1169,8 +1172,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, or a `scan` or `dwim` stopped by `SIGINT` or `SIGTERM`. A
fatal error is one of these:
- `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:
- 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");
+9 -3
View File
@@ -11,10 +11,16 @@ 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. An error from the scan, an interrupt
// included, ends it before the report.
// 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.
func runDwim(ctx context.Context, root string, stdout io.Writer) error {
err := runScan(ctx, []string{root}, runtime.NumCPU(), false, false)
scanCtx, stop := interruptContext(ctx)
err := runScan(scanCtx, []string{root}, runtime.NumCPU(), false, false)
stop()
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, "small1/f.bin", pattern(1, 300))),
filepath.Dir(writeFile(t, root, "small2/f.bin", pattern(1, 300))),
filepath.Dir(writeFile(t, root, "a1/f.bin", pattern(1, 300))),
filepath.Dir(writeFile(t, root, "a2/f.bin", pattern(1, 300))),
}
large := []string{
filepath.Dir(writeFile(t, root, "large1/f.bin", pattern(2, 5000))),
filepath.Dir(writeFile(t, root, "large2/f.bin", pattern(2, 5000))),
filepath.Dir(writeFile(t, root, "b1/f.bin", pattern(2, 5000))),
filepath.Dir(writeFile(t, root, "b2/f.bin", pattern(2, 5000))),
}
var stdout bytes.Buffer
+1 -4
View File
@@ -14,7 +14,7 @@
// sfdupes report > dupes.tsv
// sfdupes trees > dupetrees.tsv
// sfdupes info
// sfdupes dwim > dupetrees.tsv
// sfdupes dwim
// sfdupes --version
//
// See README.md for the complete specification.
@@ -208,9 +208,6 @@ 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)
}),
}