summaryrefslogtreecommitdiff
path: root/packages/cli/src
diff options
context:
space:
mode:
authorAnas H. Sulaiman <[email protected]>2025-06-13 17:17:08 -0400
committerGitHub <[email protected]>2025-06-13 17:17:08 -0400
commitbb67d317394ba4be7b55bdc7175c5d432a40ae53 (patch)
tree5ce691bbb871d3aa39bd79e93f8111fa72104ad7 /packages/cli/src
parent54f0d9d0e5d8c21ed4c0d5b81ea8beb5908ebd4d (diff)
reuse `GitIgnoreParser` for loading `.geminiignore` (#1025)
Diffstat (limited to 'packages/cli/src')
-rw-r--r--packages/cli/src/gemini.tsx2
-rw-r--r--packages/cli/src/utils/loadIgnorePatterns.test.ts93
-rw-r--r--packages/cli/src/utils/loadIgnorePatterns.ts42
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;
}