diff options
| author | Anas H. Sulaiman <[email protected]> | 2025-06-13 17:17:08 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-06-13 17:17:08 -0400 |
| commit | bb67d317394ba4be7b55bdc7175c5d432a40ae53 (patch) | |
| tree | 5ce691bbb871d3aa39bd79e93f8111fa72104ad7 /packages/cli/src | |
| parent | 54f0d9d0e5d8c21ed4c0d5b81ea8beb5908ebd4d (diff) | |
reuse `GitIgnoreParser` for loading `.geminiignore` (#1025)
Diffstat (limited to 'packages/cli/src')
| -rw-r--r-- | packages/cli/src/gemini.tsx | 2 | ||||
| -rw-r--r-- | packages/cli/src/utils/loadIgnorePatterns.test.ts | 93 | ||||
| -rw-r--r-- | packages/cli/src/utils/loadIgnorePatterns.ts | 42 |
3 files changed, 26 insertions, 111 deletions
diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index 35c94214..6cd246db 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -41,7 +41,7 @@ export async function main() { const settings = loadSettings(workspaceRoot); setWindowTitle(basename(workspaceRoot), settings); - const geminiIgnorePatterns = loadGeminiIgnorePatterns(workspaceRoot); + const geminiIgnorePatterns = await loadGeminiIgnorePatterns(workspaceRoot); await cleanupCheckpoints(); if (settings.errors.length > 0) { for (const error of settings.errors) { diff --git a/packages/cli/src/utils/loadIgnorePatterns.test.ts b/packages/cli/src/utils/loadIgnorePatterns.test.ts index 9bcddf34..5ff89c4d 100644 --- a/packages/cli/src/utils/loadIgnorePatterns.test.ts +++ b/packages/cli/src/utils/loadIgnorePatterns.test.ts @@ -42,9 +42,6 @@ describe('loadGeminiIgnorePatterns', () => { let consoleLogSpy: Mock< (message?: unknown, ...optionalParams: unknown[]) => void >; - let consoleWarnSpy: Mock< - (message?: unknown, ...optionalParams: unknown[]) => void - >; beforeAll(async () => { actualFs = await vi.importActual<typeof import('node:fs')>('node:fs'); @@ -62,11 +59,6 @@ describe('loadGeminiIgnorePatterns', () => { .mockImplementation(() => {}) as Mock< (message?: unknown, ...optionalParams: unknown[]) => void >; - consoleWarnSpy = vi - .spyOn(console, 'warn') - .mockImplementation(() => {}) as Mock< - (message?: unknown, ...optionalParams: unknown[]) => void - >; mockedFsReadFileSync.mockReset(); }); @@ -77,7 +69,7 @@ describe('loadGeminiIgnorePatterns', () => { vi.restoreAllMocks(); }); - it('should load and parse patterns from .geminiignore, ignoring comments and empty lines', () => { + it('should load and parse patterns from .geminiignore, ignoring comments and empty lines', async () => { const ignoreContent = [ '# This is a comment', 'pattern1', @@ -90,14 +82,7 @@ describe('loadGeminiIgnorePatterns', () => { const ignoreFilePath = path.join(tempDir, '.geminiignore'); actualFs.writeFileSync(ignoreFilePath, ignoreContent); - mockedFsReadFileSync.mockImplementation((p: string, encoding: string) => { - if (p === ignoreFilePath && encoding === 'utf-8') return ignoreContent; - throw new Error( - `Mock fs.readFileSync: Unexpected call with path: ${p}, encoding: ${encoding}`, - ); - }); - - const patterns = loadGeminiIgnorePatterns(tempDir); + const patterns = await loadGeminiIgnorePatterns(tempDir); expect(patterns).toEqual([ 'pattern1', @@ -109,39 +94,19 @@ describe('loadGeminiIgnorePatterns', () => { expect(consoleLogSpy).toHaveBeenCalledWith( expect.stringContaining('Loaded 5 patterns from .geminiignore'), ); - expect(mockedFsReadFileSync).toHaveBeenCalledWith(ignoreFilePath, 'utf-8'); }); - it('should return an empty array and log info if .geminiignore is not found', () => { - const ignoreFilePath = path.join(tempDir, '.geminiignore'); - mockedFsReadFileSync.mockImplementation((p: string, encoding: string) => { - if (p === ignoreFilePath && encoding === 'utf-8') { - const error = new Error('File not found') as NodeJS.ErrnoException; - error.code = 'ENOENT'; - throw error; - } - throw new Error( - `Mock fs.readFileSync: Unexpected call with path: ${p}, encoding: ${encoding}`, - ); - }); - - const patterns = loadGeminiIgnorePatterns(tempDir); + it('should return an empty array and log info if .geminiignore is not found', async () => { + const patterns = await loadGeminiIgnorePatterns(tempDir); expect(patterns).toEqual([]); expect(consoleLogSpy).not.toHaveBeenCalled(); - expect(mockedFsReadFileSync).toHaveBeenCalledWith(ignoreFilePath, 'utf-8'); }); - it('should return an empty array if .geminiignore is empty', () => { + it('should return an empty array if .geminiignore is empty', async () => { const ignoreFilePath = path.join(tempDir, '.geminiignore'); actualFs.writeFileSync(ignoreFilePath, ''); - mockedFsReadFileSync.mockImplementation((p: string, encoding: string) => { - if (p === ignoreFilePath && encoding === 'utf-8') return ''; // Return string for empty file - throw new Error( - `Mock fs.readFileSync: Unexpected call with path: ${p}, encoding: ${encoding}`, - ); - }); - const patterns = loadGeminiIgnorePatterns(tempDir); + const patterns = await loadGeminiIgnorePatterns(tempDir); expect(patterns).toEqual([]); expect(consoleLogSpy).not.toHaveBeenCalledWith( expect.stringContaining('Loaded 0 patterns from .geminiignore'), @@ -149,10 +114,9 @@ describe('loadGeminiIgnorePatterns', () => { expect(consoleLogSpy).not.toHaveBeenCalledWith( expect.stringContaining('No .geminiignore file found'), ); - expect(mockedFsReadFileSync).toHaveBeenCalledWith(ignoreFilePath, 'utf-8'); }); - it('should return an empty array if .geminiignore contains only comments and empty lines', () => { + it('should return an empty array if .geminiignore contains only comments and empty lines', async () => { const ignoreContent = [ '# Comment 1', ' # Comment 2 with leading spaces', @@ -161,14 +125,8 @@ describe('loadGeminiIgnorePatterns', () => { ].join('\n'); const ignoreFilePath = path.join(tempDir, '.geminiignore'); actualFs.writeFileSync(ignoreFilePath, ignoreContent); - mockedFsReadFileSync.mockImplementation((p: string, encoding: string) => { - if (p === ignoreFilePath && encoding === 'utf-8') return ignoreContent; - throw new Error( - `Mock fs.readFileSync: Unexpected call with path: ${p}, encoding: ${encoding}`, - ); - }); - const patterns = loadGeminiIgnorePatterns(tempDir); + const patterns = await loadGeminiIgnorePatterns(tempDir); expect(patterns).toEqual([]); expect(consoleLogSpy).not.toHaveBeenCalledWith( expect.stringContaining('Loaded 0 patterns from .geminiignore'), @@ -176,48 +134,17 @@ describe('loadGeminiIgnorePatterns', () => { expect(consoleLogSpy).not.toHaveBeenCalledWith( expect.stringContaining('No .geminiignore file found'), ); - expect(mockedFsReadFileSync).toHaveBeenCalledWith(ignoreFilePath, 'utf-8'); - }); - - it('should handle read errors (other than ENOENT) and log a warning', () => { - const ignoreFilePath = path.join(tempDir, '.geminiignore'); - mockedFsReadFileSync.mockImplementation((p: string, encoding: string) => { - if (p === ignoreFilePath && encoding === 'utf-8') { - const error = new Error('Test read error') as NodeJS.ErrnoException; - error.code = 'EACCES'; - throw error; - } - throw new Error( - `Mock fs.readFileSync: Unexpected call with path: ${p}, encoding: ${encoding}`, - ); - }); - - const patterns = loadGeminiIgnorePatterns(tempDir); - expect(patterns).toEqual([]); - expect(consoleWarnSpy).toHaveBeenCalledWith( - expect.stringContaining( - `[WARN] Could not read .geminiignore file at ${ignoreFilePath}: Test read error`, - ), - ); - expect(mockedFsReadFileSync).toHaveBeenCalledWith(ignoreFilePath, 'utf-8'); }); - it('should correctly handle patterns with inline comments if not starting with #', () => { + it('should correctly handle patterns with inline comments if not starting with #', async () => { const ignoreContent = 'src/important # but not this part'; const ignoreFilePath = path.join(tempDir, '.geminiignore'); actualFs.writeFileSync(ignoreFilePath, ignoreContent); - mockedFsReadFileSync.mockImplementation((p: string, encoding: string) => { - if (p === ignoreFilePath && encoding === 'utf-8') return ignoreContent; - throw new Error( - `Mock fs.readFileSync: Unexpected call with path: ${p}, encoding: ${encoding}`, - ); - }); - const patterns = loadGeminiIgnorePatterns(tempDir); + const patterns = await loadGeminiIgnorePatterns(tempDir); expect(patterns).toEqual(['src/important # but not this part']); expect(consoleLogSpy).toHaveBeenCalledWith( expect.stringContaining('Loaded 1 patterns from .geminiignore'), ); - expect(mockedFsReadFileSync).toHaveBeenCalledWith(ignoreFilePath, 'utf-8'); }); }); diff --git a/packages/cli/src/utils/loadIgnorePatterns.ts b/packages/cli/src/utils/loadIgnorePatterns.ts index 1910942f..34efc8c8 100644 --- a/packages/cli/src/utils/loadIgnorePatterns.ts +++ b/packages/cli/src/utils/loadIgnorePatterns.ts @@ -4,46 +4,28 @@ * SPDX-License-Identifier: Apache-2.0 */ -import * as fs from 'node:fs'; import * as path from 'node:path'; +import { GitIgnoreParser } from '@gemini-cli/core'; const GEMINI_IGNORE_FILE_NAME = '.geminiignore'; /** * Loads and parses a .geminiignore file from the given workspace root. - * The .geminiignore file follows a format similar to .gitignore: - * - Each line specifies a glob pattern. - * - Lines are trimmed of leading and trailing whitespace. - * - Blank lines (after trimming) are ignored. - * - Lines starting with a pound sign (#) (after trimming) are treated as comments and ignored. - * - Patterns are case-sensitive and follow standard glob syntax. - * - If a # character appears elsewhere in a line (not at the start after trimming), - * it is considered part of the glob pattern. + * The .geminiignore file follows a format similar to .gitignore. * * @param workspaceRoot The absolute path to the workspace root where the .geminiignore file is expected. * @returns An array of glob patterns extracted from the .geminiignore file. Returns an empty array * if the file does not exist or contains no valid patterns. */ -export function loadGeminiIgnorePatterns(workspaceRoot: string): string[] { - const ignoreFilePath = path.join(workspaceRoot, GEMINI_IGNORE_FILE_NAME); - const patterns: string[] = []; +export async function loadGeminiIgnorePatterns( + workspaceRoot: string, +): Promise<string[]> { + const parser = new GitIgnoreParser(workspaceRoot); try { - const fileContent = fs.readFileSync(ignoreFilePath, 'utf-8'); - const lines = fileContent.split(/\r?\n/); - - for (const line of lines) { - const trimmedLine = line.trim(); - if (trimmedLine && !trimmedLine.startsWith('#')) { - patterns.push(trimmedLine); - } - } - if (patterns.length > 0) { - console.log( - `[INFO] Loaded ${patterns.length} patterns from .geminiignore`, - ); - } + await parser.loadPatterns(GEMINI_IGNORE_FILE_NAME); } catch (error: unknown) { + const ignoreFilePath = path.join(workspaceRoot, GEMINI_IGNORE_FILE_NAME); if ( error instanceof Error && 'code' in error && @@ -64,5 +46,11 @@ export function loadGeminiIgnorePatterns(workspaceRoot: string): string[] { ); } } - return patterns; + const loadedPatterns = parser.getPatterns(); + if (loadedPatterns.length > 0) { + console.log( + `[INFO] Loaded ${loadedPatterns.length} patterns from .geminiignore`, + ); + } + return loadedPatterns; } |
