fix(core): Add .geminiignore support to SearchText tool (#13763)

Co-authored-by: Gaurav <39389231+gsquared94@users.noreply.github.com>
This commit is contained in:
Serghei
2025-12-22 06:25:26 +02:00
committed by GitHub
parent b923604602
commit 58fd00a3df
5 changed files with 183 additions and 0 deletions

View File

@@ -252,6 +252,7 @@ describe('RipGrepTool', () => {
getTargetDir: () => tempRootDir,
getWorkspaceContext: () => createMockWorkspaceContext(tempRootDir),
getDebugMode: () => false,
getFileFilteringRespectGeminiIgnore: () => true,
} as unknown as Config;
beforeEach(async () => {
@@ -735,6 +736,7 @@ describe('RipGrepTool', () => {
getWorkspaceContext: () =>
createMockWorkspaceContext(tempRootDir, [secondDir]),
getDebugMode: () => false,
getFileFilteringRespectGeminiIgnore: () => true,
} as unknown as Config;
// Setup specific mock for this test - multi-directory search for 'world'
@@ -876,6 +878,7 @@ describe('RipGrepTool', () => {
getWorkspaceContext: () =>
createMockWorkspaceContext(tempRootDir, [secondDir]),
getDebugMode: () => false,
getFileFilteringRespectGeminiIgnore: () => true,
} as unknown as Config;
// Setup specific mock for this test - searching in 'sub' should only return matches from that directory
@@ -1644,6 +1647,80 @@ describe('RipGrepTool', () => {
expect(result.llmContent).toContain('L1: secret log entry');
});
it('should add .geminiignore when enabled and patterns exist', async () => {
const geminiIgnorePath = path.join(tempRootDir, '.geminiignore');
await fs.writeFile(geminiIgnorePath, 'ignored.log');
const configWithGeminiIgnore = {
getTargetDir: () => tempRootDir,
getWorkspaceContext: () => createMockWorkspaceContext(tempRootDir),
getDebugMode: () => false,
getFileFilteringRespectGeminiIgnore: () => true,
} as unknown as Config;
const geminiIgnoreTool = new RipGrepTool(configWithGeminiIgnore);
mockSpawn.mockImplementationOnce(
createMockSpawn({
outputData:
JSON.stringify({
type: 'match',
data: {
path: { text: 'ignored.log' },
line_number: 1,
lines: { text: 'secret log entry\n' },
},
}) + '\n',
exitCode: 0,
}),
);
const params: RipGrepToolParams = { pattern: 'secret' };
const invocation = geminiIgnoreTool.build(params);
await invocation.execute(abortSignal);
expect(mockSpawn).toHaveBeenLastCalledWith(
expect.anything(),
expect.arrayContaining(['--ignore-file', geminiIgnorePath]),
expect.anything(),
);
});
it('should skip .geminiignore when disabled', async () => {
const geminiIgnorePath = path.join(tempRootDir, '.geminiignore');
await fs.writeFile(geminiIgnorePath, 'ignored.log');
const configWithoutGeminiIgnore = {
getTargetDir: () => tempRootDir,
getWorkspaceContext: () => createMockWorkspaceContext(tempRootDir),
getDebugMode: () => false,
getFileFilteringRespectGeminiIgnore: () => false,
} as unknown as Config;
const geminiIgnoreTool = new RipGrepTool(configWithoutGeminiIgnore);
mockSpawn.mockImplementationOnce(
createMockSpawn({
outputData:
JSON.stringify({
type: 'match',
data: {
path: { text: 'ignored.log' },
line_number: 1,
lines: { text: 'secret log entry\n' },
},
}) + '\n',
exitCode: 0,
}),
);
const params: RipGrepToolParams = { pattern: 'secret' };
const invocation = geminiIgnoreTool.build(params);
await invocation.execute(abortSignal);
expect(mockSpawn).toHaveBeenLastCalledWith(
expect.anything(),
expect.not.arrayContaining(['--ignore-file', geminiIgnorePath]),
expect.anything(),
);
});
it('should handle context parameters', async () => {
mockSpawn.mockImplementationOnce(
createMockSpawn({
@@ -1761,6 +1838,7 @@ describe('RipGrepTool', () => {
});
});
});
afterAll(() => {
storageSpy.mockRestore();
});

View File

@@ -23,6 +23,7 @@ import {
FileExclusions,
COMMON_DIRECTORY_EXCLUDES,
} from '../utils/ignorePatterns.js';
import { GeminiIgnoreParser } from '../utils/geminiIgnoreParser.js';
const DEFAULT_TOTAL_MAX_MATCHES = 20000;
@@ -189,6 +190,7 @@ class GrepToolInvocation extends BaseToolInvocation<
> {
constructor(
private readonly config: Config,
private readonly geminiIgnoreParser: GeminiIgnoreParser,
params: RipGrepToolParams,
messageBus?: MessageBus,
_toolName?: string,
@@ -387,6 +389,14 @@ class GrepToolInvocation extends BaseToolInvocation<
excludes.forEach((exclude) => {
rgArgs.push('--glob', `!${exclude}`);
});
if (this.config.getFileFilteringRespectGeminiIgnore()) {
// Add .geminiignore support (ripgrep natively handles .gitignore)
const geminiIgnorePath = this.geminiIgnoreParser.getIgnoreFilePath();
if (geminiIgnorePath) {
rgArgs.push('--ignore-file', geminiIgnorePath);
}
}
}
rgArgs.push('--threads', '4');
@@ -479,6 +489,7 @@ export class RipGrepTool extends BaseDeclarativeTool<
ToolResult
> {
static readonly Name = GREP_TOOL_NAME;
private readonly geminiIgnoreParser: GeminiIgnoreParser;
constructor(
private readonly config: Config,
@@ -544,6 +555,7 @@ export class RipGrepTool extends BaseDeclarativeTool<
false, // canUpdateOutput
messageBus,
);
this.geminiIgnoreParser = new GeminiIgnoreParser(config.getTargetDir());
}
/**
@@ -580,6 +592,7 @@ export class RipGrepTool extends BaseDeclarativeTool<
): ToolInvocation<RipGrepToolParams, ToolResult> {
return new GrepToolInvocation(
this.config,
this.geminiIgnoreParser,
params,
messageBus,
_toolName,