in codex-cli/src/utils/singlepass/context_files.ts [308:409]
export async function getFileContents(
rootPath: string,
compiledPatterns: Array<RegExp>,
): Promise<Array<FileContent>> {
const root = path.resolve(rootPath);
const candidateFiles: Array<string> = [];
// BFS queue of directories
const queue: Array<string> = [root];
while (queue.length > 0) {
const currentDir = queue.pop()!;
let dirents: Array<fsSync.Dirent> = [];
try {
dirents = await fs.readdir(currentDir, { withFileTypes: true });
} catch {
continue;
}
for (const dirent of dirents) {
try {
const resolved = path.resolve(currentDir, dirent.name);
// skip symlinks
const lstat = await fs.lstat(resolved);
if (lstat.isSymbolicLink()) {
continue;
}
if (dirent.isDirectory()) {
// check if ignored
if (!shouldIgnorePath(resolved, compiledPatterns)) {
queue.push(resolved);
}
} else if (dirent.isFile()) {
// check if ignored
if (!shouldIgnorePath(resolved, compiledPatterns)) {
candidateFiles.push(resolved);
}
}
} catch {
// skip
}
}
}
// We'll read the stat for each candidate file, see if we can skip reading from cache.
const results: Array<FileContent> = [];
// We'll keep track of which files we actually see.
const seenPaths = new Set<string>();
await Promise.all(
candidateFiles.map(async (filePath) => {
seenPaths.add(filePath);
let st: fsSync.Stats | null = null;
try {
st = await fs.stat(filePath);
} catch {
return;
}
if (!st) {
return;
}
const cEntry = FILE_CONTENTS_CACHE.get(filePath);
if (
cEntry &&
Math.abs(cEntry.mtime - st.mtime.getTime()) < 1 &&
cEntry.size === st.size
) {
// same mtime, same size => use cache
results.push({ path: filePath, content: cEntry.content });
} else {
// read file
try {
const buf = await fs.readFile(filePath);
const content = buf.toString("utf-8");
// store in cache
FILE_CONTENTS_CACHE.set(filePath, {
mtime: st.mtime.getTime(),
size: st.size,
content,
});
results.push({ path: filePath, content });
} catch {
// skip
}
}
}),
);
// Now remove from cache any file that wasn't encountered.
const currentKeys = [...FILE_CONTENTS_CACHE.keys()];
for (const key of currentKeys) {
if (!seenPaths.has(key)) {
FILE_CONTENTS_CACHE.delete(key);
}
}
// sort results by path
results.sort((a, b) => a.path.localeCompare(b.path));
return results;
}