|
|
@@ -125,10 +125,61 @@ export function shouldIncludeFile(
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * Collect git-visible files (tracked + untracked, .gitignore-respected) from the
|
|
|
+ * git repository rooted at `repoDir`, adding each to `files` with `prefix`
|
|
|
+ * prepended so paths stay relative to the original scan root.
|
|
|
+ *
|
|
|
+ * Recurses into embedded git repositories — nested repos that are NOT submodules
|
|
|
+ * (independent clones living inside the workspace, common in CMake "super-repo"
|
|
|
+ * layouts). The parent repo's `git ls-files` cannot see into them: tracked output
|
|
|
+ * skips them entirely, and untracked output reports them only as an opaque
|
|
|
+ * "subdir/" entry (trailing slash) rather than expanding their files. Each
|
|
|
+ * embedded repo is its own git boundary, so we re-run `git ls-files` inside it.
|
|
|
+ * (See issue #193.)
|
|
|
+ */
|
|
|
+function collectGitFiles(repoDir: string, prefix: string, files: Set<string>): void {
|
|
|
+ const gitOpts = { cwd: repoDir, encoding: 'utf-8' as const, timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] as ['pipe', 'pipe', 'pipe'] };
|
|
|
+
|
|
|
+ // Tracked files. --recurse-submodules pulls in files from active submodules,
|
|
|
+ // which the index would otherwise represent only as a commit pointer.
|
|
|
+ // Without this, monorepos using submodules index 0 files. (See issue #147.)
|
|
|
+ // Note: --recurse-submodules only supports -c/--cached and --stage modes — it
|
|
|
+ // can't be combined with -o, so untracked files are gathered separately below.
|
|
|
+ const tracked = execFileSync('git', ['ls-files', '-c', '--recurse-submodules'], gitOpts);
|
|
|
+ for (const line of tracked.split('\n')) {
|
|
|
+ const trimmed = line.trim();
|
|
|
+ if (trimmed) {
|
|
|
+ files.add(normalizePath(prefix + trimmed));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Untracked files (submodules manage their own untracked state). Embedded git
|
|
|
+ // repos surface here as a single "subdir/" entry that git refuses to descend
|
|
|
+ // into — recurse into those as their own repos so their source gets indexed.
|
|
|
+ const untracked = execFileSync('git', ['ls-files', '-o', '--exclude-standard'], gitOpts);
|
|
|
+ for (const line of untracked.split('\n')) {
|
|
|
+ const trimmed = line.trim();
|
|
|
+ if (!trimmed) continue;
|
|
|
+ if (trimmed.endsWith('/')) {
|
|
|
+ // git only emits a trailing-slash directory entry for an embedded repo.
|
|
|
+ // Guard with a .git check anyway, and skip anything else exactly as git
|
|
|
+ // itself skips it (we never descend into a non-repo opaque dir).
|
|
|
+ const childDir = path.join(repoDir, trimmed);
|
|
|
+ if (fs.existsSync(path.join(childDir, '.git'))) {
|
|
|
+ collectGitFiles(childDir, prefix + trimmed, files);
|
|
|
+ }
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ files.add(normalizePath(prefix + trimmed));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* Get all files visible to git (tracked + untracked but not ignored).
|
|
|
- * Respects .gitignore at all levels (root, subdirectories).
|
|
|
- * Returns null on failure (non-git project) so callers can fall back.
|
|
|
+ * Respects .gitignore at all levels (root, subdirectories) and descends into
|
|
|
+ * embedded (nested, non-submodule) git repos. Returns null on failure
|
|
|
+ * (non-git project) so callers can fall back to a filesystem walk.
|
|
|
*/
|
|
|
function getGitVisibleFiles(rootDir: string): Set<string> | null {
|
|
|
try {
|
|
|
@@ -157,30 +208,7 @@ function getGitVisibleFiles(rootDir: string): Set<string> | null {
|
|
|
}
|
|
|
|
|
|
const files = new Set<string>();
|
|
|
- const gitOpts = { cwd: rootDir, encoding: 'utf-8' as const, timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] as ['pipe', 'pipe', 'pipe'] };
|
|
|
-
|
|
|
- // Tracked files. --recurse-submodules pulls in files from active submodules,
|
|
|
- // which the main repo's index would otherwise represent only as a commit pointer.
|
|
|
- // Without this, monorepos using submodules index 0 files. (See issue #147.)
|
|
|
- // Note: --recurse-submodules only supports -c/--cached and --stage modes — it
|
|
|
- // can't be combined with -o, so untracked files are gathered separately below.
|
|
|
- const tracked = execFileSync('git', ['ls-files', '-c', '--recurse-submodules'], gitOpts);
|
|
|
- for (const line of tracked.split('\n')) {
|
|
|
- const trimmed = line.trim();
|
|
|
- if (trimmed) {
|
|
|
- files.add(normalizePath(trimmed));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Untracked files in the main repo (submodules manage their own untracked state).
|
|
|
- const untracked = execFileSync('git', ['ls-files', '-o', '--exclude-standard'], gitOpts);
|
|
|
- for (const line of untracked.split('\n')) {
|
|
|
- const trimmed = line.trim();
|
|
|
- if (trimmed) {
|
|
|
- files.add(normalizePath(trimmed));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
+ collectGitFiles(rootDir, '', files);
|
|
|
return files;
|
|
|
} catch {
|
|
|
return null;
|