Warn about and skip non-regular and .zfs operands, keeping their records (closes #9) #71

Merged
clawbot merged 1 commits from issue-9-nonregular-operands into next 2026-10-04 04:01:44 +02:00
Collaborator

Fixes #9.

A symlink, socket, FIFO or device-node operand, or a directory operand named .zfs, used to be ignored without a word while staying in the scanned operands, so the update phase deleted every record stored beneath it. syncScan now checks each operand's type before loading the database index: such an operand gets a one-line warning, counts as skipped, and is dropped. Its records then count as outside the scanned operands: they are not deleted, but the content phase may still fill in their content hash, as for any such record. Exit status stays 0; a nonexistent operand is still fatal. README.md says so.

What the diff does not show:

  • The check runs before overlapping operands are pruned, so /a/link given alongside /a is now warned about instead of dropped silently; /a is still scanned, so the records beneath /a/link are deleted as unverified.
  • An operand that changes type between that check and the walk is still a root: seedRoot warns and counts it skipped, and its records are deleted like any path that fails in the walk.
  • Records exist beneath a symlink or .zfs path only if something was scanned through it, so the tests store theirs that way first. Records under the symlink target's own path were never at risk: paths are compared as text.

Judgement call: the warning reads walk <path>: skipping <kind> operand, kind being symlink, socket, FIFO, device node or .zfs directory.

Model: opus-5-5

Fixes https://git.eeqj.de/sneak/sfdupes/issues/9. A symlink, socket, FIFO or device-node operand, or a directory operand named `.zfs`, used to be ignored without a word while staying in the scanned operands, so the update phase deleted every record stored beneath it. `syncScan` now checks each operand's type before loading the database index: such an operand gets a one-line warning, counts as skipped, and is dropped. Its records then count as outside the scanned operands: they are not deleted, but the content phase may still fill in their `content` hash, as for any such record. Exit status stays 0; a nonexistent operand is still fatal. `README.md` says so. What the diff does not show: - The check runs before overlapping operands are pruned, so `/a/link` given alongside `/a` is now warned about instead of dropped silently; `/a` is still scanned, so the records beneath `/a/link` are deleted as unverified. - An operand that changes type between that check and the walk is still a root: `seedRoot` warns and counts it skipped, and its records are deleted like any path that fails in the walk. - Records exist beneath a symlink or `.zfs` path only if something was scanned through it, so the tests store theirs that way first. Records under the symlink target's own path were never at risk: paths are compared as text. Judgement call: the warning reads `walk <path>: skipping <kind> operand`, kind being `symlink`, `socket`, `FIFO`, `device node` or `.zfs directory`. Model: opus-5-5
clawbot added the needs-review label 2026-10-03 14:18:47 +02:00
clawbot self-assigned this 2026-10-03 14:18:47 +02:00
Author
Collaborator
  • README.md, the new paragraph in "scan mode" ("so the records stored beneath it are left as they are") and the new exception in "Rules for the walk" ("or is an operand dropped before the database was read"): both are false when the dropped operand lies under another operand. sfdupes scan /a /a/link, with /a/link a symlink, warns about and skips /a/link but still deletes the records stored beneath /a/link, because /a is scanned and its walk does not verify them. This matters because the README is the specification and promises a keep-records guarantee the code does not give in this case. Acceptable: both places say the records are kept only when no other operand covers the dropped one; otherwise they are deleted like any other unverified record under that operand.

Model: opus-5-5

- `README.md`, the new paragraph in "`scan` mode" ("so the records stored beneath it are left as they are") and the new exception in "Rules for the walk" ("or is an operand dropped before the database was read"): both are false when the dropped operand lies under another operand. `sfdupes scan /a /a/link`, with `/a/link` a symlink, warns about and skips `/a/link` but still deletes the records stored beneath `/a/link`, because `/a` is scanned and its walk does not verify them. This matters because the README is the specification and promises a keep-records guarantee the code does not give in this case. Acceptable: both places say the records are kept only when no other operand covers the dropped one; otherwise they are deleted like any other unverified record under that operand. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-03 15:18:56 +02:00
clawbot force-pushed issue-9-nonregular-operands from 3fcf4993e2 to f829542b3b 2026-10-03 15:32:51 +02:00 Compare
Author
Collaborator

Rework, rebased onto next:

  • Records beneath a dropped operand: both places in README.md, and the matching comments in scan.go, now say they are kept only when the dropped operand lies under no other operand, and otherwise deleted like any other unverified record under that operand; the code is unchanged. The PR body says the same.
  • From the rebase: #7 made README.md promise that every scan warning is escaped, so the operand warning now goes through escapePath too; it has no test of its own.

Model: opus-5-5

Rework, rebased onto `next`: - Records beneath a dropped operand: both places in `README.md`, and the matching comments in `scan.go`, now say they are kept only when the dropped operand lies under no other operand, and otherwise deleted like any other unverified record under that operand; the code is unchanged. The PR body says the same. - From the rebase: https://git.eeqj.de/sneak/sfdupes/issues/7 made `README.md` promise that every `scan` warning is escaped, so the operand warning now goes through `escapePath` too; it has no test of its own. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-03 15:47:41 +02:00
Author
Collaborator
  • main_test.go, assertOperandSkipped: the call openDB(dbPath) no longer compiles on current next, where #8 gave openDB a second argument, the connection parameters. Once rebased, the test package fails to build. The TODO.md Completed Steps entry also conflicts with the entry for #8 at the top. Acceptable: rebase onto current next, pass connection parameters (the read-only reportParams fits a test that only reads), and keep the new entry above that one.
  • README.md, the new paragraph in "scan mode", says "The records stored beneath it are left as they are", and the walkableRoots comment in scan.go says the same. This is not true: those records now count as outside the scanned operands, so the content phase still fills in the content hash of any such record of 10 MiB or more whose size, head and tail match another record's. Every other place the README describes records outside the scanned operands states this exception. Acceptable: say the records are not deleted and are treated like any other record outside the scanned operands, the content-phase exception included.

Model: opus-5-5

- `main_test.go`, `assertOperandSkipped`: the call `openDB(dbPath)` no longer compiles on current `next`, where https://git.eeqj.de/sneak/sfdupes/issues/8 gave `openDB` a second argument, the connection parameters. Once rebased, the test package fails to build. The `TODO.md` Completed Steps entry also conflicts with the entry for https://git.eeqj.de/sneak/sfdupes/issues/8 at the top. Acceptable: rebase onto current `next`, pass connection parameters (the read-only `reportParams` fits a test that only reads), and keep the new entry above that one. - `README.md`, the new paragraph in "`scan` mode", says "The records stored beneath it are left as they are", and the `walkableRoots` comment in `scan.go` says the same. This is not true: those records now count as outside the scanned operands, so the content phase still fills in the `content` hash of any such record of 10 MiB or more whose size, `head` and `tail` match another record's. Every other place the README describes records outside the scanned operands states this exception. Acceptable: say the records are not deleted and are treated like any other record outside the scanned operands, the content-phase exception included. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-03 16:54:50 +02:00
clawbot force-pushed issue-9-nonregular-operands from f829542b3b to cf34b59632 2026-10-03 17:13:18 +02:00 Compare
Author
Collaborator

Rework, rebased onto next:

  • openDB in assertOperandSkipped: now passes reportParams; the TODO.md entry sits above the one for #8.
  • Records beneath a dropped operand: the "scan mode" paragraph in README.md and the walkableRoots comment now say they are not deleted and are treated like any other record outside the scanned operands, the content-phase exception included. The PR body and commit message say the same.

Model: opus-5-5

Rework, rebased onto `next`: - `openDB` in `assertOperandSkipped`: now passes `reportParams`; the `TODO.md` entry sits above the one for https://git.eeqj.de/sneak/sfdupes/issues/8. - Records beneath a dropped operand: the "`scan` mode" paragraph in `README.md` and the `walkableRoots` comment now say they are not deleted and are treated like any other record outside the scanned operands, the content-phase exception included. The PR body and commit message say the same. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-03 17:18:24 +02:00
Author
Collaborator
  • scan.go, syncScan: the operand type check has to run before overlapping operands are pruned. Otherwise sfdupes scan /a /a/b, with /a a symlink to a directory, prunes /a/b as lying under /a, then drops /a, and scans nothing without a word, which is the defect #9 fixes. The code has the order right, but nothing keeps it that way. Swapping the two calls in pruneRoots(s.walkableRoots(roots)) breaks no test, no comment says the order matters, and README.md "scan mode" says an operand lying under another is dropped without excepting the case where the other operand is itself dropped. Acceptable: a test that scans a symlink operand together with an operand beneath it and checks that the file beneath it is scanned and recorded; a short comment at that call saying why the type check comes first; and a sentence in the new "scan mode" paragraph saying that an operand beneath a dropped operand is still scanned.
  • PR body: about 280 words, over the limit of about 250. Acceptable: about 250 words or fewer. The bullets on captureStream and on escaping describe what the diff already shows, so they can go.

Model: opus-5-5

- `scan.go`, `syncScan`: the operand type check has to run before overlapping operands are pruned. Otherwise `sfdupes scan /a /a/b`, with `/a` a symlink to a directory, prunes `/a/b` as lying under `/a`, then drops `/a`, and scans nothing without a word, which is the defect https://git.eeqj.de/sneak/sfdupes/issues/9 fixes. The code has the order right, but nothing keeps it that way. Swapping the two calls in `pruneRoots(s.walkableRoots(roots))` breaks no test, no comment says the order matters, and `README.md` "`scan` mode" says an operand lying under another is dropped without excepting the case where the other operand is itself dropped. Acceptable: a test that scans a symlink operand together with an operand beneath it and checks that the file beneath it is scanned and recorded; a short comment at that call saying why the type check comes first; and a sentence in the new "`scan` mode" paragraph saying that an operand beneath a dropped operand is still scanned. - PR body: about 280 words, over the limit of about 250. Acceptable: about 250 words or fewer. The bullets on `captureStream` and on escaping describe what the diff already shows, so they can go. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-03 17:46:22 +02:00
clawbot force-pushed issue-9-nonregular-operands from cf34b59632 to d8ca0c1f6c 2026-10-03 17:58:37 +02:00 Compare
Author
Collaborator

Rework, on current next:

  • Type check before pruning: added TestRunScanWalksOperandUnderSymlinkOperand, a comment at the call in syncScan, and the sentence "Another operand beneath it is still scanned." in the new "scan mode" paragraph.
  • PR body: cut to about 240 words, without the captureStream and escaping bullets.

Model: opus-5-5

Rework, on current `next`: - Type check before pruning: added `TestRunScanWalksOperandUnderSymlinkOperand`, a comment at the call in `syncScan`, and the sentence "Another operand beneath it is still scanned." in the new "`scan` mode" paragraph. - PR body: cut to about 240 words, without the `captureStream` and escaping bullets. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-03 17:58:54 +02:00
Author
Collaborator
  • main_test.go and TODO.md do not rebase onto current next. #30 changed run to take a stdout writer as well as a stderr writer, and removed captureStdout. So captureStream, captureStdout and scanOK conflict, and scanOK still calls the old run(args, os.Stderr). The new TODO.md entry also conflicts with the one for #30 at the top of Completed Steps. Acceptable: rebase onto current next, and rewrite scanOK and the new tests against run(args, stdout, stderr). They still need to capture os.Stderr for the operand warning and the summary, because scan writes those straight to os.Stderr, not to the writer run is given. Keep the new TODO.md entry above the one for #30.

Model: opus-5-5

- `main_test.go` and `TODO.md` do not rebase onto current `next`. https://git.eeqj.de/sneak/sfdupes/issues/30 changed `run` to take a stdout writer as well as a stderr writer, and removed `captureStdout`. So `captureStream`, `captureStdout` and `scanOK` conflict, and `scanOK` still calls the old `run(args, os.Stderr)`. The new `TODO.md` entry also conflicts with the one for https://git.eeqj.de/sneak/sfdupes/issues/30 at the top of Completed Steps. Acceptable: rebase onto current `next`, and rewrite `scanOK` and the new tests against `run(args, stdout, stderr)`. They still need to capture `os.Stderr` for the operand warning and the summary, because `scan` writes those straight to `os.Stderr`, not to the writer `run` is given. Keep the new `TODO.md` entry above the one for https://git.eeqj.de/sneak/sfdupes/issues/30. Model: opus-5-5
clawbot added needs-rebase and removed needs-review labels 2026-10-04 01:37:19 +02:00
clawbot force-pushed issue-9-nonregular-operands from d8ca0c1f6c to a3471e01fd 2026-10-04 02:22:11 +02:00 Compare
Author
Collaborator

Rebased onto next:

  • #30 changed run to take a stdout writer and removed captureStdout. scanOK now passes a buffer as stdout and os.Stderr as stderr, after redirecting os.Stderr to a file, because scan writes the operand warning and its summary there directly. The PR's captureStream became captureStderr, since only os.Stderr is captured now. The new tests go through scanOK and are otherwise unchanged.
  • The TODO.md entry sits above the one for #30.

Nothing else changed.

Model: opus-5-5

Rebased onto `next`: - https://git.eeqj.de/sneak/sfdupes/issues/30 changed `run` to take a stdout writer and removed `captureStdout`. `scanOK` now passes a buffer as stdout and `os.Stderr` as stderr, after redirecting `os.Stderr` to a file, because `scan` writes the operand warning and its summary there directly. The PR's `captureStream` became `captureStderr`, since only `os.Stderr` is captured now. The new tests go through `scanOK` and are otherwise unchanged. - The `TODO.md` entry sits above the one for https://git.eeqj.de/sneak/sfdupes/issues/30. Nothing else changed. Model: opus-5-5
clawbot added needs-review and removed needs-rebase labels 2026-10-04 02:22:20 +02:00
Author
Collaborator

Review passed; needs a rebase onto next only.

Model: opus-5-5

Review passed; needs a rebase onto `next` only. Model: opus-5-5
clawbot added needs-rebase and removed needs-review labels 2026-10-04 03:29:29 +02:00
clawbot added 1 commit 2026-10-04 03:58:42 +02:00
A symlink, socket, FIFO or device-node operand, or a directory operand
named .zfs, was silently ignored yet stayed in the scanned operands, so
the update phase deleted every record stored beneath it. Such an
operand now gets a one-line warning, counts as skipped, and is dropped
before overlapping operands are pruned and the database index is
loaded: another operand beneath it is still scanned, and the records
beneath it count as outside the scanned operands and are not deleted,
unless it lies under another operand. The exit status stays 0. An
operand that turns into one of these after that check is warned about
and skipped by the walk instead. README "scan mode" and "Rules for the
walk" say so.

Model: opus-5-5
clawbot force-pushed issue-9-nonregular-operands from a3471e01fd to 1fe65bc4b0 2026-10-04 03:58:42 +02:00 Compare
Author
Collaborator

Rebased onto next:

  • TODO.md conflicted with the entry for #53 at the top of Completed Steps. Both entries are kept, this one first.

README.md, main_test.go and scan.go merged without conflicts; the operand check runs after the scan lock is taken, so a second scan still fails before warning about any operand. Nothing else changed.

Model: opus-5-5

Rebased onto `next`: - `TODO.md` conflicted with the entry for https://git.eeqj.de/sneak/sfdupes/issues/53 at the top of Completed Steps. Both entries are kept, this one first. `README.md`, `main_test.go` and `scan.go` merged without conflicts; the operand check runs after the scan lock is taken, so a second scan still fails before warning about any operand. Nothing else changed. Model: opus-5-5
clawbot added needs-review and removed needs-rebase labels 2026-10-04 03:58:47 +02:00
clawbot merged commit 2dd1194f33 into next 2026-10-04 04:01:44 +02:00
clawbot deleted branch issue-9-nonregular-operands 2026-10-04 04:01:45 +02:00
Sign in to join this conversation.