Add support for an additional exclusion file besides .gitignore and .geminiignore (#16487)

Co-authored-by: Adam Weidman <adamfweidman@google.com>
This commit is contained in:
Alisa
2026-01-27 17:19:13 -08:00
committed by GitHub
parent bde29518cd
commit 3b9af4b813
40 changed files with 1394 additions and 612 deletions
@@ -5,15 +5,18 @@
*/
import type { GitIgnoreFilter } from '../utils/gitIgnoreParser.js';
import type { GeminiIgnoreFilter } from '../utils/geminiIgnoreParser.js';
import type { IgnoreFileFilter } from '../utils/ignoreFileParser.js';
import { GitIgnoreParser } from '../utils/gitIgnoreParser.js';
import { GeminiIgnoreParser } from '../utils/geminiIgnoreParser.js';
import { IgnoreFileParser } from '../utils/ignoreFileParser.js';
import { isGitRepository } from '../utils/gitUtils.js';
import { GEMINI_IGNORE_FILE_NAME } from '../config/constants.js';
import fs from 'node:fs';
import * as path from 'node:path';
export interface FilterFilesOptions {
respectGitIgnore?: boolean;
respectGeminiIgnore?: boolean;
customIgnoreFilePaths?: string[];
}
export interface FilterReport {
@@ -23,32 +26,83 @@ export interface FilterReport {
export class FileDiscoveryService {
private gitIgnoreFilter: GitIgnoreFilter | null = null;
private geminiIgnoreFilter: GeminiIgnoreFilter | null = null;
private combinedIgnoreFilter: GitIgnoreFilter | null = null;
private geminiIgnoreFilter: IgnoreFileFilter | null = null;
private customIgnoreFilter: IgnoreFileFilter | null = null;
private combinedIgnoreFilter: GitIgnoreFilter | IgnoreFileFilter | null =
null;
private defaultFilterFileOptions: FilterFilesOptions = {
respectGitIgnore: true,
respectGeminiIgnore: true,
customIgnoreFilePaths: [],
};
private projectRoot: string;
constructor(projectRoot: string) {
constructor(projectRoot: string, options?: FilterFilesOptions) {
this.projectRoot = path.resolve(projectRoot);
this.applyFilterFilesOptions(options);
if (isGitRepository(this.projectRoot)) {
this.gitIgnoreFilter = new GitIgnoreParser(this.projectRoot);
}
this.geminiIgnoreFilter = new GeminiIgnoreParser(this.projectRoot);
this.geminiIgnoreFilter = new IgnoreFileParser(
this.projectRoot,
GEMINI_IGNORE_FILE_NAME,
);
if (this.defaultFilterFileOptions.customIgnoreFilePaths?.length) {
this.customIgnoreFilter = new IgnoreFileParser(
this.projectRoot,
this.defaultFilterFileOptions.customIgnoreFilePaths,
);
}
if (this.gitIgnoreFilter) {
const geminiPatterns = this.geminiIgnoreFilter.getPatterns();
// Create combined parser: .gitignore + .geminiignore
const customPatterns = this.customIgnoreFilter
? this.customIgnoreFilter.getPatterns()
: [];
// Create combined parser: .gitignore + .geminiignore + custom ignore
this.combinedIgnoreFilter = new GitIgnoreParser(
this.projectRoot,
geminiPatterns,
// customPatterns should go the last to ensure overwriting of geminiPatterns
[...geminiPatterns, ...customPatterns],
);
} else {
// Create combined parser when not git repo
const geminiPatterns = this.geminiIgnoreFilter.getPatterns();
const customPatterns = this.customIgnoreFilter
? this.customIgnoreFilter.getPatterns()
: [];
this.combinedIgnoreFilter = new IgnoreFileParser(
this.projectRoot,
[...geminiPatterns, ...customPatterns],
true,
);
}
}
private applyFilterFilesOptions(options?: FilterFilesOptions): void {
if (!options) return;
if (options.respectGitIgnore !== undefined) {
this.defaultFilterFileOptions.respectGitIgnore = options.respectGitIgnore;
}
if (options.respectGeminiIgnore !== undefined) {
this.defaultFilterFileOptions.respectGeminiIgnore =
options.respectGeminiIgnore;
}
if (options.customIgnoreFilePaths) {
this.defaultFilterFileOptions.customIgnoreFilePaths =
options.customIgnoreFilePaths;
}
}
/**
* Filters a list of file paths based on git ignore rules
* Filters a list of file paths based on ignore rules
*/
filterFiles(filePaths: string[], options: FilterFilesOptions = {}): string[] {
const { respectGitIgnore = true, respectGeminiIgnore = true } = options;
const {
respectGitIgnore = this.defaultFilterFileOptions.respectGitIgnore,
respectGeminiIgnore = this.defaultFilterFileOptions.respectGeminiIgnore,
} = options;
return filePaths.filter((filePath) => {
if (
respectGitIgnore &&
@@ -58,6 +112,11 @@ export class FileDiscoveryService {
return !this.combinedIgnoreFilter.isIgnored(filePath);
}
// Always respect custom ignore filter if provided
if (this.customIgnoreFilter?.isIgnored(filePath)) {
return false;
}
if (respectGitIgnore && this.gitIgnoreFilter?.isIgnored(filePath)) {
return false;
}
@@ -97,4 +156,38 @@ export class FileDiscoveryService {
): boolean {
return this.filterFiles([filePath], options).length === 0;
}
/**
* Returns the list of ignore files being used (e.g. .geminiignore) excluding .gitignore.
*/
getIgnoreFilePaths(): string[] {
const paths: string[] = [];
if (
this.geminiIgnoreFilter &&
this.defaultFilterFileOptions.respectGeminiIgnore
) {
paths.push(...this.geminiIgnoreFilter.getIgnoreFilePaths());
}
if (this.customIgnoreFilter) {
paths.push(...this.customIgnoreFilter.getIgnoreFilePaths());
}
return paths;
}
/**
* Returns all ignore files including .gitignore if applicable.
*/
getAllIgnoreFilePaths(): string[] {
const paths: string[] = [];
if (
this.gitIgnoreFilter &&
this.defaultFilterFileOptions.respectGitIgnore
) {
const gitIgnorePath = path.join(this.projectRoot, '.gitignore');
if (fs.existsSync(gitIgnorePath)) {
paths.push(gitIgnorePath);
}
}
return paths.concat(this.getIgnoreFilePaths());
}
}