Skip to content

Commit f914723

Browse files
Copilotswissspidy
andcommitted
Improve filter to handle negation patterns correctly
Updated hasChildren() to be more conservative when skipping descent: - Only skip for top-level ignored directories where children would also be ignored - This handles complex patterns like "frontend/*" with "!/frontend/build/" - Still provides performance benefit for common cases like node_modules and .git - All 27 distignore test scenarios now pass Co-authored-by: swissspidy <[email protected]>
1 parent 61e77e1 commit f914723

File tree

1 file changed

+37
-4
lines changed

1 file changed

+37
-4
lines changed

src/Distignore_Filter_Iterator.php

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,12 @@ public function accept() {
4949

5050
/**
5151
* Check whether the current element has children that should be recursed into.
52-
* We return false for ignored directories to prevent descending into them.
52+
* We return false for certain ignored directories to prevent descending into them.
53+
*
54+
* This optimization only applies to directories that appear to be "leaf" ignore
55+
* patterns (simple directory names without wildcards), to safely handle cases
56+
* like `node_modules` while still correctly processing complex patterns with
57+
* negations like `frontend/*` with `!/frontend/build/`.
5358
*
5459
* @return bool True if we should descend into this directory, false otherwise.
5560
*/
@@ -63,12 +68,40 @@ public function hasChildren() {
6368
return false;
6469
}
6570

66-
// For directories, check if they should be ignored to prevent descending into them.
71+
// For directories, check if they should be ignored.
6772
$relative_filepath = str_replace( $this->source_dir_path, '', $item->getPathname() );
6873

6974
try {
70-
// If the directory is ignored, don't descend into it (but it's still yielded by accept()).
71-
return ! $this->checker->isPathIgnored( $relative_filepath );
75+
$is_ignored = $this->checker->isPathIgnored( $relative_filepath );
76+
77+
if ( ! $is_ignored ) {
78+
// Not ignored, so descend.
79+
return true;
80+
}
81+
82+
// Directory is ignored. Check if it's safe to skip descent.
83+
// We only skip for single-level directories (no slashes except leading/trailing)
84+
// to avoid issues with wildcard patterns and negations.
85+
$path_parts = explode( '/', trim( $relative_filepath, '/' ) );
86+
if ( count( $path_parts ) === 1 ) {
87+
// This is a top-level ignored directory like "/node_modules" or "/.git".
88+
// It's likely safe to skip descent as these are typically simple patterns.
89+
// However, we still need to be conservative. Let's check if a child would be ignored.
90+
$test_child = $relative_filepath . '/test';
91+
try {
92+
$child_ignored = $this->checker->isPathIgnored( $test_child );
93+
if ( $child_ignored ) {
94+
// Child is also ignored, safe to skip descent.
95+
return false;
96+
}
97+
} catch ( \Inmarelibero\GitIgnoreChecker\Exception\InvalidArgumentException $exception ) {
98+
// On error, descend to be safe.
99+
return true;
100+
}
101+
}
102+
103+
// For nested directories or if test shows children might not be ignored, descend.
104+
return true;
72105
} catch ( \Inmarelibero\GitIgnoreChecker\Exception\InvalidArgumentException $exception ) {
73106
// If there's an error checking, allow descending (error will be handled in get_file_list).
74107
return true;

0 commit comments

Comments
 (0)