(dir: string, matchers: ScopedIgnore[])
| 1026 | }; |
| 1027 | |
| 1028 | function walk(dir: string, matchers: ScopedIgnore[]): void { |
| 1029 | let realDir: string; |
| 1030 | try { |
| 1031 | realDir = fs.realpathSync(dir); |
| 1032 | } catch { |
| 1033 | logDebug('Skipping unresolvable directory', { dir }); |
| 1034 | return; |
| 1035 | } |
| 1036 | |
| 1037 | if (visitedDirs.has(realDir)) { |
| 1038 | logDebug('Skipping already-visited directory (symlink cycle)', { dir, realDir }); |
| 1039 | return; |
| 1040 | } |
| 1041 | visitedDirs.add(realDir); |
| 1042 | |
| 1043 | // This directory's own .gitignore (if present) applies to everything below it. |
| 1044 | // The root's .gitignore is already merged into the seeded base matcher (so a |
| 1045 | // negation there can override a built-in default), so skip it here. |
| 1046 | const own = dir === rootDir ? null : loadIgnore(dir); |
| 1047 | const active = own ? [...matchers, own] : matchers; |
| 1048 | |
| 1049 | let entries: fs.Dirent[]; |
| 1050 | try { |
| 1051 | entries = fs.readdirSync(dir, { withFileTypes: true }); |
| 1052 | } catch (error) { |
| 1053 | logDebug('Skipping unreadable directory', { dir, error: String(error) }); |
| 1054 | return; |
| 1055 | } |
| 1056 | |
| 1057 | for (const entry of entries) { |
| 1058 | // Never descend into git internals or any CodeGraph data directory |
| 1059 | // (the active one or a sibling another environment created — #636). |
| 1060 | if (entry.name === '.git' || isCodeGraphDataDir(entry.name)) continue; |
| 1061 | |
| 1062 | const fullPath = path.join(dir, entry.name); |
| 1063 | const relativePath = normalizePath(path.relative(rootDir, fullPath)); |
| 1064 | |
| 1065 | if (entry.isSymbolicLink()) { |
| 1066 | try { |
| 1067 | const realTarget = fs.realpathSync(fullPath); |
| 1068 | const stat = fs.statSync(realTarget); |
| 1069 | if (stat.isDirectory()) { |
| 1070 | if (!isIgnored(fullPath, true, active)) { |
| 1071 | walk(fullPath, active); |
| 1072 | } |
| 1073 | } else if (stat.isFile()) { |
| 1074 | if (!isIgnored(fullPath, false, active) && isSourceFile(relativePath, overrides)) { |
| 1075 | files.push(relativePath); |
| 1076 | count++; |
| 1077 | onProgress?.(count, relativePath); |
| 1078 | } |
| 1079 | } |
| 1080 | } catch { |
| 1081 | logDebug('Skipping broken symlink', { path: fullPath }); |
| 1082 | } |
| 1083 | continue; |
| 1084 | } |
| 1085 |
no test coverage detected