Skip to content

Commit f8e94e0

Browse files
committed
fix: resolve codebase indexing ignore pattern issues
- Add .gitignore fallback support when .rooignore does not exist - Fix .rooignore changes requiring VSCode restart by enhancing file watchers - Fix clearIndexData() ignoring .rooignore by recreating services - Remove duplicate ignore filtering in DirectoryScanner - Update RooIgnoreController to handle both .gitignore and .rooignore patterns - Add proper cleanup and service lifecycle management Fixes #5656
1 parent 195f4eb commit f8e94e0

File tree

4 files changed

+93
-35
lines changed

4 files changed

+93
-35
lines changed

src/core/ignore/RooIgnoreController.ts

Lines changed: 73 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@ export class RooIgnoreController {
1717
private ignoreInstance: Ignore
1818
private disposables: vscode.Disposable[] = []
1919
rooIgnoreContent: string | undefined
20+
private gitIgnoreContent: string | undefined
21+
private usingGitIgnoreFallback: boolean = false
2022

2123
constructor(cwd: string) {
2224
this.cwd = cwd
2325
this.ignoreInstance = ignore()
2426
this.rooIgnoreContent = undefined
25-
// Set up file watcher for .rooignore
27+
this.gitIgnoreContent = undefined
28+
// Set up file watcher for .rooignore and .gitignore
2629
this.setupFileWatcher()
2730
}
2831

@@ -31,52 +34,85 @@ export class RooIgnoreController {
3134
* Must be called after construction and before using the controller
3235
*/
3336
async initialize(): Promise<void> {
34-
await this.loadRooIgnore()
37+
await this.loadIgnorePatterns()
3538
}
3639

3740
/**
38-
* Set up the file watcher for .rooignore changes
41+
* Set up the file watcher for .rooignore and .gitignore changes
3942
*/
4043
private setupFileWatcher(): void {
4144
const rooignorePattern = new vscode.RelativePattern(this.cwd, ".rooignore")
42-
const fileWatcher = vscode.workspace.createFileSystemWatcher(rooignorePattern)
45+
const rooignoreWatcher = vscode.workspace.createFileSystemWatcher(rooignorePattern)
4346

44-
// Watch for changes and updates
47+
const gitignorePattern = new vscode.RelativePattern(this.cwd, ".gitignore")
48+
const gitignoreWatcher = vscode.workspace.createFileSystemWatcher(gitignorePattern)
49+
50+
// Watch for .rooignore changes and updates
51+
this.disposables.push(
52+
rooignoreWatcher.onDidChange(() => {
53+
this.loadIgnorePatterns()
54+
}),
55+
rooignoreWatcher.onDidCreate(() => {
56+
this.loadIgnorePatterns()
57+
}),
58+
rooignoreWatcher.onDidDelete(() => {
59+
this.loadIgnorePatterns()
60+
}),
61+
)
62+
63+
// Watch for .gitignore changes and updates
4564
this.disposables.push(
46-
fileWatcher.onDidChange(() => {
47-
this.loadRooIgnore()
65+
gitignoreWatcher.onDidChange(() => {
66+
this.loadIgnorePatterns()
4867
}),
49-
fileWatcher.onDidCreate(() => {
50-
this.loadRooIgnore()
68+
gitignoreWatcher.onDidCreate(() => {
69+
this.loadIgnorePatterns()
5170
}),
52-
fileWatcher.onDidDelete(() => {
53-
this.loadRooIgnore()
71+
gitignoreWatcher.onDidDelete(() => {
72+
this.loadIgnorePatterns()
5473
}),
5574
)
5675

57-
// Add fileWatcher itself to disposables
58-
this.disposables.push(fileWatcher)
76+
// Add fileWatchers themselves to disposables
77+
this.disposables.push(rooignoreWatcher, gitignoreWatcher)
5978
}
6079

6180
/**
62-
* Load custom patterns from .rooignore if it exists
81+
* Load ignore patterns from .rooignore and .gitignore files
82+
* .rooignore takes precedence, but .gitignore is used as fallback
6383
*/
64-
private async loadRooIgnore(): Promise<void> {
84+
private async loadIgnorePatterns(): Promise<void> {
6585
try {
6686
// Reset ignore instance to prevent duplicate patterns
6787
this.ignoreInstance = ignore()
68-
const ignorePath = path.join(this.cwd, ".rooignore")
69-
if (await fileExistsAtPath(ignorePath)) {
70-
const content = await fs.readFile(ignorePath, "utf8")
88+
this.usingGitIgnoreFallback = false
89+
90+
const rooIgnorePath = path.join(this.cwd, ".rooignore")
91+
const gitIgnorePath = path.join(this.cwd, ".gitignore")
92+
93+
// Check for .rooignore first
94+
if (await fileExistsAtPath(rooIgnorePath)) {
95+
const content = await fs.readFile(rooIgnorePath, "utf8")
7196
this.rooIgnoreContent = content
7297
this.ignoreInstance.add(content)
7398
this.ignoreInstance.add(".rooignore")
7499
} else {
75100
this.rooIgnoreContent = undefined
101+
102+
// Fallback to .gitignore if .rooignore doesn't exist
103+
if (await fileExistsAtPath(gitIgnorePath)) {
104+
const content = await fs.readFile(gitIgnorePath, "utf8")
105+
this.gitIgnoreContent = content
106+
this.ignoreInstance.add(content)
107+
this.ignoreInstance.add(".gitignore")
108+
this.usingGitIgnoreFallback = true
109+
} else {
110+
this.gitIgnoreContent = undefined
111+
}
76112
}
77113
} catch (error) {
78114
// Should never happen: reading file failed even though it exists
79-
console.error("Unexpected error loading .rooignore:", error)
115+
console.error("Unexpected error loading ignore patterns:", error)
80116
}
81117
}
82118

@@ -87,8 +123,8 @@ export class RooIgnoreController {
87123
* @returns true if file is accessible, false if ignored
88124
*/
89125
validateAccess(filePath: string): boolean {
90-
// Always allow access if .rooignore does not exist
91-
if (!this.rooIgnoreContent) {
126+
// Always allow access if no ignore patterns are loaded
127+
if (!this.rooIgnoreContent && !this.usingGitIgnoreFallback) {
92128
return true
93129
}
94130
try {
@@ -121,8 +157,8 @@ export class RooIgnoreController {
121157
* @returns path of file that is being accessed if it is being accessed, undefined if command is allowed
122158
*/
123159
validateCommand(command: string): string | undefined {
124-
// Always allow if no .rooignore exists
125-
if (!this.rooIgnoreContent) {
160+
// Always allow if no ignore patterns are loaded
161+
if (!this.rooIgnoreContent && !this.usingGitIgnoreFallback) {
126162
return undefined
127163
}
128164

@@ -200,14 +236,23 @@ export class RooIgnoreController {
200236
}
201237

202238
/**
203-
* Get formatted instructions about the .rooignore file for the LLM
204-
* @returns Formatted instructions or undefined if .rooignore doesn't exist
239+
* Get formatted instructions about the ignore file for the LLM
240+
* @returns Formatted instructions or undefined if no ignore patterns exist
205241
*/
206242
getInstructions(): string | undefined {
207-
if (!this.rooIgnoreContent) {
208-
return undefined
243+
if (this.rooIgnoreContent) {
244+
return `# .rooignore\n\n(The following is provided by a root-level .rooignore file where the user has specified files and directories that should not be accessed. When using list_files, you'll notice a ${LOCK_TEXT_SYMBOL} next to files that are blocked. Attempting to access the file's contents e.g. through read_file will result in an error.)\n\n${this.rooIgnoreContent}\n.rooignore`
245+
} else if (this.usingGitIgnoreFallback && this.gitIgnoreContent) {
246+
return `# .gitignore (fallback)\n\n(The following is provided by a root-level .gitignore file that is being used as fallback ignore patterns since no .rooignore file exists. When using list_files, you'll notice a ${LOCK_TEXT_SYMBOL} next to files that are blocked. Attempting to access the file's contents e.g. through read_file will result in an error.)\n\n${this.gitIgnoreContent}\n.gitignore`
209247
}
248+
return undefined
249+
}
210250

211-
return `# .rooignore\n\n(The following is provided by a root-level .rooignore file where the user has specified files and directories that should not be accessed. When using list_files, you'll notice a ${LOCK_TEXT_SYMBOL} next to files that are blocked. Attempting to access the file's contents e.g. through read_file will result in an error.)\n\n${this.rooIgnoreContent}\n.rooignore`
251+
/**
252+
* Check if the controller is using .gitignore as fallback
253+
* @returns true if using .gitignore patterns because .rooignore doesn't exist
254+
*/
255+
isUsingGitIgnoreFallback(): boolean {
256+
return this.usingGitIgnoreFallback
212257
}
213258
}

src/core/ignore/__tests__/RooIgnoreController.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ describe("RooIgnoreController", () => {
153153
await controller.initialize()
154154

155155
// Verify error was logged
156-
expect(consoleSpy).toHaveBeenCalledWith("Unexpected error loading .rooignore:", expect.any(Error))
156+
expect(consoleSpy).toHaveBeenCalledWith("Unexpected error loading ignore patterns:", expect.any(Error))
157157

158158
// Cleanup
159159
consoleSpy.mockRestore()

src/services/code-index/manager.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export class CodeIndexManager {
2727
private _orchestrator: CodeIndexOrchestrator | undefined
2828
private _searchService: CodeIndexSearchService | undefined
2929
private _cacheManager: CacheManager | undefined
30+
private _rooIgnoreController: RooIgnoreController | undefined
3031

3132
// Flag to prevent race conditions during error recovery
3233
private _isRecoveringFromError = false
@@ -250,12 +251,15 @@ export class CodeIndexManager {
250251
if (this._orchestrator) {
251252
this.stopWatcher()
252253
}
254+
if (this._rooIgnoreController) {
255+
this._rooIgnoreController.dispose()
256+
}
253257
this._stateManager.dispose()
254258
}
255259

256260
/**
257261
* Clears all index data by stopping the watcher, clearing the Qdrant collection,
258-
* and deleting the cache file.
262+
* and deleting the cache file. Also recreates services to ensure ignore patterns are reloaded.
259263
*/
260264
public async clearIndexData(): Promise<void> {
261265
if (!this.isFeatureEnabled) {
@@ -264,6 +268,10 @@ export class CodeIndexManager {
264268
this.assertInitialized()
265269
await this._orchestrator!.clearIndexData()
266270
await this._cacheManager!.clearCacheFile()
271+
272+
// Recreate services to ensure ignore patterns are properly reloaded
273+
// This fixes the issue where .rooignore is ignored after clearing index data
274+
await this._recreateServices()
267275
}
268276

269277
// --- Private Helpers ---
@@ -332,6 +340,9 @@ export class CodeIndexManager {
332340
const rooIgnoreController = new RooIgnoreController(workspacePath)
333341
await rooIgnoreController.initialize()
334342

343+
// Store reference to the RooIgnoreController for cleanup
344+
this._rooIgnoreController = rooIgnoreController
345+
335346
// (Re)Create shared service instances
336347
const { embedder, vectorStore, scanner, fileWatcher } = this._serviceFactory.createServices(
337348
this.context,

src/services/code-index/processors/scanner.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,23 +84,25 @@ export class DirectoryScanner implements IDirectoryScanner {
8484

8585
// Initialize RooIgnoreController if not provided
8686
const ignoreController = new RooIgnoreController(directoryPath)
87-
8887
await ignoreController.initialize()
8988

90-
// Filter paths using .rooignore
89+
// Filter paths using .rooignore/.gitignore patterns
9190
const allowedPaths = ignoreController.filterPaths(filePaths)
9291

93-
// Filter by supported extensions, ignore patterns, and excluded directories
92+
// Filter by supported extensions and excluded directories
93+
// Note: We removed the duplicate this.ignoreInstance.ignores() check here because:
94+
// 1. The RooIgnoreController already handles both .gitignore and .rooignore patterns
95+
// 2. The listFiles() function already applies .gitignore filtering via ripgrep
96+
// 3. This prevents double-filtering and ensures consistent ignore behavior
9497
const supportedPaths = allowedPaths.filter((filePath) => {
9598
const ext = path.extname(filePath).toLowerCase()
96-
const relativeFilePath = generateRelativeFilePath(filePath, scanWorkspace)
9799

98100
// Check if file is in an ignored directory using the shared helper
99101
if (isPathInIgnoredDirectory(filePath)) {
100102
return false
101103
}
102104

103-
return scannerExtensions.includes(ext) && !this.ignoreInstance.ignores(relativeFilePath)
105+
return scannerExtensions.includes(ext)
104106
})
105107

106108
// Initialize tracking variables

0 commit comments

Comments
 (0)