diff --git a/src/Skipper/Matcher/FileInfoMatcher.php b/src/Skipper/Matcher/FileInfoMatcher.php index 19eef3ee00d..5f384eb57a1 100644 --- a/src/Skipper/Matcher/FileInfoMatcher.php +++ b/src/Skipper/Matcher/FileInfoMatcher.php @@ -11,6 +11,13 @@ */ final class FileInfoMatcher { + /** + * PHP does not cache a failed realpath() lookup, and a wildcard pattern fails it for every file + * + * @var array + */ + private array $realpathByPattern = []; + /** * Returns the original (un-normalized) pattern that matched, so callers can report the exact * configured path. Returns null when no pattern matches. @@ -90,7 +97,7 @@ private function matchFnmatch(string $matchingPath, string $filePath): bool private function matchRealpath(string $matchingPath, string $filePath): bool { - $realPathMatchingPath = realpath($matchingPath); + $realPathMatchingPath = $this->realpathByPattern[$matchingPath] ??= realpath($matchingPath); if ($realPathMatchingPath === false) { return false; } diff --git a/tests/Skipper/Matcher/FileInfoMatcherTest.php b/tests/Skipper/Matcher/FileInfoMatcherTest.php index 72f92ee4dc7..97c3dd76d48 100644 --- a/tests/Skipper/Matcher/FileInfoMatcherTest.php +++ b/tests/Skipper/Matcher/FileInfoMatcherTest.php @@ -35,6 +35,22 @@ public function testMatchPatternReturnsNullWhenNoPatternMatches(): void $this->assertNull($matchedPattern); } + public function testRealPathOfEachPatternIsResolvedSeparatelyOnRepeatedCalls(): void + { + // the file paths are not real paths, so only the realpath() comparison can match them + $pathInPattern = __DIR__ . '/Fixture/path/in'; + $inPattern = __DIR__ . '/Fixture/in'; + $patterns = [$pathInPattern, $inPattern]; + + $pathInFile = __DIR__ . '/Fixture/path/with/../in/it/KeepThisFile.txt'; + $inFile = __DIR__ . '/Fixture/path/../in/it/KeepThisFile.txt'; + + for ($i = 0; $i < 2; ++$i) { + $this->assertSame($pathInPattern, $this->fileInfoMatcher->matchPattern($pathInFile, $patterns)); + $this->assertSame($inPattern, $this->fileInfoMatcher->matchPattern($inFile, $patterns)); + } + } + #[DataProvider('providePatterns')] public function testPatternNormalization(string $filePath, string $filePattern, bool $shouldMatch): void {