|
| 1 | +/** |
| 2 | + * The directories git ignores under the tree being analysed, so that no walker reads them. |
| 3 | + * |
| 4 | + * Every walker in the parser skips a fixed list of names (node_modules, dist, .next, …). A generated tree |
| 5 | + * that only the repository's .gitignore names — a framework's build cache under another name, a vendored |
| 6 | + * bundle, a local export — was parsed as source: an index of a 431-file Next.js/TypeScript project read |
| 7 | + * 15,108 files and took 12 minutes. git already knows which directories are not the project, so it is asked |
| 8 | + * once per run: |
| 9 | + * |
| 10 | + * git ls-files --others --ignored --exclude-standard --directory |
| 11 | + * |
| 12 | + * lists each ignored directory once (not its contents) and only when NOTHING under it is tracked, so a file |
| 13 | + * committed on purpose beneath an ignored name is still read, and so is everything outside a git work tree |
| 14 | + * (the command fails there, and nothing is skipped). Only directories are skipped, never single ignored |
| 15 | + * files: an ignored declaration file (`next-env.d.ts`) still contributes types. The background refresher |
| 16 | + * (plugins/axiomcode/skills/axiomcode/scripts/ax_fresh.py, git_ignored_dirs) asks the same question, so the |
| 17 | + * files it watches are the files the parser reads. |
| 18 | + * |
| 19 | + * Off for a library tree (`--library`: a dependency's own .gitignore names its build output, which for a |
| 20 | + * dependency is the source) and with AXIOMCODE_NO_GITIGNORE=1. |
| 21 | + */ |
| 22 | +import { execFileSync } from 'child_process'; |
| 23 | +import * as path from 'path'; |
| 24 | + |
| 25 | +let ignored: ReadonlySet<string> = new Set(); |
| 26 | + |
| 27 | +const key = (p: string): string => { |
| 28 | + const r = path.resolve(p); |
| 29 | + return process.platform === 'win32' ? r.toLowerCase() : r; |
| 30 | +}; |
| 31 | + |
| 32 | +/** Load the ignored directories under `root`; returns how many there are. Replaces what an earlier call loaded. */ |
| 33 | +export function loadGitIgnored(root: string): number { |
| 34 | + ignored = new Set(); |
| 35 | + if (process.env.AXIOMCODE_NO_GITIGNORE) return 0; |
| 36 | + let out: string; |
| 37 | + try { |
| 38 | + out = execFileSync('git', ['-C', root, 'ls-files', '--others', '--ignored', '--exclude-standard', '--directory', '-z', '--', '.'], |
| 39 | + { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 512 * 1024 * 1024, timeout: 120_000 }); |
| 40 | + } catch { |
| 41 | + return 0; // not a work tree, or no git: nothing is skipped |
| 42 | + } |
| 43 | + const set = new Set<string>(); |
| 44 | + for (const entry of out.split('\0')) { |
| 45 | + if (entry.endsWith('/')) set.add(key(path.join(root, entry))); |
| 46 | + } |
| 47 | + ignored = set; |
| 48 | + return set.size; |
| 49 | +} |
| 50 | + |
| 51 | +/** Forget the loaded set (a library tree is parsed with nothing skipped). */ |
| 52 | +export function clearGitIgnored(): void { |
| 53 | + ignored = new Set(); |
| 54 | +} |
| 55 | + |
| 56 | +/** True when git ignores the directory at `dir` (absolute, or relative to the working directory). */ |
| 57 | +export function isGitIgnoredDir(dir: string): boolean { |
| 58 | + return ignored.size > 0 && ignored.has(key(dir)); |
| 59 | +} |
0 commit comments