* Get files using git ls-files (much faster than ripgrep for git repos) * Returns tracked files immediately, fetches untracked in background * @param respectGitignore If true, excludes gitignored files from untracked results * * Note: Unlike ripgrep --follow, git ls-files doesn't follow symlinks
( abortSignal: AbortSignal, respectGitignore: boolean, )
| 246 | * This is intentional as git tracks symlinks as symlinks. |
| 247 | */ |
| 248 | async function getFilesUsingGit( |
| 249 | abortSignal: AbortSignal, |
| 250 | respectGitignore: boolean, |
| 251 | ): Promise<string[] | null> { |
| 252 | const startTime = Date.now() |
| 253 | logForDebugging(`[FileIndex] getFilesUsingGit called`) |
| 254 | |
| 255 | // Check if we're in a git repo. findGitRoot is LRU-memoized per path. |
| 256 | const repoRoot = findGitRoot(getCwd()) |
| 257 | if (!repoRoot) { |
| 258 | logForDebugging(`[FileIndex] not a git repo, returning null`) |
| 259 | return null |
| 260 | } |
| 261 | |
| 262 | try { |
| 263 | const cwd = getCwd() |
| 264 | |
| 265 | // Get tracked files (fast - reads from git index) |
| 266 | // Run from repoRoot so paths are relative to repo root, not CWD |
| 267 | const lsFilesStart = Date.now() |
| 268 | const trackedResult = await execFileNoThrowWithCwd( |
| 269 | gitExe(), |
| 270 | ['-c', 'core.quotepath=false', 'ls-files', '--recurse-submodules'], |
| 271 | { timeout: 5000, abortSignal, cwd: repoRoot }, |
| 272 | ) |
| 273 | logForDebugging( |
| 274 | `[FileIndex] git ls-files (tracked) took ${Date.now() - lsFilesStart}ms`, |
| 275 | ) |
| 276 | |
| 277 | if (trackedResult.code !== 0) { |
| 278 | logForDebugging( |
| 279 | `[FileIndex] git ls-files failed (code=${trackedResult.code}, stderr=${trackedResult.stderr}), falling back to ripgrep`, |
| 280 | ) |
| 281 | return null |
| 282 | } |
| 283 | |
| 284 | const trackedFiles = trackedResult.stdout.trim().split('\n').filter(Boolean) |
| 285 | |
| 286 | // Normalize paths relative to the current working directory |
| 287 | let normalizedTracked = normalizeGitPaths(trackedFiles, repoRoot, cwd) |
| 288 | |
| 289 | // Apply .ignore/.rgignore patterns if present (faster than falling back to ripgrep) |
| 290 | const ignorePatterns = await loadRipgrepIgnorePatterns(repoRoot, cwd) |
| 291 | if (ignorePatterns) { |
| 292 | const beforeCount = normalizedTracked.length |
| 293 | normalizedTracked = ignorePatterns.filter(normalizedTracked) |
| 294 | logForDebugging( |
| 295 | `[FileIndex] applied ignore patterns: ${beforeCount} -> ${normalizedTracked.length} files`, |
| 296 | ) |
| 297 | } |
| 298 | |
| 299 | // Cache tracked files for later merge with untracked |
| 300 | cachedTrackedFiles = normalizedTracked |
| 301 | |
| 302 | const duration = Date.now() - startTime |
| 303 | logForDebugging( |
| 304 | `[FileIndex] git ls-files: ${normalizedTracked.length} tracked files in ${duration}ms`, |
| 305 | ) |
no test coverage detected