Skip to content

fix: special paths are pruned away below the second level - #1202

Open
VXNCXNX wants to merge 1 commit into
Canop:mainfrom
VXNCXNX:fix/special-paths-pruned-too-early
Open

fix: special paths are pruned away below the second level#1202
VXNCXNX wants to merge 1 commit into
Canop:mainfrom
VXNCXNX:fix/special-paths-pruned-too-early

Conversation

@VXNCXNX

@VXNCXNX VXNCXNX commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

A special_paths entry with sum: "never" is honored at the tree root and its
first level, then silently ignored deeper. Directory sizes include paths the user
asked to leave out.

Config special_paths: { "node_modules": { sum: "never" } }, and a tree where
node_modules sits three levels down, so only the worker threads ever see it
(40 dirs, each with a 4M node_modules/big and a 1M keep):

before:
210M /tmp/sptest/root
 5.3M  d01
 5.3M  d02   (and so on, 40 of them)

after:
 42M /tmp/sptest/root
 1.1M  d01
 1.1M  d02

42M is the 40 keep files, which is the right answer.

Cause

SpecialPaths::reduce prunes the set handed to the deep-summing threads using:

path.to_str()
    .is_some_and(|p| self.pattern.as_str().starts_with(p))

A relative conf entry is not stored verbatim. GlobConf::to_glob turns anything
not starting with / or ~ into **/<name>:

let pattern = format!("**/{}", s);

So the test becomes "**/node_modules".starts_with("/home/dys/dev"), which is
false for every directory except the ones that happen to prefix-match the literal
pattern text. The entry is dropped from the reduced set and the special handling
never applies below. An absolute pattern containing a wildcard, such as
/home/*/.cargo, fails the same way once you descend past the fixed part.

The fix

Compare against the part of the pattern before the first glob metacharacter,
which every matching path must start with, and treat the presence of a wildcard
as permission to match deeper:

  • fixed is at least as long as the directory: keep it if fixed starts with the
    directory, the old test, restricted to the fixed part.
  • fixed is shorter: keep it only if the pattern has a wildcard and the directory
    starts with fixed.

When the path is not valid UTF-8 it now returns true rather than false. That is
the deliberate direction: a false positive costs a few glob matchings, a false
negative silently returns a wrong size.

The cost is real and worth stating: every relative conf entry now stays in the
reduced set at every depth, because **/x genuinely can match anywhere. Pruning
for those patterns is weaker than before. The documentation already notes that
"defining a lot of paths will impact the overall speed"; the previous behaviour
was faster only because it was skipping work it should have done.

Verification

test_can_have_matches_in covers the relative case at depth and at root, an
absolute pattern inside and outside its own branch, and an absolute pattern with
a wildcard.

Restoring the old body fails it:

test path::special_path::special_path_tests::test_can_have_matches_in ... FAILED
thread '...' panicked at src/path/special_path.rs:159:9:
assertion failed: can_have_matches_in("**/node_modules", "/home/dys/dev")

The absolute-pattern assertions pass under that mutation too, which is the point:
they pin the pruning that already worked, so the fix cannot quietly disable it.

cargo test is 60 + 7 passed, 0 failed. Clippy reports nothing on the changed
file.

Disclosure: written with AI assistance (Claude Code). The before and after above come from running two binaries in a real terminal under tmux, not from reading the code, and I ran the mutation check myself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant