fix frontmatter scans to respect git excludes (#2462)

This commit is contained in:
kubi
2026-07-17 11:32:48 -07:00
committed by GitHub
parent ff8ce4d764
commit 73bbbde01d
4 changed files with 125 additions and 3 deletions
+8
View File
@@ -30,6 +30,7 @@ import {
type AuditReport,
type AuditFix,
} from '../core/brain-writer.ts';
import { collectGitVisibleFiles } from '../core/git-visible-files.ts';
import { isSyncable, pruneDir, slugifyPath } from '../core/sync.ts';
export async function runFrontmatter(args: string[]): Promise<void> {
@@ -272,6 +273,13 @@ export function collectFiles(
if (st.isFile()) {
return [target];
}
const gitFiles = collectGitVisibleFiles(target, (rel) => isSyncable(rel, { strategy: 'markdown' }));
if (gitFiles) {
if (visitDir) visitDir(target);
return gitFiles;
}
const out: string[] = [];
const stack = [target];
if (visitDir) visitDir(target);
+13 -2
View File
@@ -22,6 +22,7 @@ import { join, relative, resolve, dirname, basename, isAbsolute } from 'path';
import type { BrainEngine } from './engine.ts';
import type { ProgressReporter } from './progress.ts';
import { gbrainPath } from './config.ts';
import { collectGitVisibleFiles } from './git-visible-files.ts';
import {
parseMarkdown,
type ParseValidationCode,
@@ -579,7 +580,7 @@ function scanOneSource(
let ignoredMissingOpen = 0;
let interrupted = false;
walkDir(rootResolved, (absPath) => {
const visitFile = (absPath: string): boolean | void => {
// Per-file deadline + abort gate. Deadline is the load-bearing
// wall-clock bound (sync I/O blocks the event loop so timer-based
// AbortSignal.timeout can't fire mid-walk — codex C1).
@@ -625,7 +626,17 @@ function scanOneSource(
opts.onProgress.tick(50);
}
return true;
}, opts.visitDir);
};
const gitFiles = collectGitVisibleFiles(rootResolved, (rel) => isSyncable(rel, { strategy: 'markdown' }));
if (gitFiles) {
if (opts.visitDir) opts.visitDir(rootResolved);
for (const absPath of gitFiles) {
if (visitFile(absPath) === false) break;
}
} else {
walkDir(rootResolved, visitFile, opts.visitDir);
}
if (opts.onProgress) {
opts.onProgress.heartbeat(`scanned ${scanned} pages in ${sourceId}`);
+59
View File
@@ -0,0 +1,59 @@
import { execFileSync } from 'child_process';
import { lstatSync } from 'fs';
import { join } from 'path';
/**
* Return files visible to git from `dir`, respecting .gitignore,
* .git/info/exclude, and global git excludes. Returns null when `dir` is not
* inside a git work tree or git is unavailable, so callers can keep their
* existing filesystem-walk fallback.
*/
export function collectGitVisibleFiles(
dir: string,
acceptRelPath: (relPath: string) => boolean,
): string[] | null {
let stdout: string;
try {
stdout = execFileSync(
'git',
['-C', dir, 'ls-files', '--cached', '--others', '--exclude-standard', '-z'],
{ encoding: 'utf8', maxBuffer: 512 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] },
);
} catch {
return null;
}
const ignoredTracked = new Set<string>();
try {
const ignoredStdout = execFileSync(
'git',
['-C', dir, 'ls-files', '-ci', '--exclude-standard', '-z'],
{ encoding: 'utf8', maxBuffer: 512 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] },
);
for (const rel of ignoredStdout.split('\0')) {
if (rel) ignoredTracked.add(rel);
}
} catch {
// Best effort: older Git or unusual worktrees still get the standard list.
}
const files: string[] = [];
for (const rel of stdout.split('\0')) {
if (!rel) continue;
if (ignoredTracked.has(rel)) continue;
const normalizedRel = rel.replace(/\\/g, '/');
if (!acceptRelPath(normalizedRel)) continue;
const full = join(dir, rel);
let st: ReturnType<typeof lstatSync>;
try {
st = lstatSync(full);
} catch {
continue;
}
if (st.isSymbolicLink() || !st.isFile()) continue;
files.push(full);
}
return files.sort();
}