From 5407878138131796064440f2b335f9199de57cc0 Mon Sep 17 00:00:00 2001 From: Serghei Date: Tue, 3 Feb 2026 21:37:21 +0200 Subject: [PATCH] fix(core): Respect user's `.gitignore` preference (#15482) Co-authored-by: Gaurav <39389231+gsquared94@users.noreply.github.com> --- integration-tests/ripgrep-real.test.ts | 4 ++ packages/core/src/tools/ripGrep.test.ts | 70 +++++++++++++++++++++++++ packages/core/src/tools/ripGrep.ts | 4 ++ 3 files changed, 78 insertions(+) diff --git a/integration-tests/ripgrep-real.test.ts b/integration-tests/ripgrep-real.test.ts index fee76aedc1..6b2aff905a 100644 --- a/integration-tests/ripgrep-real.test.ts +++ b/integration-tests/ripgrep-real.test.ts @@ -28,6 +28,10 @@ class MockConfig { return true; } + getFileFilteringRespectGitIgnore() { + return true; + } + getFileFilteringRespectGeminiIgnore() { return true; } diff --git a/packages/core/src/tools/ripGrep.test.ts b/packages/core/src/tools/ripGrep.test.ts index 944a320fa4..2e1171d11c 100644 --- a/packages/core/src/tools/ripGrep.test.ts +++ b/packages/core/src/tools/ripGrep.test.ts @@ -253,6 +253,7 @@ describe('RipGrepTool', () => { getTargetDir: () => tempRootDir, getWorkspaceContext: () => createMockWorkspaceContext(tempRootDir), getDebugMode: () => false, + getFileFilteringRespectGitIgnore: () => true, getFileFilteringRespectGeminiIgnore: () => true, getFileFilteringOptions: () => ({ respectGitIgnore: true, @@ -277,6 +278,7 @@ describe('RipGrepTool', () => { getTargetDir: () => tempRootDir, getWorkspaceContext: () => createMockWorkspaceContext(tempRootDir), getDebugMode: () => false, + getFileFilteringRespectGitIgnore: () => true, getFileFilteringRespectGeminiIgnore: () => true, getFileFilteringOptions: () => ({ respectGitIgnore: true, @@ -844,6 +846,7 @@ describe('RipGrepTool', () => { getWorkspaceContext: () => createMockWorkspaceContext(tempRootDir, [secondDir]), getDebugMode: () => false, + getFileFilteringRespectGitIgnore: () => true, getFileFilteringRespectGeminiIgnore: () => true, getFileFilteringOptions: () => ({ respectGitIgnore: true, @@ -956,6 +959,7 @@ describe('RipGrepTool', () => { getWorkspaceContext: () => createMockWorkspaceContext(tempRootDir, [secondDir]), getDebugMode: () => false, + getFileFilteringRespectGitIgnore: () => true, getFileFilteringRespectGeminiIgnore: () => true, getFileFilteringOptions: () => ({ respectGitIgnore: true, @@ -1477,6 +1481,70 @@ describe('RipGrepTool', () => { expect(result.llmContent).toContain('L1: secret log entry'); }); + it('should disable gitignore rules when respectGitIgnore is false', async () => { + const configWithoutGitIgnore = { + getTargetDir: () => tempRootDir, + getWorkspaceContext: () => createMockWorkspaceContext(tempRootDir), + getDebugMode: () => false, + getFileFilteringRespectGitIgnore: () => false, + getFileFilteringRespectGeminiIgnore: () => true, + getFileFilteringOptions: () => ({ + respectGitIgnore: false, + respectGeminiIgnore: true, + }), + storage: { + getProjectTempDir: vi.fn().mockReturnValue('/tmp/project'), + }, + isPathAllowed(this: Config, absolutePath: string): boolean { + const workspaceContext = this.getWorkspaceContext(); + if (workspaceContext.isPathWithinWorkspace(absolutePath)) { + return true; + } + + const projectTempDir = this.storage.getProjectTempDir(); + return isSubpath(path.resolve(projectTempDir), absolutePath); + }, + validatePathAccess(this: Config, absolutePath: string): string | null { + if (this.isPathAllowed(absolutePath)) { + return null; + } + + const workspaceDirs = this.getWorkspaceContext().getDirectories(); + const projectTempDir = this.storage.getProjectTempDir(); + return `Path not in workspace: Attempted path "${absolutePath}" resolves outside the allowed workspace directories: ${workspaceDirs.join(', ')} or the project temp directory: ${projectTempDir}`; + }, + } as unknown as Config; + const gitIgnoreDisabledTool = new RipGrepTool( + configWithoutGitIgnore, + createMockMessageBus(), + ); + + 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 = gitIgnoreDisabledTool.build(params); + await invocation.execute(abortSignal); + + expect(mockSpawn).toHaveBeenLastCalledWith( + expect.anything(), + expect.arrayContaining(['--no-ignore-vcs', '--no-ignore-exclude']), + expect.anything(), + ); + }); + it('should add .geminiignore when enabled and patterns exist', async () => { const geminiIgnorePath = path.join(tempRootDir, GEMINI_IGNORE_FILE_NAME); await fs.writeFile(geminiIgnorePath, 'ignored.log'); @@ -1484,6 +1552,7 @@ describe('RipGrepTool', () => { getTargetDir: () => tempRootDir, getWorkspaceContext: () => createMockWorkspaceContext(tempRootDir), getDebugMode: () => false, + getFileFilteringRespectGitIgnore: () => true, getFileFilteringRespectGeminiIgnore: () => true, getFileFilteringOptions: () => ({ respectGitIgnore: true, @@ -1549,6 +1618,7 @@ describe('RipGrepTool', () => { getTargetDir: () => tempRootDir, getWorkspaceContext: () => createMockWorkspaceContext(tempRootDir), getDebugMode: () => false, + getFileFilteringRespectGitIgnore: () => true, getFileFilteringRespectGeminiIgnore: () => false, getFileFilteringOptions: () => ({ respectGitIgnore: true, diff --git a/packages/core/src/tools/ripGrep.ts b/packages/core/src/tools/ripGrep.ts index e905f2f404..892960fa94 100644 --- a/packages/core/src/tools/ripGrep.ts +++ b/packages/core/src/tools/ripGrep.ts @@ -366,6 +366,10 @@ class GrepToolInvocation extends BaseToolInvocation< } if (!no_ignore) { + if (!this.config.getFileFilteringRespectGitIgnore()) { + rgArgs.push('--no-ignore-vcs', '--no-ignore-exclude'); + } + const fileExclusions = new FileExclusions(this.config); const excludes = fileExclusions.getGlobExcludes([ ...COMMON_DIRECTORY_EXCLUDES,