diff --git a/src/Documentation/Git/Repository.php b/src/Documentation/Git/Repository.php index bf79370..da94412 100644 --- a/src/Documentation/Git/Repository.php +++ b/src/Documentation/Git/Repository.php @@ -3,7 +3,10 @@ namespace Synapse\Documentation\Git; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; use RuntimeException; +use UnexpectedValueException; /** * Represents a git repository for documentation @@ -103,30 +106,36 @@ public function getMarkdownFiles(): array $searchPath .= DS . str_replace('/', DS, $this->root); } - if (!is_dir($searchPath)) { + if (!is_dir($searchPath) || !is_readable($searchPath)) { return []; } - $command = sprintf( - 'find %s -type f -name "*.md" 2>/dev/null', - escapeshellarg($searchPath), - ); - - $output = []; - exec($command, $output); - $files = []; $pathPrefix = $this->path . DS; $pathPrefixLen = strlen($pathPrefix); - foreach ($output as $file) { - // Convert absolute path to relative path from repo root - if (str_starts_with($file, $pathPrefix)) { - $relativePath = substr($file, $pathPrefixLen); - // Normalize directory separators - $relativePath = str_replace(DS, '/', $relativePath); - $files[] = $relativePath; + try { + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( + $searchPath, + RecursiveDirectoryIterator::SKIP_DOTS, + ), + ); + + foreach ($iterator as $file) { + if ($file->isFile() && $file->getExtension() === 'md') { + $absolutePath = $file->getPathname(); + if (str_starts_with($absolutePath, $pathPrefix)) { + $relativePath = substr($absolutePath, $pathPrefixLen); + $relativePath = str_replace(DS, '/', $relativePath); + $files[] = $relativePath; + } + } } + } catch (UnexpectedValueException $unexpectedValueException) { + // Permission denied or unreadable subdirectory encountered + // Return files collected so far to allow partial indexing + return $files; } return $files; diff --git a/tests/TestCase/Documentation/Git/RepositoryTest.php b/tests/TestCase/Documentation/Git/RepositoryTest.php index 116d2ae..a046fb7 100644 --- a/tests/TestCase/Documentation/Git/RepositoryTest.php +++ b/tests/TestCase/Documentation/Git/RepositoryTest.php @@ -537,4 +537,35 @@ public function testMultiplePullsTracked(): void $repository->pull(); $this->assertEquals(3, $this->gitAdapter->getPullCount($repoPath)); } + + /** + * Test getMarkdownFiles normalizes path separators to forward slashes + * + * Verifies that the RecursiveDirectoryIterator implementation returns + * paths with forward slashes regardless of the operating system. + */ + public function testGetMarkdownFilesNormalizesPathSeparators(): void + { + $repoPath = $this->testDir . 'test-repo'; + mkdir($repoPath . DS . '.git', 0755, true); + mkdir($repoPath . DS . 'docs' . DS . 'guides' . DS . 'advanced', 0755, true); + + file_put_contents($repoPath . DS . 'docs' . DS . 'guides' . DS . 'advanced' . DS . 'tips.md', '# Tips'); + + $repository = new Repository( + url: 'https://github.com/test/repo.git', + branch: 'main', + path: $repoPath, + gitAdapter: $this->gitAdapter, + ); + + $files = $repository->getMarkdownFiles(); + + // All paths should use forward slashes, never backslashes + foreach ($files as $file) { + $this->assertStringNotContainsString('\\', $file, 'Path should not contain backslashes: ' . $file); + } + + $this->assertContains('docs/guides/advanced/tips.md', $files); + } }