feat(config): codegraph.json include to force gitignored first-party source into the index (#1063)
Adds an `include` list to the root `codegraph.json` that forces gitignored first-party source (second-VCS / SVN / Perforce dual-tracked repos) into the index — discovered directly off disk on the full index, incremental sync, and file-watching, on both git and non-git projects. Gitignore-style patterns, root-relative; explicit `exclude` still wins and built-in skips (node_modules, dist, .git) are never re-included. Complements `exclude` and `includeIgnored`. Closes #1163. Thanks @luoyxy for the contribution.
This commit is contained in:
+65
-2
@@ -53,6 +53,20 @@ export interface ProjectConfig {
|
||||
* and your `.gitignore`.
|
||||
*/
|
||||
exclude?: string[];
|
||||
/**
|
||||
* Gitignore-style patterns for first-party source to force INTO the index even
|
||||
* when `.gitignore` would drop it — the general whitelist `includeIgnored`
|
||||
* never was (that one only revives *embedded git repos* inside ignored dirs).
|
||||
* The case this exists for: a project under a second VCS (SVN, Perforce, …)
|
||||
* deliberately `.gitignore`s its own real source so it never lands in Git, yet
|
||||
* that source must still be indexed. Matched against project-root-relative
|
||||
* paths, so `"Tools/"`, a recursive `"Tools/**"` glob, or `"Local/typescript"`
|
||||
* all work.
|
||||
* Built-in default-ignored dirs (`node_modules`, `dist`, …), `.git`, and
|
||||
* CodeGraph's own data dir are never resurfaced; an explicit `exclude` still
|
||||
* wins. Absent/empty (the default) forces nothing in.
|
||||
*/
|
||||
include?: string[];
|
||||
}
|
||||
|
||||
/** Parsed, validated view of a project's `codegraph.json`. */
|
||||
@@ -60,6 +74,7 @@ interface ParsedConfig {
|
||||
extensions: Record<string, Language>;
|
||||
includeIgnored: string[];
|
||||
exclude: string[];
|
||||
include: string[];
|
||||
}
|
||||
|
||||
interface CacheEntry {
|
||||
@@ -81,6 +96,7 @@ const EMPTY_CONFIG: ParsedConfig = Object.freeze({
|
||||
extensions: EMPTY_EXTENSIONS,
|
||||
includeIgnored: Object.freeze([]) as unknown as string[],
|
||||
exclude: Object.freeze([]) as unknown as string[],
|
||||
include: Object.freeze([]) as unknown as string[],
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -132,10 +148,16 @@ function parseConfig(file: string): ParsedConfig {
|
||||
const extensions = extractExtensions(parsed, file);
|
||||
const includeIgnored = extractIncludeIgnored(parsed, file);
|
||||
const exclude = extractExclude(parsed, file);
|
||||
if (extensions === EMPTY_EXTENSIONS && includeIgnored.length === 0 && exclude.length === 0) {
|
||||
const include = extractInclude(parsed, file);
|
||||
if (
|
||||
extensions === EMPTY_EXTENSIONS &&
|
||||
includeIgnored.length === 0 &&
|
||||
exclude.length === 0 &&
|
||||
include.length === 0
|
||||
) {
|
||||
return EMPTY_CONFIG;
|
||||
}
|
||||
return { extensions, includeIgnored, exclude };
|
||||
return { extensions, includeIgnored, exclude, include };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,6 +236,34 @@ function extractExclude(parsed: object, file: string): string[] {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the `include` patterns: an array of non-empty gitignore-style strings
|
||||
* naming first-party source to force INTO the index despite `.gitignore` — the
|
||||
* whitelist for SVN/Perforce-only source a project gitignores out of Git (the
|
||||
* general case `includeIgnored` never covered). A non-array value or a
|
||||
* non-string/blank entry warns-and-skips; never throws. Patterns are kept
|
||||
* verbatim (trimmed) so they match exactly as a `.gitignore` line would, against
|
||||
* project-root-relative paths.
|
||||
*/
|
||||
function extractInclude(parsed: object, file: string): string[] {
|
||||
const raw = (parsed as ProjectConfig).include;
|
||||
if (raw === undefined) return [];
|
||||
if (!Array.isArray(raw)) {
|
||||
logWarn(`Ignoring "include" in ${PROJECT_CONFIG_FILENAME}: must be an array of gitignore-style patterns`, { file });
|
||||
return [];
|
||||
}
|
||||
|
||||
const out: string[] = [];
|
||||
for (const entry of raw) {
|
||||
if (typeof entry !== 'string' || !entry.trim()) {
|
||||
logWarn(`Ignoring an "include" entry in ${PROJECT_CONFIG_FILENAME}: every pattern must be a non-empty string`, { file });
|
||||
continue;
|
||||
}
|
||||
out.push(entry.trim());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the parsed `codegraph.json` for a project, mtime-cached. A missing or
|
||||
* malformed file yields the zero-config default. One `stat` (and at most one
|
||||
@@ -275,6 +325,19 @@ export function loadExcludePatterns(rootDir: string): string[] {
|
||||
return loadParsedConfig(rootDir).exclude;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the validated `include` patterns for a project, mtime-cached.
|
||||
*
|
||||
* These name first-party source to force INTO the index even when `.gitignore`
|
||||
* would drop it — the whitelist for SVN/Perforce-only source a project
|
||||
* gitignores out of Git. An empty result — the zero-config default — forces
|
||||
* nothing in. Built-in default-ignored dirs, `.git`, and CodeGraph's data dir
|
||||
* are never resurfaced, and an explicit `exclude` still wins.
|
||||
*/
|
||||
export function loadIncludePatterns(rootDir: string): string[] {
|
||||
return loadParsedConfig(rootDir).include;
|
||||
}
|
||||
|
||||
/** Test/maintenance hook: forget cached config (e.g. after rewriting it in a test). */
|
||||
export function clearProjectConfigCache(): void {
|
||||
cache.clear();
|
||||
|
||||
Reference in New Issue
Block a user