MCPcopy Create free account
hub / github.com/colbymchenry/codegraph / scanDirectoryWalk

Function scanDirectoryWalk

src/extraction/index.ts:1248–1383  ·  view source on GitHub ↗

* Filesystem walk fallback for non-git projects.

(
  rootDir: string,
  onProgress?: (current: number, file: string) => void
)

Source from the content-addressed store, hash-verified

1246 * Filesystem walk fallback for non-git projects.
1247 */
1248function scanDirectoryWalk(
1249 rootDir: string,
1250 onProgress?: (current: number, file: string) => void
1251): string[] {
1252 const files: string[] = [];
1253 let count = 0;
1254 const visitedDirs = new Set<string>();
1255 // Custom extension → language overrides from the project's codegraph.json.
1256 const overrides = loadExtensionOverrides(rootDir);
1257
1258 // A .gitignore matcher scoped to the directory that declared it. Patterns in
1259 // a nested .gitignore are relative to that directory, so we keep the dir
1260 // alongside the matcher and test paths relative to it — mirroring how git
1261 // applies .gitignore files at every level.
1262 interface ScopedIgnore {
1263 dir: string;
1264 ig: Ignore;
1265 }
1266
1267 const loadIgnore = (dir: string): ScopedIgnore | null => {
1268 const giPath = path.join(dir, '.gitignore');
1269 if (!fs.existsSync(giPath)) return null;
1270 // readGitignorePatterns is defensive: a non-UTF-8 (DLP-encrypted) or
1271 // uncompilable .gitignore is skipped/filtered with a warning, never thrown
1272 // (issue #682) — so the per-file `.ignores()` calls below can't crash.
1273 const patterns = readGitignorePatterns(giPath);
1274 return patterns ? { dir, ig: ignore().add(patterns) } : null;
1275 };
1276
1277 const isIgnored = (fullPath: string, isDir: boolean, matchers: ScopedIgnore[]): boolean => {
1278 for (const { dir, ig } of matchers) {
1279 let rel = normalizePath(path.relative(dir, fullPath));
1280 if (!rel || rel.startsWith('..')) continue; // not under this matcher's dir
1281 if (isDir) rel += '/'; // dir-only rules (e.g. `build/`) only match with the slash
1282 if (ig.ignores(rel)) return true;
1283 }
1284 return false;
1285 };
1286
1287 function walk(dir: string, matchers: ScopedIgnore[]): void {
1288 let realDir: string;
1289 try {
1290 realDir = fs.realpathSync(dir);
1291 } catch {
1292 logDebug('Skipping unresolvable directory', { dir });
1293 return;
1294 }
1295
1296 if (visitedDirs.has(realDir)) {
1297 logDebug('Skipping already-visited directory (symlink cycle)', { dir, realDir });
1298 return;
1299 }
1300 visitedDirs.add(realDir);
1301
1302 // This directory's own .gitignore (if present) applies to everything below it.
1303 // The root's .gitignore is already merged into the seeded base matcher (so a
1304 // negation there can override a built-in default), so skip it here.
1305 const own = dir === rootDir ? null : loadIgnore(dir);

Callers 2

scanDirectoryFunction · 0.85
scanDirectoryAsyncFunction · 0.85

Calls 6

loadExtensionOverridesFunction · 0.90
buildDefaultIgnoreFunction · 0.85
loadExcludeMatcherFunction · 0.85
hasMethod · 0.80
walkFunction · 0.70

Tested by

no test coverage detected