check / scan: project walks skip dot folders, worktrees and git-ignored output, no stat per entry (244 s -> 46 s on a big game repo)
This commit is contained in:
parent
f2d7beb7ce
commit
164ba390dd
6 changed files with 106 additions and 30 deletions
42
cli/lib/walk-skip.ts
Normal file
42
cli/lib/walk-skip.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Which folders a project walk (scan, check's privacy manifests) leaves out, beyond each walker's own name list:
|
||||
// - dot folders below the root (.harness-worktrees, .wt-head, .ota-out, .native-build, ...): tool state, caches and
|
||||
// whole copies of the repo, never app sources;
|
||||
// - linked git worktrees (another checkout of the same repo);
|
||||
// - folders git ignores at the root (release-out/, artifacts/, ...), asked once per root with
|
||||
// `git ls-files --others --ignored --exclude-standard --directory`; SDK folders the privacy checks read (Pods,
|
||||
// Carthage, SourcePackages) and generated native projects (ios, android) stay in even when ignored.
|
||||
// Seen live: a game repo with several worktrees and release folders made `check` walk ~60 000 files for 2.5 minutes.
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join, relative, resolve, sep } from 'node:path';
|
||||
import * as rt from './rt';
|
||||
|
||||
// kept even when git ignores them: SDK folders the privacy checks read, and generated native projects (Expo prebuild)
|
||||
const KEEP = new Set(['Pods', 'Carthage', 'SourcePackages', 'ios', 'android', 'macos']);
|
||||
const cache = new Map<string, Set<string>>();
|
||||
|
||||
/** A linked git worktree: its .git is a file pointing into <repo>/.git/worktrees/ (a submodule's points into modules/). */
|
||||
export function isWorktree(dir: string): boolean {
|
||||
try { return /^gitdir:.*[\\/]worktrees[\\/]/m.test(readFileSync(join(dir, '.git'), 'utf8')); } catch { return false; }
|
||||
}
|
||||
|
||||
/** Root-relative folders (with "/" separators, no trailing slash) git ignores in this repo; empty outside git. */
|
||||
export function gitIgnoredDirs(root: string): Set<string> {
|
||||
const key = resolve(root);
|
||||
const hit = cache.get(key);
|
||||
if (hit) return hit;
|
||||
const out = new Set<string>();
|
||||
try {
|
||||
const r = rt.spawnSync(['git', 'ls-files', '--others', '--ignored', '--exclude-standard', '--directory', '-z'], { cwd: key, timeout: 15_000 });
|
||||
if (r.success) for (const p of r.stdout.toString('utf8').split('\0')) if (p.endsWith('/')) out.add(p.slice(0, -1));
|
||||
} catch { /* no git */ }
|
||||
cache.set(key, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Leave this folder out of a walk that started at root? */
|
||||
export function skipDir(root: string, abs: string, name: string): boolean {
|
||||
if (KEEP.has(name)) return false;
|
||||
if (name.startsWith('.') && resolve(abs) !== resolve(root)) return true;
|
||||
if (gitIgnoredDirs(root).has(relative(root, abs).split(sep).join('/'))) return true;
|
||||
return isWorktree(abs);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue