mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -07:00
refactor: Switch over to unified shouldIgnoreFile (#11815)
This commit is contained in:
@@ -12,6 +12,7 @@ import type {
|
|||||||
ToolResult,
|
ToolResult,
|
||||||
ToolCallConfirmationDetails,
|
ToolCallConfirmationDetails,
|
||||||
GeminiCLIExtension,
|
GeminiCLIExtension,
|
||||||
|
FilterFilesOptions,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import {
|
import {
|
||||||
AuthType,
|
AuthType,
|
||||||
@@ -571,7 +572,8 @@ class Session {
|
|||||||
|
|
||||||
// Get centralized file discovery service
|
// Get centralized file discovery service
|
||||||
const fileDiscovery = this.config.getFileService();
|
const fileDiscovery = this.config.getFileService();
|
||||||
const respectGitIgnore = this.config.getFileFilteringRespectGitIgnore();
|
const fileFilteringOptions: FilterFilesOptions =
|
||||||
|
this.config.getFileFilteringOptions();
|
||||||
|
|
||||||
const pathSpecsToRead: string[] = [];
|
const pathSpecsToRead: string[] = [];
|
||||||
const contentLabelsForDisplay: string[] = [];
|
const contentLabelsForDisplay: string[] = [];
|
||||||
@@ -587,13 +589,10 @@ class Session {
|
|||||||
|
|
||||||
for (const atPathPart of atPathCommandParts) {
|
for (const atPathPart of atPathCommandParts) {
|
||||||
const pathName = atPathPart.fileData!.fileUri;
|
const pathName = atPathPart.fileData!.fileUri;
|
||||||
// Check if path should be ignored by git
|
// Check if path should be ignored
|
||||||
if (fileDiscovery.shouldGitIgnoreFile(pathName)) {
|
if (fileDiscovery.shouldIgnoreFile(pathName, fileFilteringOptions)) {
|
||||||
ignoredPaths.push(pathName);
|
ignoredPaths.push(pathName);
|
||||||
const reason = respectGitIgnore
|
debugLogger.warn(`Path ${pathName} is ignored and will be skipped.`);
|
||||||
? 'git-ignored and will be skipped'
|
|
||||||
: 'ignored by custom patterns';
|
|
||||||
debugLogger.warn(`Path ${pathName} is ${reason}.`);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let currentPathSpec = pathName;
|
let currentPathSpec = pathName;
|
||||||
@@ -730,9 +729,8 @@ class Session {
|
|||||||
initialQueryText = initialQueryText.trim();
|
initialQueryText = initialQueryText.trim();
|
||||||
// Inform user about ignored paths
|
// Inform user about ignored paths
|
||||||
if (ignoredPaths.length > 0) {
|
if (ignoredPaths.length > 0) {
|
||||||
const ignoreType = respectGitIgnore ? 'git-ignored' : 'custom-ignored';
|
|
||||||
this.debug(
|
this.debug(
|
||||||
`Ignored ${ignoredPaths.length} ${ignoreType} files: ${ignoredPaths.join(', ')}`,
|
`Ignored ${ignoredPaths.length} files: ${ignoredPaths.join(', ')}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -747,7 +745,6 @@ class Session {
|
|||||||
if (pathSpecsToRead.length > 0) {
|
if (pathSpecsToRead.length > 0) {
|
||||||
const toolArgs = {
|
const toolArgs = {
|
||||||
paths: pathSpecsToRead,
|
paths: pathSpecsToRead,
|
||||||
respectGitIgnore, // Use configuration setting
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const callId = `${readManyFilesTool.name}-${Date.now()}`;
|
const callId = `${readManyFilesTool.name}-${Date.now()}`;
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ describe('FileDiscoveryService', () => {
|
|||||||
|
|
||||||
const service = new FileDiscoveryService(projectRoot);
|
const service = new FileDiscoveryService(projectRoot);
|
||||||
// Let's check the effect of the parser instead of mocking it.
|
// Let's check the effect of the parser instead of mocking it.
|
||||||
expect(service.shouldGitIgnoreFile('node_modules/foo.js')).toBe(true);
|
expect(service.shouldIgnoreFile('node_modules/foo.js')).toBe(true);
|
||||||
expect(service.shouldGitIgnoreFile('src/foo.js')).toBe(false);
|
expect(service.shouldIgnoreFile('src/foo.js')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not load git repo patterns when not in a git repo', async () => {
|
it('should not load git repo patterns when not in a git repo', async () => {
|
||||||
@@ -50,15 +50,15 @@ describe('FileDiscoveryService', () => {
|
|||||||
const service = new FileDiscoveryService(projectRoot);
|
const service = new FileDiscoveryService(projectRoot);
|
||||||
|
|
||||||
// .gitignore is not loaded in non-git repos
|
// .gitignore is not loaded in non-git repos
|
||||||
expect(service.shouldGitIgnoreFile('node_modules/foo.js')).toBe(false);
|
expect(service.shouldIgnoreFile('node_modules/foo.js')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load .geminiignore patterns even when not in a git repo', async () => {
|
it('should load .geminiignore patterns even when not in a git repo', async () => {
|
||||||
await createTestFile('.geminiignore', 'secrets.txt');
|
await createTestFile('.geminiignore', 'secrets.txt');
|
||||||
const service = new FileDiscoveryService(projectRoot);
|
const service = new FileDiscoveryService(projectRoot);
|
||||||
|
|
||||||
expect(service.shouldGeminiIgnoreFile('secrets.txt')).toBe(true);
|
expect(service.shouldIgnoreFile('secrets.txt')).toBe(true);
|
||||||
expect(service.shouldGeminiIgnoreFile('src/index.js')).toBe(false);
|
expect(service.shouldIgnoreFile('src/index.js')).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ describe('FileDiscoveryService', () => {
|
|||||||
const service = new FileDiscoveryService(projectRoot);
|
const service = new FileDiscoveryService(projectRoot);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
service.shouldGitIgnoreFile(
|
service.shouldIgnoreFile(
|
||||||
path.join(projectRoot, 'node_modules/package/index.js'),
|
path.join(projectRoot, 'node_modules/package/index.js'),
|
||||||
),
|
),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
@@ -194,7 +194,7 @@ describe('FileDiscoveryService', () => {
|
|||||||
const service = new FileDiscoveryService(projectRoot);
|
const service = new FileDiscoveryService(projectRoot);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
service.shouldGitIgnoreFile(path.join(projectRoot, 'src/index.ts')),
|
service.shouldIgnoreFile(path.join(projectRoot, 'src/index.ts')),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ describe('FileDiscoveryService', () => {
|
|||||||
const service = new FileDiscoveryService(projectRoot);
|
const service = new FileDiscoveryService(projectRoot);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
service.shouldGeminiIgnoreFile(path.join(projectRoot, 'debug.log')),
|
service.shouldIgnoreFile(path.join(projectRoot, 'debug.log')),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ describe('FileDiscoveryService', () => {
|
|||||||
const service = new FileDiscoveryService(projectRoot);
|
const service = new FileDiscoveryService(projectRoot);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
service.shouldGeminiIgnoreFile(path.join(projectRoot, 'src/index.ts')),
|
service.shouldIgnoreFile(path.join(projectRoot, 'src/index.ts')),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -224,10 +224,10 @@ describe('FileDiscoveryService', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
service.shouldGitIgnoreFile(path.join(projectRoot, 'ignored.txt')),
|
service.shouldIgnoreFile(path.join(projectRoot, 'ignored.txt')),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
expect(
|
expect(
|
||||||
service.shouldGitIgnoreFile(path.join(projectRoot, 'not-ignored.txt')),
|
service.shouldIgnoreFile(path.join(projectRoot, 'not-ignored.txt')),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -37,21 +37,13 @@ export class FileDiscoveryService {
|
|||||||
/**
|
/**
|
||||||
* Filters a list of file paths based on git ignore rules
|
* Filters a list of file paths based on git ignore rules
|
||||||
*/
|
*/
|
||||||
filterFiles(
|
filterFiles(filePaths: string[], options: FilterFilesOptions = {}): string[] {
|
||||||
filePaths: string[],
|
const { respectGitIgnore = true, respectGeminiIgnore = true } = options;
|
||||||
options: FilterFilesOptions = {
|
|
||||||
respectGitIgnore: true,
|
|
||||||
respectGeminiIgnore: true,
|
|
||||||
},
|
|
||||||
): string[] {
|
|
||||||
return filePaths.filter((filePath) => {
|
return filePaths.filter((filePath) => {
|
||||||
if (options.respectGitIgnore && this.shouldGitIgnoreFile(filePath)) {
|
if (respectGitIgnore && this.gitIgnoreFilter?.isIgnored(filePath)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (
|
if (respectGeminiIgnore && this.geminiIgnoreFilter?.isIgnored(filePath)) {
|
||||||
options.respectGeminiIgnore &&
|
|
||||||
this.shouldGeminiIgnoreFile(filePath)
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -78,26 +70,6 @@ export class FileDiscoveryService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a single file should be git-ignored
|
|
||||||
*/
|
|
||||||
shouldGitIgnoreFile(filePath: string): boolean {
|
|
||||||
if (this.gitIgnoreFilter) {
|
|
||||||
return this.gitIgnoreFilter.isIgnored(filePath);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a single file should be gemini-ignored
|
|
||||||
*/
|
|
||||||
shouldGeminiIgnoreFile(filePath: string): boolean {
|
|
||||||
if (this.geminiIgnoreFilter) {
|
|
||||||
return this.geminiIgnoreFilter.isIgnored(filePath);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unified method to check if a file should be ignored based on filtering options
|
* Unified method to check if a file should be ignored based on filtering options
|
||||||
*/
|
*/
|
||||||
@@ -105,14 +77,6 @@ export class FileDiscoveryService {
|
|||||||
filePath: string,
|
filePath: string,
|
||||||
options: FilterFilesOptions = {},
|
options: FilterFilesOptions = {},
|
||||||
): boolean {
|
): boolean {
|
||||||
const { respectGitIgnore = true, respectGeminiIgnore = true } = options;
|
return this.filterFiles([filePath], options).length === 0;
|
||||||
|
|
||||||
if (respectGitIgnore && this.shouldGitIgnoreFile(filePath)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (respectGeminiIgnore && this.shouldGeminiIgnoreFile(filePath)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ describe('ReadFileTool', () => {
|
|||||||
getFileSystemService: () => new StandardFileSystemService(),
|
getFileSystemService: () => new StandardFileSystemService(),
|
||||||
getTargetDir: () => tempRootDir,
|
getTargetDir: () => tempRootDir,
|
||||||
getWorkspaceContext: () => createMockWorkspaceContext(tempRootDir),
|
getWorkspaceContext: () => createMockWorkspaceContext(tempRootDir),
|
||||||
|
getFileFilteringOptions: () => ({
|
||||||
|
respectGitIgnore: true,
|
||||||
|
respectGeminiIgnore: true,
|
||||||
|
}),
|
||||||
storage: {
|
storage: {
|
||||||
getProjectTempDir: () => path.join(tempRootDir, '.temp'),
|
getProjectTempDir: () => path.join(tempRootDir, '.temp'),
|
||||||
},
|
},
|
||||||
@@ -462,7 +466,7 @@ describe('ReadFileTool', () => {
|
|||||||
const params: ReadFileToolParams = {
|
const params: ReadFileToolParams = {
|
||||||
absolute_path: ignoredFilePath,
|
absolute_path: ignoredFilePath,
|
||||||
};
|
};
|
||||||
const expectedError = `File path '${ignoredFilePath}' is ignored by .geminiignore pattern(s).`;
|
const expectedError = `File path '${ignoredFilePath}' is ignored by configured ignore patterns.`;
|
||||||
expect(() => tool.build(params)).toThrow(expectedError);
|
expect(() => tool.build(params)).toThrow(expectedError);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -474,7 +478,7 @@ describe('ReadFileTool', () => {
|
|||||||
const params: ReadFileToolParams = {
|
const params: ReadFileToolParams = {
|
||||||
absolute_path: ignoredFilePath,
|
absolute_path: ignoredFilePath,
|
||||||
};
|
};
|
||||||
const expectedError = `File path '${ignoredFilePath}' is ignored by .geminiignore pattern(s).`;
|
const expectedError = `File path '${ignoredFilePath}' is ignored by configured ignore patterns.`;
|
||||||
expect(() => tool.build(params)).toThrow(expectedError);
|
expect(() => tool.build(params)).toThrow(expectedError);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -210,8 +210,11 @@ export class ReadFileTool extends BaseDeclarativeTool<
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fileService = this.config.getFileService();
|
const fileService = this.config.getFileService();
|
||||||
if (fileService.shouldGeminiIgnoreFile(params.absolute_path)) {
|
const fileFilteringOptions = this.config.getFileFilteringOptions();
|
||||||
return `File path '${filePath}' is ignored by .geminiignore pattern(s).`;
|
if (
|
||||||
|
fileService.shouldIgnoreFile(params.absolute_path, fileFilteringOptions)
|
||||||
|
) {
|
||||||
|
return `File path '${filePath}' is ignored by configured ignore patterns.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import * as fs from 'node:fs/promises';
|
|||||||
import type { Dirent } from 'node:fs';
|
import type { Dirent } from 'node:fs';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import { getErrorMessage, isNodeError } from './errors.js';
|
import { getErrorMessage, isNodeError } from './errors.js';
|
||||||
import type { FileDiscoveryService } from '../services/fileDiscoveryService.js';
|
import type {
|
||||||
|
FileDiscoveryService,
|
||||||
|
FilterFilesOptions,
|
||||||
|
} from '../services/fileDiscoveryService.js';
|
||||||
import type { FileFilteringOptions } from '../config/constants.js';
|
import type { FileFilteringOptions } from '../config/constants.js';
|
||||||
import { DEFAULT_FILE_FILTERING_OPTIONS } from '../config/constants.js';
|
import { DEFAULT_FILE_FILTERING_OPTIONS } from '../config/constants.js';
|
||||||
import { debugLogger } from './debugLogger.js';
|
import { debugLogger } from './debugLogger.js';
|
||||||
@@ -119,6 +122,10 @@ async function readFullStructure(
|
|||||||
|
|
||||||
const filesInCurrentDir: string[] = [];
|
const filesInCurrentDir: string[] = [];
|
||||||
const subFoldersInCurrentDir: FullFolderInfo[] = [];
|
const subFoldersInCurrentDir: FullFolderInfo[] = [];
|
||||||
|
const filterFileOptions: FilterFilesOptions = {
|
||||||
|
respectGitIgnore: options.fileFilteringOptions?.respectGitIgnore,
|
||||||
|
respectGeminiIgnore: options.fileFilteringOptions?.respectGeminiIgnore,
|
||||||
|
};
|
||||||
|
|
||||||
// Process files first in the current directory
|
// Process files first in the current directory
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
@@ -129,15 +136,10 @@ async function readFullStructure(
|
|||||||
}
|
}
|
||||||
const fileName = entry.name;
|
const fileName = entry.name;
|
||||||
const filePath = path.join(currentPath, fileName);
|
const filePath = path.join(currentPath, fileName);
|
||||||
if (options.fileService) {
|
if (
|
||||||
const shouldIgnore =
|
options.fileService?.shouldIgnoreFile(filePath, filterFileOptions)
|
||||||
(options.fileFilteringOptions.respectGitIgnore &&
|
) {
|
||||||
options.fileService.shouldGitIgnoreFile(filePath)) ||
|
continue;
|
||||||
(options.fileFilteringOptions.respectGeminiIgnore &&
|
|
||||||
options.fileService.shouldGeminiIgnoreFile(filePath));
|
|
||||||
if (shouldIgnore) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!options.fileIncludePattern ||
|
!options.fileIncludePattern ||
|
||||||
@@ -168,14 +170,11 @@ async function readFullStructure(
|
|||||||
const subFolderName = entry.name;
|
const subFolderName = entry.name;
|
||||||
const subFolderPath = path.join(currentPath, subFolderName);
|
const subFolderPath = path.join(currentPath, subFolderName);
|
||||||
|
|
||||||
let isIgnored = false;
|
const isIgnored =
|
||||||
if (options.fileService) {
|
options.fileService?.shouldIgnoreFile(
|
||||||
isIgnored =
|
subFolderPath,
|
||||||
(options.fileFilteringOptions.respectGitIgnore &&
|
filterFileOptions,
|
||||||
options.fileService.shouldGitIgnoreFile(subFolderPath)) ||
|
) ?? false;
|
||||||
(options.fileFilteringOptions.respectGeminiIgnore &&
|
|
||||||
options.fileService.shouldGeminiIgnoreFile(subFolderPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.ignoredFolders.has(subFolderName) || isIgnored) {
|
if (options.ignoredFolders.has(subFolderName) || isIgnored) {
|
||||||
const ignoredSubFolder: FullFolderInfo = {
|
const ignoredSubFolder: FullFolderInfo = {
|
||||||
|
|||||||
Reference in New Issue
Block a user