@@ -21,10 +21,15 @@ final class VcsIgnoredFilterIterator extends \FilterIterator
2121 private $ baseDir ;
2222
2323 /**
24- * @var array<string, string|null>
24+ * @var array<string, array{0: string, 1: string} |null>
2525 */
2626 private $ gitignoreFilesCache = [];
2727
28+ /**
29+ * @var array<string, bool>
30+ */
31+ private $ ignoredPathsCache = [];
32+
2833 public function __construct (\Iterator $ iterator , string $ baseDir )
2934 {
3035 $ this ->baseDir = $ this ->normalizePath ($ baseDir );
@@ -37,25 +42,50 @@ public function accept(): bool
3742 $ file = $ this ->current ();
3843
3944 $ fileRealPath = $ this ->normalizePath ($ file ->getRealPath ());
40- if ($ file ->isDir () && !str_ends_with ($ fileRealPath , '/ ' )) {
45+
46+ return !$ this ->isIgnored ($ fileRealPath );
47+ }
48+
49+ private function isIgnored (string $ fileRealPath ): bool
50+ {
51+ if (is_dir ($ fileRealPath ) && !str_ends_with ($ fileRealPath , '/ ' )) {
4152 $ fileRealPath .= '/ ' ;
4253 }
4354
55+ if (isset ($ this ->ignoredPathsCache [$ fileRealPath ])) {
56+ return $ this ->ignoredPathsCache [$ fileRealPath ];
57+ }
58+
59+ $ ignored = false ;
60+
4461 foreach ($ this ->parentsDirectoryDownward ($ fileRealPath ) as $ parentDirectory ) {
62+ if ($ this ->isIgnored ($ parentDirectory )) {
63+ $ ignored = true ;
64+
65+ // rules in ignored directories are ignored, no need to check further.
66+ break ;
67+ }
68+
4569 $ fileRelativePath = substr ($ fileRealPath , \strlen ($ parentDirectory ) + 1 );
4670
47- $ regex = $ this ->readGitignoreFile ("{$ parentDirectory }/.gitignore " );
71+ if (null === $ regexps = $ this ->readGitignoreFile ("{$ parentDirectory }/.gitignore " )) {
72+ continue ;
73+ }
74+
75+ [$ exclusionRegex , $ inclusionRegex ] = $ regexps ;
76+
77+ if (preg_match ($ exclusionRegex , $ fileRelativePath )) {
78+ $ ignored = true ;
4879
49- if (null !== $ regex && preg_match ($ regex , $ fileRelativePath )) {
50- return false ;
80+ continue ;
5181 }
5282
53- if (0 !== strpos ( $ parentDirectory , $ this -> baseDir )) {
54- break ;
83+ if (preg_match ( $ inclusionRegex , $ fileRelativePath )) {
84+ $ ignored = false ;
5585 }
5686 }
5787
58- return true ;
88+ return $ this -> ignoredPathsCache [ $ fileRealPath ] = $ ignored ;
5989 }
6090
6191 /**
@@ -87,7 +117,10 @@ private function parentsDirectoryDownward(string $fileRealPath): array
87117 return array_reverse ($ parentDirectories );
88118 }
89119
90- private function readGitignoreFile (string $ path ): ?string
120+ /**
121+ * @return array{0: string, 1: string}|null
122+ */
123+ private function readGitignoreFile (string $ path ): ?array
91124 {
92125 if (\array_key_exists ($ path , $ this ->gitignoreFilesCache )) {
93126 return $ this ->gitignoreFilesCache [$ path ];
@@ -101,7 +134,12 @@ private function readGitignoreFile(string $path): ?string
101134 throw new \RuntimeException ("The \"ignoreVCSIgnored \" option cannot be used by the Finder as the \"{$ path }\" file is not readable. " );
102135 }
103136
104- return $ this ->gitignoreFilesCache [$ path ] = Gitignore::toRegex (file_get_contents ($ path ));
137+ $ gitignoreFileContent = file_get_contents ($ path );
138+
139+ return $ this ->gitignoreFilesCache [$ path ] = [
140+ Gitignore::toRegex ($ gitignoreFileContent ),
141+ Gitignore::toRegexMatchingNegatedPatterns ($ gitignoreFileContent ),
142+ ];
105143 }
106144
107145 private function normalizePath (string $ path ): string
0 commit comments