@lzehrung/codegraph 2.3.19 → 2.3.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/codegraph-skill/codegraph/SKILL.md +2 -1
- package/dist/agent/session.d.ts +2 -2
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +13 -9
- package/dist/agent/session.js.map +1 -1
- package/dist/bin/cli.js +36849 -37670
- package/dist/bin/queryIndexWorker.js +28 -5
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +12 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/invocationContext.d.ts.map +1 -1
- package/dist/cli/invocationContext.js +94 -31
- package/dist/cli/invocationContext.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -1
- package/dist/config.js.map +1 -1
- package/dist/indexer/build-cache/manifest.d.ts +2 -2
- package/dist/indexer/build-cache/manifest.d.ts.map +1 -1
- package/dist/indexer/build-cache/manifest.js +8 -33
- package/dist/indexer/build-cache/manifest.js.map +1 -1
- package/dist/indexer/build-index.d.ts.map +1 -1
- package/dist/indexer/build-index.js +134 -56
- package/dist/indexer/build-index.js.map +1 -1
- package/dist/indexer/build-manifest.d.ts +8 -0
- package/dist/indexer/build-manifest.d.ts.map +1 -1
- package/dist/indexer/build-manifest.js +20 -2
- package/dist/indexer/build-manifest.js.map +1 -1
- package/dist/indexer/finalize.d.ts +2 -1
- package/dist/indexer/finalize.d.ts.map +1 -1
- package/dist/indexer/finalize.js +14 -6
- package/dist/indexer/finalize.js.map +1 -1
- package/dist/indexer/incremental-plan.d.ts +3 -2
- package/dist/indexer/incremental-plan.d.ts.map +1 -1
- package/dist/indexer/incremental-plan.js +8 -4
- package/dist/indexer/incremental-plan.js.map +1 -1
- package/dist/indexer/types.d.ts +12 -2
- package/dist/indexer/types.d.ts.map +1 -1
- package/dist/lifecycle/manifest.d.ts.map +1 -1
- package/dist/lifecycle/manifest.js +82 -13
- package/dist/lifecycle/manifest.js.map +1 -1
- package/dist/util/git.d.ts +13 -10
- package/dist/util/git.d.ts.map +1 -1
- package/dist/util/git.js +29 -25
- package/dist/util/git.js.map +1 -1
- package/dist/util/paths.d.ts +2 -2
- package/dist/util/paths.d.ts.map +1 -1
- package/dist/util/paths.js +27 -7
- package/dist/util/paths.js.map +1 -1
- package/dist/util/projectFiles/definitions.d.ts +19 -0
- package/dist/util/projectFiles/definitions.d.ts.map +1 -1
- package/dist/util/projectFiles/definitions.js.map +1 -1
- package/dist/util/projectFiles.d.ts +80 -22
- package/dist/util/projectFiles.d.ts.map +1 -1
- package/dist/util/projectFiles.js +319 -38
- package/dist/util/projectFiles.js.map +1 -1
- package/package.json +3 -3
|
@@ -12,7 +12,8 @@ export { isRelativePathInside, matchesDiscoveryGlob } from "./discoveryPath.js";
|
|
|
12
12
|
import { PROJECT_FILE_DEFINITIONS, } from "./projectFiles/definitions.js";
|
|
13
13
|
import { trimToNull } from "./projectFiles/parsers.js";
|
|
14
14
|
import { mapLimitSemaphore } from "./concurrency.js";
|
|
15
|
-
import {
|
|
15
|
+
import { CODEGRAPH_CONFIG_FILE } from "../config.js";
|
|
16
|
+
import { createGitDiscoveryCache, getGitRepositoryRoot, isGitProjectRootIgnored, isGitRepo, isGitTimeoutError, listGitExcludeFiles, listGitStageSpecialPaths, listGitSubmoduleDirectories, listTrackedFiles, listUntrackedFiles, } from "./git.js";
|
|
16
17
|
export const DEFAULT_PROJECT_FILE_IGNORES = [
|
|
17
18
|
"**/node_modules/**",
|
|
18
19
|
"**/.git/**",
|
|
@@ -100,6 +101,9 @@ export const DEFAULT_PROJECT_PATTERNS = [
|
|
|
100
101
|
...DEFAULT_PROJECT_MANIFESTS.map((name) => `**/${name}`),
|
|
101
102
|
];
|
|
102
103
|
const REALPATH_FILTER_CONCURRENCY = 64;
|
|
104
|
+
export function createProjectDiscoveryContext(projectRoot) {
|
|
105
|
+
return new DiscoveryContext(projectRoot);
|
|
106
|
+
}
|
|
103
107
|
function emitDiscoveryActivity(report, activity, current = 0, total = 0) {
|
|
104
108
|
report?.({ activity, current, total });
|
|
105
109
|
}
|
|
@@ -121,6 +125,35 @@ function isPresent(value) {
|
|
|
121
125
|
function normalizeGlobPattern(globPattern) {
|
|
122
126
|
return globPattern.trim().replace(/\\/g, "/");
|
|
123
127
|
}
|
|
128
|
+
const ROOT_CONFIG_NAME_MATCHERS = [...DEFAULT_PROJECT_MANIFESTS, CODEGRAPH_CONFIG_FILE].map((pattern) => picomatch(normalizeGlobPattern(pattern), { dot: true }));
|
|
129
|
+
function isRootConfigFileName(fileName) {
|
|
130
|
+
return ROOT_CONFIG_NAME_MATCHERS.some((matcher) => matcher(fileName));
|
|
131
|
+
}
|
|
132
|
+
async function listRootConfigFiles(root, state) {
|
|
133
|
+
const entries = await readDirectoryWithFileTypes(root, state);
|
|
134
|
+
const files = [];
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
if (!isRootConfigFileName(entry.name))
|
|
137
|
+
continue;
|
|
138
|
+
const file = normalizePath(path.join(root, entry.name));
|
|
139
|
+
if (!entry.isFile()) {
|
|
140
|
+
if (!entry.isSymbolicLink())
|
|
141
|
+
continue;
|
|
142
|
+
try {
|
|
143
|
+
if (!(await fsp.stat(file)).isFile())
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
const code = error.code;
|
|
148
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
149
|
+
continue;
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
files.push(file);
|
|
154
|
+
}
|
|
155
|
+
return files;
|
|
156
|
+
}
|
|
124
157
|
const DIRECTORY_METADATA_EXTENSIONS = new Set();
|
|
125
158
|
const DIRECTORY_METADATA_BASENAMES = new Set();
|
|
126
159
|
for (const definition of PROJECT_FILE_DEFINITIONS) {
|
|
@@ -275,6 +308,126 @@ const EMPTY_GITIGNORE_INDEX = {
|
|
|
275
308
|
repositoryAliases: [],
|
|
276
309
|
byBaseDir: new Map(),
|
|
277
310
|
};
|
|
311
|
+
class DiscoveryContext {
|
|
312
|
+
projectRoot;
|
|
313
|
+
git;
|
|
314
|
+
gitCandidatesByRoot = new Map();
|
|
315
|
+
ignoreIndexByKey = new Map();
|
|
316
|
+
fileTextByPath = new Map();
|
|
317
|
+
readdirCache = new Map();
|
|
318
|
+
globFilesystem;
|
|
319
|
+
constructor(projectRoot) {
|
|
320
|
+
this.projectRoot = projectRoot;
|
|
321
|
+
this.git = createGitDiscoveryCache();
|
|
322
|
+
this.globFilesystem = { readdir: createSharedReaddir(this) };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function createSharedReaddir(state) {
|
|
326
|
+
function readdir(directory, optionsOrCallback, callback) {
|
|
327
|
+
const listing = readDirectoryWithFileTypes(directory, state);
|
|
328
|
+
if (typeof optionsOrCallback === "function") {
|
|
329
|
+
listing.then((entries) => optionsOrCallback(null, entries.map((entry) => entry.name)), (error) => optionsOrCallback(error, []));
|
|
330
|
+
}
|
|
331
|
+
else if (callback) {
|
|
332
|
+
listing.then((entries) => callback(null, entries), (error) => callback(error, []));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return readdir;
|
|
336
|
+
}
|
|
337
|
+
function globFilesystemOption(state) {
|
|
338
|
+
if (!state)
|
|
339
|
+
return {};
|
|
340
|
+
return { fs: state.globFilesystem };
|
|
341
|
+
}
|
|
342
|
+
function globFilesystemField(state) {
|
|
343
|
+
if (!state)
|
|
344
|
+
return {};
|
|
345
|
+
return { globFilesystem: state.globFilesystem };
|
|
346
|
+
}
|
|
347
|
+
async function readDirectoryWithFileTypes(directory, state) {
|
|
348
|
+
if (!state)
|
|
349
|
+
return fsp.readdir(directory, { withFileTypes: true });
|
|
350
|
+
const key = fileIdentityKey(directory);
|
|
351
|
+
const cached = state.readdirCache.get(key);
|
|
352
|
+
if (cached)
|
|
353
|
+
return cached;
|
|
354
|
+
const pending = fsp.readdir(directory, { withFileTypes: true });
|
|
355
|
+
state.readdirCache.set(key, pending);
|
|
356
|
+
return pending;
|
|
357
|
+
}
|
|
358
|
+
async function readDiscoveryFileResult(state, filePath) {
|
|
359
|
+
if (!state) {
|
|
360
|
+
try {
|
|
361
|
+
return { ok: true, text: await fsp.readFile(filePath, "utf8") };
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
return { ok: false, error };
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const key = fileIdentityKey(filePath);
|
|
368
|
+
const cached = state.fileTextByPath.get(key);
|
|
369
|
+
if (cached)
|
|
370
|
+
return await cached;
|
|
371
|
+
const pending = (async () => {
|
|
372
|
+
try {
|
|
373
|
+
return { ok: true, text: await fsp.readFile(filePath, "utf8") };
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
return { ok: false, error };
|
|
377
|
+
}
|
|
378
|
+
})();
|
|
379
|
+
state.fileTextByPath.set(key, pending);
|
|
380
|
+
return await pending;
|
|
381
|
+
}
|
|
382
|
+
export async function readProjectDiscoveryFileText(discoveryContext, filePath) {
|
|
383
|
+
const result = await readDiscoveryFileResult(discoveryContext, filePath);
|
|
384
|
+
if (result.ok)
|
|
385
|
+
return result.text;
|
|
386
|
+
if (result.error instanceof Error)
|
|
387
|
+
throw result.error;
|
|
388
|
+
throw new Error(`Failed to read ${filePath}: ${stringifyUnknown(result.error)}`);
|
|
389
|
+
}
|
|
390
|
+
async function sharedGitignoreIndex(state, cacheKey, load) {
|
|
391
|
+
if (!state)
|
|
392
|
+
return await load();
|
|
393
|
+
const cached = state.ignoreIndexByKey.get(cacheKey);
|
|
394
|
+
if (cached)
|
|
395
|
+
return await cached;
|
|
396
|
+
const pending = load();
|
|
397
|
+
state.ignoreIndexByKey.set(cacheKey, pending);
|
|
398
|
+
return await pending;
|
|
399
|
+
}
|
|
400
|
+
function rememberGitCandidates(state, root, candidates) {
|
|
401
|
+
if (!state)
|
|
402
|
+
return;
|
|
403
|
+
const key = fileIdentityKey(root);
|
|
404
|
+
if (state.gitCandidatesByRoot.has(key))
|
|
405
|
+
return;
|
|
406
|
+
state.gitCandidatesByRoot.set(key, Promise.resolve(candidates));
|
|
407
|
+
}
|
|
408
|
+
async function getSharedGitCandidates(context, root, logLevel, callbacks) {
|
|
409
|
+
const state = context;
|
|
410
|
+
const gitCache = context?.git;
|
|
411
|
+
if (!state)
|
|
412
|
+
return await listGitCandidateFiles(root, logLevel, callbacks, gitCache);
|
|
413
|
+
const key = fileIdentityKey(root);
|
|
414
|
+
const cached = state.gitCandidatesByRoot.get(key);
|
|
415
|
+
if (cached)
|
|
416
|
+
return await cached;
|
|
417
|
+
const pending = listGitCandidateFiles(root, logLevel, callbacks, gitCache);
|
|
418
|
+
state.gitCandidatesByRoot.set(key, pending);
|
|
419
|
+
return await pending;
|
|
420
|
+
}
|
|
421
|
+
function gitExcludeListOptions(gitCache) {
|
|
422
|
+
if (!gitCache)
|
|
423
|
+
return undefined;
|
|
424
|
+
return { discoveryCache: gitCache };
|
|
425
|
+
}
|
|
426
|
+
function gitUntrackedListOptions(gitCache) {
|
|
427
|
+
if (!gitCache)
|
|
428
|
+
return { respectGitignore: true };
|
|
429
|
+
return { respectGitignore: true, discoveryCache: gitCache };
|
|
430
|
+
}
|
|
278
431
|
/**
|
|
279
432
|
* Parse `.gitignore` rules from an already-discovered set of `.gitignore` files.
|
|
280
433
|
*
|
|
@@ -284,7 +437,7 @@ const EMPTY_GITIGNORE_INDEX = {
|
|
|
284
437
|
* child negation could re-include files. Depth-first ordering also keeps the "last match
|
|
285
438
|
* wins" order deeper files depend on, and drops the locale sensitivity of `localeCompare`.
|
|
286
439
|
*/
|
|
287
|
-
async function buildGitignoreIndex(sources, repositoryRoots = [], repositoryAliases = []) {
|
|
440
|
+
async function buildGitignoreIndex(sources, repositoryRoots = [], repositoryAliases = [], state) {
|
|
288
441
|
const sorted = [...sources]
|
|
289
442
|
.map(({ file, baseDir, repositoryRoot, priority }) => ({
|
|
290
443
|
file: normalizePath(file),
|
|
@@ -316,13 +469,10 @@ async function buildGitignoreIndex(sources, repositoryRoots = [], repositoryAlia
|
|
|
316
469
|
if (isIgnoredByGitignore(path.dirname(file), gitignoreIndex, true)) {
|
|
317
470
|
continue;
|
|
318
471
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
raw = await fsp.readFile(file, "utf8");
|
|
322
|
-
}
|
|
323
|
-
catch {
|
|
472
|
+
const fileText = await readDiscoveryFileResult(state, file);
|
|
473
|
+
if (!fileText.ok)
|
|
324
474
|
continue;
|
|
325
|
-
|
|
475
|
+
const raw = fileText.text;
|
|
326
476
|
const baseKey = fileIdentityKey(baseDir);
|
|
327
477
|
for (const line of raw.split(/\r?\n/)) {
|
|
328
478
|
const rule = parseGitignoreRule(baseDir, line);
|
|
@@ -338,16 +488,17 @@ async function buildGitignoreIndex(sources, repositoryRoots = [], repositoryAlia
|
|
|
338
488
|
}
|
|
339
489
|
return gitignoreIndex;
|
|
340
490
|
}
|
|
341
|
-
async function findGitignoreFiles(projectRoot) {
|
|
491
|
+
async function findGitignoreFiles(projectRoot, state) {
|
|
342
492
|
return await fg(["**/.gitignore"], {
|
|
343
493
|
cwd: projectRoot,
|
|
344
494
|
absolute: true,
|
|
345
495
|
dot: true,
|
|
346
496
|
followSymbolicLinks: false,
|
|
347
497
|
ignore: DEFAULT_PROJECT_FILE_IGNORES,
|
|
498
|
+
...globFilesystemOption(state),
|
|
348
499
|
});
|
|
349
500
|
}
|
|
350
|
-
async function loadGitignoreIndexForRootAliases(projectRoot) {
|
|
501
|
+
async function loadGitignoreIndexForRootAliases(projectRoot, state) {
|
|
351
502
|
const roots = [projectRoot];
|
|
352
503
|
const realRoot = await fsp.realpath(projectRoot);
|
|
353
504
|
if (normalizePath(realRoot) !== normalizePath(projectRoot)) {
|
|
@@ -355,11 +506,11 @@ async function loadGitignoreIndexForRootAliases(projectRoot) {
|
|
|
355
506
|
}
|
|
356
507
|
const sources = [];
|
|
357
508
|
for (const root of roots) {
|
|
358
|
-
for (const file of await findGitignoreFiles(root)) {
|
|
509
|
+
for (const file of await findGitignoreFiles(root, state)) {
|
|
359
510
|
sources.push({ file, baseDir: path.dirname(file) });
|
|
360
511
|
}
|
|
361
512
|
}
|
|
362
|
-
return await buildGitignoreIndex(sources);
|
|
513
|
+
return await buildGitignoreIndex(sources, [], [], state);
|
|
363
514
|
}
|
|
364
515
|
/**
|
|
365
516
|
* Whether `.gitignore` rules ignore `absolutePath`.
|
|
@@ -459,11 +610,12 @@ function collectCandidateAncestorDirectories(root, files) {
|
|
|
459
610
|
}
|
|
460
611
|
return directories;
|
|
461
612
|
}
|
|
462
|
-
function owningGitIgnoreSourceRoot(file, roots) {
|
|
613
|
+
export function owningGitIgnoreSourceRoot(file, roots, realpathMemo) {
|
|
463
614
|
const normalized = normalizePath(file);
|
|
464
615
|
let owning;
|
|
465
616
|
for (const root of roots) {
|
|
466
|
-
if (!isFilePathWithinRoot(root.path, normalized
|
|
617
|
+
if (!isFilePathWithinRoot(root.path, normalized, realpathMemo) &&
|
|
618
|
+
!isFilePathWithinRoot(root.repositoryRoot, normalized, realpathMemo)) {
|
|
467
619
|
continue;
|
|
468
620
|
}
|
|
469
621
|
if (!owning || root.repositoryRoot.length > owning.repositoryRoot.length) {
|
|
@@ -472,6 +624,24 @@ function owningGitIgnoreSourceRoot(file, roots) {
|
|
|
472
624
|
}
|
|
473
625
|
return owning;
|
|
474
626
|
}
|
|
627
|
+
/**
|
|
628
|
+
* Ownership is pure path containment, so every candidate in the same parent directory
|
|
629
|
+
* shares one answer. The lookup and its realpath memo live only for this discovery pass,
|
|
630
|
+
* so a long-lived process cannot serve a stale owner after the tree changes.
|
|
631
|
+
*/
|
|
632
|
+
export function createGitIgnoreSourceOwnershipLookup(roots, realpathMemo) {
|
|
633
|
+
const ownershipByDirectory = new Map();
|
|
634
|
+
return (file) => {
|
|
635
|
+
const directory = normalizePath(path.dirname(file));
|
|
636
|
+
const key = fileIdentityKey(directory);
|
|
637
|
+
if (ownershipByDirectory.has(key)) {
|
|
638
|
+
return ownershipByDirectory.get(key);
|
|
639
|
+
}
|
|
640
|
+
const owning = owningGitIgnoreSourceRoot(directory, roots, realpathMemo);
|
|
641
|
+
ownershipByDirectory.set(key, owning);
|
|
642
|
+
return owning;
|
|
643
|
+
};
|
|
644
|
+
}
|
|
475
645
|
async function gitignoreFileIfPresent(directory) {
|
|
476
646
|
const file = normalizePath(path.join(directory, ".gitignore"));
|
|
477
647
|
try {
|
|
@@ -494,7 +664,7 @@ async function gitignoreFileIfPresent(directory) {
|
|
|
494
664
|
* `.gitignore` files walks every gitignored tree looking for them, which times
|
|
495
665
|
* out on a large ignored data directory even after file listing already finished.
|
|
496
666
|
*/
|
|
497
|
-
async function findGitIgnoreSources(sourceRoots, candidateFiles) {
|
|
667
|
+
async function findGitIgnoreSources(sourceRoots, candidateFiles, gitCache) {
|
|
498
668
|
const roots = Array.from(new Map(sourceRoots.map(({ path: sourceRoot, repositoryRoot }) => [
|
|
499
669
|
fileIdentityKey(sourceRoot),
|
|
500
670
|
{ path: normalizePath(sourceRoot), repositoryRoot: normalizePath(repositoryRoot) },
|
|
@@ -510,13 +680,15 @@ async function findGitIgnoreSources(sourceRoots, candidateFiles) {
|
|
|
510
680
|
for (const { path: sourceRoot, repositoryRoot } of roots) {
|
|
511
681
|
addDirectory(sourceRoot, repositoryRoot);
|
|
512
682
|
}
|
|
683
|
+
const realpathMemo = new Map();
|
|
684
|
+
const owningOf = createGitIgnoreSourceOwnershipLookup(roots, realpathMemo);
|
|
513
685
|
for (const file of candidateFiles) {
|
|
514
|
-
const owning =
|
|
686
|
+
const owning = owningOf(file);
|
|
515
687
|
if (!owning)
|
|
516
688
|
continue;
|
|
517
689
|
let dir = normalizePath(path.dirname(file));
|
|
518
690
|
const stopKey = fileIdentityKey(owning.repositoryRoot);
|
|
519
|
-
|
|
691
|
+
while (true) {
|
|
520
692
|
const dirKey = fileIdentityKey(dir);
|
|
521
693
|
const walkKey = `${dirKey}\0${stopKey}`;
|
|
522
694
|
if (walkedDirectoryKeys.has(walkKey))
|
|
@@ -527,7 +699,8 @@ async function findGitIgnoreSources(sourceRoots, candidateFiles) {
|
|
|
527
699
|
break;
|
|
528
700
|
const parent = normalizePath(path.dirname(dir));
|
|
529
701
|
if (parent === dir ||
|
|
530
|
-
(!isFilePathWithinRoot(owning.path, parent
|
|
702
|
+
(!isFilePathWithinRoot(owning.path, parent, realpathMemo) &&
|
|
703
|
+
!isFilePathWithinRoot(owning.repositoryRoot, parent, realpathMemo))) {
|
|
531
704
|
break;
|
|
532
705
|
}
|
|
533
706
|
dir = parent;
|
|
@@ -539,7 +712,7 @@ async function findGitIgnoreSources(sourceRoots, candidateFiles) {
|
|
|
539
712
|
const file = await gitignoreFileIfPresent(dir);
|
|
540
713
|
return file ? [{ file, baseDir: dir, repositoryRoot, priority: 2 }] : [];
|
|
541
714
|
}),
|
|
542
|
-
Promise.all(uniqueRepositoryRoots.map((repositoryRoot) => listGitExcludeFiles(repositoryRoot))),
|
|
715
|
+
Promise.all(uniqueRepositoryRoots.map((repositoryRoot) => listGitExcludeFiles(repositoryRoot, gitExcludeListOptions(gitCache)))),
|
|
543
716
|
]);
|
|
544
717
|
return [
|
|
545
718
|
...gitignoreHits.flat(),
|
|
@@ -555,15 +728,15 @@ async function findGitIgnoreSources(sourceRoots, candidateFiles) {
|
|
|
555
728
|
* Tracked plus untracked-but-not-ignored files are Git candidates. Submodule files retain
|
|
556
729
|
* their own repository boundary, and source roots cover logical and physical aliases.
|
|
557
730
|
*/
|
|
558
|
-
async function listGitCandidateFiles(root, logLevel, opts) {
|
|
731
|
+
async function listGitCandidateFiles(root, logLevel, opts, gitCache) {
|
|
559
732
|
let gitListStart;
|
|
560
733
|
let gitIgnoreStart;
|
|
561
734
|
try {
|
|
562
735
|
const realRoot = normalizePath(await fsp.realpath(root));
|
|
563
|
-
const gitRoot = (await isGitRepo(root)) ? root : realRoot;
|
|
564
|
-
if (!(await isGitRepo(gitRoot)))
|
|
736
|
+
const gitRoot = (await isGitRepo(root, gitCache)) ? root : realRoot;
|
|
737
|
+
if (!(await isGitRepo(gitRoot, gitCache)))
|
|
565
738
|
return null;
|
|
566
|
-
if (await isGitProjectRootIgnored(realRoot))
|
|
739
|
+
if (await isGitProjectRootIgnored(realRoot, gitCache))
|
|
567
740
|
return null;
|
|
568
741
|
const gitRepositoryRoot = await getGitRepositoryRoot(gitRoot);
|
|
569
742
|
if (!gitRepositoryRoot)
|
|
@@ -571,9 +744,10 @@ async function listGitCandidateFiles(root, logLevel, opts) {
|
|
|
571
744
|
emitDiscoveryActivity(opts?.onDiscoveryProgress, "Listing Git files");
|
|
572
745
|
const listStartedAt = performance.now();
|
|
573
746
|
gitListStart = listStartedAt;
|
|
747
|
+
const untrackedOptions = gitUntrackedListOptions(gitCache);
|
|
574
748
|
const [tracked, untracked, stageSpecial] = await Promise.all([
|
|
575
749
|
listTrackedFiles(gitRoot, { recurseSubmodules: true, ...(logLevel === undefined ? {} : { logLevel }) }),
|
|
576
|
-
listUntrackedFiles(gitRoot,
|
|
750
|
+
listUntrackedFiles(gitRoot, untrackedOptions),
|
|
577
751
|
listGitStageSpecialPaths(gitRoot, { recurse: true }),
|
|
578
752
|
]);
|
|
579
753
|
const submoduleDirectories = stageSpecial.gitlinks;
|
|
@@ -581,7 +755,7 @@ async function listGitCandidateFiles(root, logLevel, opts) {
|
|
|
581
755
|
logicalPath: normalizePath(logicalPath),
|
|
582
756
|
physicalPath: normalizePath(await fsp.realpath(logicalPath).catch(() => logicalPath)),
|
|
583
757
|
})));
|
|
584
|
-
const submoduleUntracked = await Promise.all(submoduleRoots.map(async ({ physicalPath }) => await listUntrackedFiles(physicalPath,
|
|
758
|
+
const submoduleUntracked = await Promise.all(submoduleRoots.map(async ({ physicalPath }) => await listUntrackedFiles(physicalPath, untrackedOptions)));
|
|
585
759
|
const rawFiles = Array.from(new Set([...tracked, ...untracked, ...submoduleUntracked.flat()])).sort();
|
|
586
760
|
const needsLogicalRemap = normalizePath(gitRoot) !== normalizePath(root);
|
|
587
761
|
const files = Array.from(new Set(rawFiles.map((file) => needsLogicalRemap && isFilePathWithinRoot(realRoot, file)
|
|
@@ -607,7 +781,7 @@ async function listGitCandidateFiles(root, logLevel, opts) {
|
|
|
607
781
|
emitDiscoveryActivity(opts?.onDiscoveryProgress, "Listing Git ignore files");
|
|
608
782
|
const ignoreStartedAt = performance.now();
|
|
609
783
|
gitIgnoreStart = ignoreStartedAt;
|
|
610
|
-
const gitignoreFiles = await findGitIgnoreSources(sourceRoots, files);
|
|
784
|
+
const gitignoreFiles = await findGitIgnoreSources(sourceRoots, files, gitCache);
|
|
611
785
|
emitDiscoveryTiming(opts?.onDiscoveryTiming, "git-ignore", ignoreStartedAt);
|
|
612
786
|
gitIgnoreStart = undefined;
|
|
613
787
|
emitDiscoveryActivity(opts?.onDiscoveryProgress, "Listing Git ignore files", gitignoreFiles.length, gitignoreFiles.length);
|
|
@@ -673,15 +847,26 @@ async function listProjectFilesInternal(projectRoot, patterns = DEFAULT_PROJECT_
|
|
|
673
847
|
// disk): on an Unreal project holding 100,168 files (818 tracked) the scan produced
|
|
674
848
|
// 55,983 pattern-matching candidates, nearly all generated sources under a gitignored
|
|
675
849
|
// `Intermediate/` tree, and discovery took 26.1s. Git lists 4,010 in about 111ms.
|
|
676
|
-
// An aliased root or a gitignore-root override means Git's notion
|
|
677
|
-
// not this call's, so those fall back to the scan.
|
|
678
|
-
|
|
850
|
+
// An aliased root or a gitignore-root override that points elsewhere means Git's notion
|
|
851
|
+
// of the project is not this call's, so those fall back to the scan. Passing
|
|
852
|
+
// `gitignoreRoot` equal to the scan root (the CLI's historical whole-project form) still
|
|
853
|
+
// wants this repository's ignore rules, so keep the Git candidate path. On a 501-file
|
|
854
|
+
// Git repo with a 20,000-file gitignored tree, `gitignoreRoot: scanRoot` otherwise
|
|
855
|
+
// forced a filesystem scan plus a full-tree symlink probe (~403ms) instead of Git
|
|
856
|
+
// listing (~66ms) of the 502 tracked paths.
|
|
857
|
+
// Compare by identity key, not slash normalization: on a case-insensitive filesystem a
|
|
858
|
+
// casing or drive-letter difference still names the same directory, and calling that a
|
|
859
|
+
// mismatch would silently drop back to the slow scan this fast path exists to avoid.
|
|
860
|
+
const scanRootKey = fileIdentityKey(root);
|
|
861
|
+
const gitignoreRootMatchesScanRoot = options?.gitignoreRoot === undefined || fileIdentityKey(options.gitignoreRoot) === scanRootKey;
|
|
862
|
+
const attemptedGitCandidates = useGitignore && gitignoreRootMatchesScanRoot && fileIdentityKey(realRoot) === scanRootKey;
|
|
679
863
|
const gitDiscoveryCallbacks = {
|
|
680
864
|
...(options?.onDiscoveryProgress ? { onDiscoveryProgress: options.onDiscoveryProgress } : {}),
|
|
681
865
|
...(options?.onDiscoveryTiming ? { onDiscoveryTiming: options.onDiscoveryTiming } : {}),
|
|
682
866
|
};
|
|
867
|
+
const discovery = options?.discoveryContext ?? createProjectDiscoveryContext(root);
|
|
683
868
|
const gitCandidates = attemptedGitCandidates
|
|
684
|
-
? await
|
|
869
|
+
? await getSharedGitCandidates(discovery, root, options?.logLevel, gitDiscoveryCallbacks)
|
|
685
870
|
: null;
|
|
686
871
|
// Report only when this call actually asked Git, including a null result. Callers that
|
|
687
872
|
// already enumerated candidates (build-index) can hand the same set to metadata discovery
|
|
@@ -691,13 +876,13 @@ async function listProjectFilesInternal(projectRoot, patterns = DEFAULT_PROJECT_
|
|
|
691
876
|
}
|
|
692
877
|
let gitignoreIndex = EMPTY_GITIGNORE_INDEX;
|
|
693
878
|
if (useGitignore && gitCandidates) {
|
|
694
|
-
gitignoreIndex = await buildGitignoreIndex(gitCandidates.gitignoreFiles, gitCandidates.gitignoreRoots, gitCandidates.gitignoreAliases);
|
|
879
|
+
gitignoreIndex = await sharedGitignoreIndex(discovery, `git:${fileIdentityKey(root)}`, () => buildGitignoreIndex(gitCandidates.gitignoreFiles, gitCandidates.gitignoreRoots, gitCandidates.gitignoreAliases, discovery));
|
|
695
880
|
}
|
|
696
881
|
else if (useGitignore) {
|
|
697
882
|
const gitignoreRoot = options?.gitignoreRoot
|
|
698
883
|
? await ensureDirectoryReadable(options.gitignoreRoot, "Gitignore root")
|
|
699
884
|
: root;
|
|
700
|
-
gitignoreIndex = await loadGitignoreIndexForRootAliases(gitignoreRoot);
|
|
885
|
+
gitignoreIndex = await sharedGitignoreIndex(discovery, `fs:${fileIdentityKey(gitignoreRoot)}`, () => loadGitignoreIndexForRootAliases(gitignoreRoot, discovery));
|
|
701
886
|
}
|
|
702
887
|
// Git returns a candidate set, not a filtered result: the pattern, include,
|
|
703
888
|
// default-ignore, and gitignore checks below still decide what is indexable.
|
|
@@ -714,6 +899,7 @@ async function listProjectFilesInternal(projectRoot, patterns = DEFAULT_PROJECT_
|
|
|
714
899
|
dot: true,
|
|
715
900
|
followSymbolicLinks: false,
|
|
716
901
|
ignore: fastGlobIgnoreGlobs,
|
|
902
|
+
...globFilesystemOption(discovery),
|
|
717
903
|
});
|
|
718
904
|
emitDiscoveryTiming(options?.onDiscoveryTiming, "filesystem-scan", scanStart);
|
|
719
905
|
emitDiscoveryActivity(options?.onDiscoveryProgress, "Scanning project files", files.length, files.length);
|
|
@@ -733,6 +919,7 @@ async function listProjectFilesInternal(projectRoot, patterns = DEFAULT_PROJECT_
|
|
|
733
919
|
dot: true,
|
|
734
920
|
followSymbolicLinks: false,
|
|
735
921
|
ignore: translatedUserIgnoreGlobs,
|
|
922
|
+
...globFilesystemOption(discovery),
|
|
736
923
|
})
|
|
737
924
|
: [];
|
|
738
925
|
const reportSourceSymlinkChecks = options?.onDiscoveryProgress
|
|
@@ -751,6 +938,7 @@ async function listProjectFilesInternal(projectRoot, patterns = DEFAULT_PROJECT_
|
|
|
751
938
|
? { onSymlinkDirectoriesDiscovered: options.onSymlinkDirectoriesDiscovered }
|
|
752
939
|
: {}),
|
|
753
940
|
...(reportSourceSymlinkChecks ? { onPathCheckProgress: reportSourceSymlinkChecks } : {}),
|
|
941
|
+
...globFilesystemField(discovery),
|
|
754
942
|
};
|
|
755
943
|
const safeSymlinkDirectories = await resolveSafeSymlinkDirectories(root, realRoot, symlinkProbeIgnoreGlobs, symlinkOptions);
|
|
756
944
|
const linkedFiles = await listEntriesFromSafeSymlinkDirectories(root, realRoot, patterns, fastGlobIgnoreGlobs, {
|
|
@@ -764,6 +952,7 @@ async function listProjectFilesInternal(projectRoot, patterns = DEFAULT_PROJECT_
|
|
|
764
952
|
globRoot,
|
|
765
953
|
filterIgnoreGlobs: userIgnoreGlobs,
|
|
766
954
|
resolvedSafeSymlinkDirectories: safeSymlinkDirectories,
|
|
955
|
+
...globFilesystemField(discovery),
|
|
767
956
|
});
|
|
768
957
|
// Cheap path predicates run before the realpath confinement probe. Git enumerates
|
|
769
958
|
// every tracked and untracked file it knows about, including trees this project
|
|
@@ -928,6 +1117,7 @@ async function resolveSafeSymlinkDirectories(root, realRoot, ignore, options) {
|
|
|
928
1117
|
followSymbolicLinks: false,
|
|
929
1118
|
objectMode: true,
|
|
930
1119
|
ignore,
|
|
1120
|
+
...(options.globFilesystem ? { fs: options.globFilesystem } : {}),
|
|
931
1121
|
}));
|
|
932
1122
|
const discovered = await verifySafeSymlinkDirectories(root, realRoot, entries.filter((entry) => entry.dirent.isSymbolicLink()).map((entry) => entry.path), options.onPathCheckProgress);
|
|
933
1123
|
options.onSymlinkDirectoriesDiscovered?.(discovered, "filesystem");
|
|
@@ -953,6 +1143,7 @@ async function listEntriesFromSafeSymlinkDirectories(root, realRoot, patterns, i
|
|
|
953
1143
|
ignore: locationIndependentIgnores,
|
|
954
1144
|
...(options.onlyFiles !== undefined ? { onlyFiles: options.onlyFiles } : {}),
|
|
955
1145
|
...(options.markDirectories !== undefined ? { markDirectories: options.markDirectories } : {}),
|
|
1146
|
+
...(options.globFilesystem ? { fs: options.globFilesystem } : {}),
|
|
956
1147
|
})).filter((filePath) => {
|
|
957
1148
|
const cleanPath = filePath.endsWith("/") ? filePath.slice(0, -1) : filePath;
|
|
958
1149
|
return !rootRelativeIgnoreMatchers.some((matcher) => matchesDiscoveryGlob(cleanPath, globRoot, matcher));
|
|
@@ -987,14 +1178,14 @@ export async function filterRealPathsWithinRoot(paths, realRoot, onPathCheckProg
|
|
|
987
1178
|
function toProjectGlob(pattern) {
|
|
988
1179
|
return pattern.startsWith("**/") ? pattern : `**/${pattern}`;
|
|
989
1180
|
}
|
|
990
|
-
async function buildProjectFileInfo(def, filePath) {
|
|
1181
|
+
async function buildProjectFileInfo(def, filePath, state) {
|
|
991
1182
|
const normalizedPath = normalizePath(filePath);
|
|
992
1183
|
const projectRoot = normalizePath(path.dirname(filePath));
|
|
993
1184
|
let name = null;
|
|
994
1185
|
if (def.parseName && def.kind === "file") {
|
|
995
1186
|
try {
|
|
996
|
-
const
|
|
997
|
-
name = def.parseName(
|
|
1187
|
+
const text = await readProjectDiscoveryFileText(state, filePath);
|
|
1188
|
+
name = def.parseName(text, filePath);
|
|
998
1189
|
}
|
|
999
1190
|
catch {
|
|
1000
1191
|
name = null;
|
|
@@ -1037,12 +1228,14 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1037
1228
|
return matcher ? matcher.test(fileName) : pattern === fileName;
|
|
1038
1229
|
});
|
|
1039
1230
|
};
|
|
1231
|
+
const discovery = options?.discoveryContext ?? createProjectDiscoveryContext(root);
|
|
1040
1232
|
let gitCandidates;
|
|
1041
1233
|
if (options?.knownGitCandidates !== undefined) {
|
|
1042
1234
|
gitCandidates = options.knownGitCandidates;
|
|
1235
|
+
rememberGitCandidates(discovery, root, gitCandidates);
|
|
1043
1236
|
}
|
|
1044
1237
|
else {
|
|
1045
|
-
gitCandidates = await
|
|
1238
|
+
gitCandidates = await getSharedGitCandidates(discovery, root, options?.logLevel, {
|
|
1046
1239
|
...(options?.onDiscoveryProgress ? { onDiscoveryProgress: options.onDiscoveryProgress } : {}),
|
|
1047
1240
|
...(options?.onDiscoveryTiming ? { onDiscoveryTiming: options.onDiscoveryTiming } : {}),
|
|
1048
1241
|
});
|
|
@@ -1059,7 +1252,7 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1059
1252
|
: undefined;
|
|
1060
1253
|
let rootSafeMatches;
|
|
1061
1254
|
if (gitCandidates) {
|
|
1062
|
-
const gitignoreIndex = await buildGitignoreIndex(gitCandidates.gitignoreFiles, gitCandidates.gitignoreRoots, gitCandidates.gitignoreAliases);
|
|
1255
|
+
const gitignoreIndex = await sharedGitignoreIndex(discovery, `git:${fileIdentityKey(root)}`, () => buildGitignoreIndex(gitCandidates.gitignoreFiles, gitCandidates.gitignoreRoots, gitCandidates.gitignoreAliases, discovery));
|
|
1063
1256
|
const defaultIgnoreMatchers = DEFAULT_PROJECT_FILE_IGNORES.map((globPattern) => picomatch(globPattern, { dot: true }));
|
|
1064
1257
|
const filterGitIgnoredEntries = (entries, directoryKeys = new Set()) => entries
|
|
1065
1258
|
.map(({ path: filePath, realPath }) => ({ filePath: normalizePath(filePath), realPath }))
|
|
@@ -1084,6 +1277,7 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1084
1277
|
? { onSymlinkDirectoriesDiscovered: options.onSymlinkDirectoriesDiscovered }
|
|
1085
1278
|
: {}),
|
|
1086
1279
|
...(reportMetadataSymlinkChecks ? { onPathCheckProgress: reportMetadataSymlinkChecks } : {}),
|
|
1280
|
+
...globFilesystemField(discovery),
|
|
1087
1281
|
};
|
|
1088
1282
|
const safeSymlinkDirectories = await resolveSafeSymlinkDirectories(root, realRoot, DEFAULT_PROJECT_FILE_IGNORES, symlinkOptions);
|
|
1089
1283
|
const allPatterns = PROJECT_FILE_DEFINITIONS.flatMap((definition) => definition.patterns.map(toProjectGlob));
|
|
@@ -1091,6 +1285,7 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1091
1285
|
markDirectories: true,
|
|
1092
1286
|
onlyFiles: false,
|
|
1093
1287
|
resolvedSafeSymlinkDirectories: safeSymlinkDirectories,
|
|
1288
|
+
...globFilesystemField(discovery),
|
|
1094
1289
|
});
|
|
1095
1290
|
const candidateFiles = Array.from(new Set(gitCandidates.files.map(normalizePath)));
|
|
1096
1291
|
// Only a candidate whose basename can name a manifest becomes a metadata entry, so
|
|
@@ -1123,6 +1318,7 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1123
1318
|
ignore: DEFAULT_PROJECT_FILE_IGNORES,
|
|
1124
1319
|
markDirectories: true,
|
|
1125
1320
|
onlyFiles: false,
|
|
1321
|
+
...globFilesystemOption(discovery),
|
|
1126
1322
|
});
|
|
1127
1323
|
const linkedMatches = await listEntriesFromSafeSymlinkDirectories(root, realRoot, allPatterns, DEFAULT_PROJECT_FILE_IGNORES, {
|
|
1128
1324
|
markDirectories: true,
|
|
@@ -1134,6 +1330,7 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1134
1330
|
? { onSymlinkDirectoriesDiscovered: options.onSymlinkDirectoriesDiscovered }
|
|
1135
1331
|
: {}),
|
|
1136
1332
|
...(reportMetadataSymlinkChecks ? { onPathCheckProgress: reportMetadataSymlinkChecks } : {}),
|
|
1333
|
+
...globFilesystemField(discovery),
|
|
1137
1334
|
});
|
|
1138
1335
|
rootSafeMatches = await filterRealPathsWithinRoot([...matches, ...linkedMatches].map((match) => (match.endsWith("/") ? match.slice(0, -1) : match)), realRoot, reportMetadataFileChecks);
|
|
1139
1336
|
}
|
|
@@ -1150,7 +1347,7 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1150
1347
|
continue;
|
|
1151
1348
|
const matchesPattern = matchesDefinition(fileName, definitionIndex);
|
|
1152
1349
|
if (matchesPattern) {
|
|
1153
|
-
entries.push(await buildProjectFileInfo(def, cleanMatch));
|
|
1350
|
+
entries.push(await buildProjectFileInfo(def, cleanMatch, discovery));
|
|
1154
1351
|
}
|
|
1155
1352
|
}
|
|
1156
1353
|
});
|
|
@@ -1174,4 +1371,88 @@ async function discoverProjectFilesInternal(projectRoot, options) {
|
|
|
1174
1371
|
throw new Error(`Failed to discover project files in ${root}: ${stringifyUnknown(error)}`);
|
|
1175
1372
|
}
|
|
1176
1373
|
}
|
|
1374
|
+
async function addGitExcludeFiles(files, repositoryRoot, gitCache) {
|
|
1375
|
+
const excludes = await listGitExcludeFiles(repositoryRoot, gitExcludeListOptions(gitCache));
|
|
1376
|
+
for (const source of excludes) {
|
|
1377
|
+
files.add(normalizePath(source.file));
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
async function collectAncestorGitignoreFiles(fromDir, stopDir) {
|
|
1381
|
+
const files = [];
|
|
1382
|
+
let current = normalizePath(fromDir);
|
|
1383
|
+
const stopKey = fileIdentityKey(stopDir);
|
|
1384
|
+
while (true) {
|
|
1385
|
+
const hit = await gitignoreFileIfPresent(current);
|
|
1386
|
+
if (hit)
|
|
1387
|
+
files.push(hit);
|
|
1388
|
+
if (fileIdentityKey(current) === stopKey)
|
|
1389
|
+
break;
|
|
1390
|
+
const parent = normalizePath(path.dirname(current));
|
|
1391
|
+
if (parent === current)
|
|
1392
|
+
break;
|
|
1393
|
+
if (!isFilePathWithinRoot(stopDir, parent) && fileIdentityKey(parent) !== stopKey)
|
|
1394
|
+
break;
|
|
1395
|
+
current = parent;
|
|
1396
|
+
}
|
|
1397
|
+
return files;
|
|
1398
|
+
}
|
|
1399
|
+
async function addGitignoreFilesForRoots(files, roots, state, scanned) {
|
|
1400
|
+
for (const ignoreRoot of roots) {
|
|
1401
|
+
const key = fileIdentityKey(ignoreRoot);
|
|
1402
|
+
if (scanned.has(key))
|
|
1403
|
+
continue;
|
|
1404
|
+
scanned.add(key);
|
|
1405
|
+
for (const file of await findGitignoreFiles(ignoreRoot, state)) {
|
|
1406
|
+
files.add(normalizePath(file));
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
export async function collectConfigHashInputFiles(projectRoot, discoveryContext, logLevel, callbacks) {
|
|
1411
|
+
const root = await ensureDirectoryReadable(projectRoot, "Project root");
|
|
1412
|
+
const state = discoveryContext;
|
|
1413
|
+
const files = new Set(await listRootConfigFiles(root, state));
|
|
1414
|
+
const gitCache = discoveryContext.git;
|
|
1415
|
+
const gitCandidates = await getSharedGitCandidates(discoveryContext, root, logLevel, callbacks);
|
|
1416
|
+
if (gitCandidates) {
|
|
1417
|
+
for (const source of gitCandidates.gitignoreFiles) {
|
|
1418
|
+
files.add(normalizePath(source.file));
|
|
1419
|
+
}
|
|
1420
|
+
return { files: Array.from(files).sort() };
|
|
1421
|
+
}
|
|
1422
|
+
const realRoot = normalizePath(await fsp.realpath(root));
|
|
1423
|
+
const gitRootCandidate = (await isGitRepo(root, gitCache)) ? root : realRoot;
|
|
1424
|
+
if (!(await isGitRepo(gitRootCandidate, gitCache))) {
|
|
1425
|
+
await addGitignoreFilesForRoots(files, [root], state, new Set());
|
|
1426
|
+
return { files: Array.from(files).sort() };
|
|
1427
|
+
}
|
|
1428
|
+
if (await isGitProjectRootIgnored(realRoot, gitCache)) {
|
|
1429
|
+
const scanned = new Set();
|
|
1430
|
+
await addGitignoreFilesForRoots(files, [root], state, scanned);
|
|
1431
|
+
const gitRoot = await getGitRepositoryRoot(gitRootCandidate);
|
|
1432
|
+
if (gitRoot) {
|
|
1433
|
+
await addGitExcludeFiles(files, gitRoot, gitCache);
|
|
1434
|
+
const submoduleDirectories = await listGitSubmoduleDirectories(gitRoot, { recurse: true });
|
|
1435
|
+
await addGitignoreFilesForRoots(files, [gitRoot, ...submoduleDirectories], state, scanned);
|
|
1436
|
+
for (const submodule of submoduleDirectories) {
|
|
1437
|
+
await addGitExcludeFiles(files, submodule, gitCache);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return { files: Array.from(files).sort() };
|
|
1441
|
+
}
|
|
1442
|
+
const gitRoot = await getGitRepositoryRoot(gitRootCandidate);
|
|
1443
|
+
if (gitRoot) {
|
|
1444
|
+
for (const file of await collectAncestorGitignoreFiles(root, gitRoot)) {
|
|
1445
|
+
files.add(normalizePath(file));
|
|
1446
|
+
}
|
|
1447
|
+
await addGitExcludeFiles(files, gitRoot, gitCache);
|
|
1448
|
+
const submoduleDirectories = await listGitSubmoduleDirectories(gitRoot, { recurse: true });
|
|
1449
|
+
for (const submodule of submoduleDirectories) {
|
|
1450
|
+
await addGitExcludeFiles(files, submodule, gitCache);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
return {
|
|
1454
|
+
files: Array.from(files).sort(),
|
|
1455
|
+
error: "Failed to enumerate config files: Git listing unavailable",
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1177
1458
|
//# sourceMappingURL=projectFiles.js.map
|