diff --git a/common/src/__tests__/project-file-tree.test.ts b/common/src/__tests__/project-file-tree.test.ts index ba3dad6694..bf5a1d858d 100644 --- a/common/src/__tests__/project-file-tree.test.ts +++ b/common/src/__tests__/project-file-tree.test.ts @@ -88,6 +88,28 @@ describe('getProjectFileTree', () => { expect(filePaths).toEqual(['app/src/main/Inventory.kt']) }) + it('does not stat ignore files when none exist in directory entries', async () => { + const root = '/repo' + const fs = createFsWithFiles(root, ['src/index.ts', 'src/util.ts']) + const statCalls: string[] = [] + const originalStat = fs.stat + ;(fs as any).stat = async (p: any, ...args: any[]) => { + statCalls.push(String(p)) + return (originalStat as any)(p, ...args) + } + + await getProjectFileTree({ projectRoot: root, fs }) + + // None of the stat calls should be for ignore files + const ignoreStatCalls = statCalls.filter( + (p) => + p.endsWith('.gitignore') || + p.endsWith('.codebuffignore') || + p.endsWith('.manicodeignore'), + ) + expect(ignoreStatCalls).toHaveLength(0) + }) + it('prunes directories ignored by a rule in a nested .gitignore', async () => { const root = '/repo' const fs = createFsWithFiles(root, [ diff --git a/common/src/project-file-tree.ts b/common/src/project-file-tree.ts index 5efa469ea9..2229592d84 100644 --- a/common/src/project-file-tree.ts +++ b/common/src/project-file-tree.ts @@ -128,20 +128,34 @@ export async function getProjectFileTree(params: { ] let totalFiles = 0 let dirsScanned = 0 + let queueIndex = 0 - while (queue.length > 0 && totalFiles < maxFiles && dirsScanned < maxDirs) { - const { node, fullPath, ignores, depth } = queue.shift()! + while (queueIndex < queue.length && totalFiles < maxFiles && dirsScanned < maxDirs) { + const { node, fullPath, ignores, depth } = queue[queueIndex++] dirsScanned++ - const dirIgnores = [ - ...ignores, - { - base: toPosixPath(path.relative(projectRoot, fullPath)), - ig: await parseGitignore({ fullDirPath: fullPath, fs }), - }, - ] try { const files = await fs.readdir(fullPath) + const filesSet = new Set(files) + + let dirIgnores = ignores + const hasIgnoreFile = PROJECT_IGNORE_FILES.some((fileName) => + filesSet.has(fileName), + ) + if (hasIgnoreFile) { + dirIgnores = [ + ...ignores, + { + base: toPosixPath(path.relative(projectRoot, fullPath)), + ig: await parseGitignore({ + fullDirPath: fullPath, + fs, + directoryEntries: filesSet, + }), + }, + ] + } + for (const file of files) { if (totalFiles >= maxFiles) break @@ -199,13 +213,15 @@ async function parseGitignoreWithMode(params: { fullDirPath: string fs: CodebuffFileSystem throwOnReadError: boolean + directoryEntries?: Set }): Promise { - const { fullDirPath, fs, throwOnReadError } = params + const { fullDirPath, fs, throwOnReadError, directoryEntries: entriesParam } = params const ig = ignore.default() - const directoryEntries = throwOnReadError - ? new Set(await fs.readdir(fullDirPath)) - : undefined + let directoryEntries = entriesParam + if (!directoryEntries && throwOnReadError) { + directoryEntries = new Set(await fs.readdir(fullDirPath)) + } for (const fileName of PROJECT_IGNORE_FILES) { const ignoreFilePath = path.join(fullDirPath, fileName) @@ -237,6 +253,7 @@ async function parseGitignoreWithMode(params: { export async function parseGitignore(params: { fullDirPath: string fs: CodebuffFileSystem + directoryEntries?: Set }): Promise { return parseGitignoreWithMode({ ...params, throwOnReadError: false }) }