mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
fix: always exclude .git directories from codebase indexing
- Added explicit .git exclusion in ripgrep arguments for both recursive and non-recursive modes - This prevents indexing of .git folder contents which improves performance - Added comprehensive tests to verify .git exclusion in various scenarios Fixes #9048
This commit is contained in:
parent
6965e5c791
commit
a59ca7b3c0
2 changed files with 232 additions and 1 deletions
213
src/services/glob/__tests__/list-files-git-exclusion.spec.ts
Normal file
213
src/services/glob/__tests__/list-files-git-exclusion.spec.ts
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
// npx vitest src/services/glob/__tests__/list-files-git-exclusion.spec.ts
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
|
||||
import * as childProcess from "child_process"
|
||||
import * as path from "path"
|
||||
import * as os from "os"
|
||||
import * as fs from "fs"
|
||||
import { listFiles } from "../list-files"
|
||||
|
||||
// Mock child_process module
|
||||
vi.mock("child_process", () => ({
|
||||
spawn: vi.fn(),
|
||||
}))
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("../../ripgrep", () => ({
|
||||
getBinPath: vi.fn(async () => "/usr/bin/rg"),
|
||||
}))
|
||||
|
||||
vi.mock("vscode", () => ({
|
||||
env: {
|
||||
appRoot: "/test/app/root",
|
||||
},
|
||||
workspace: {
|
||||
getConfiguration: vi.fn(() => ({
|
||||
get: vi.fn(() => undefined),
|
||||
})),
|
||||
},
|
||||
}))
|
||||
|
||||
describe("list-files .git exclusion", () => {
|
||||
let tempDir: string
|
||||
let originalCwd: string
|
||||
|
||||
beforeEach(async () => {
|
||||
// Create a temporary directory for testing
|
||||
tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "roo-git-exclusion-test-"))
|
||||
originalCwd = process.cwd()
|
||||
|
||||
// Mock fs.promises.access to simulate files exist
|
||||
vi.spyOn(fs.promises, "access").mockResolvedValue(undefined)
|
||||
vi.spyOn(fs.promises, "readdir").mockImplementation(async () => [])
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean up temporary directory
|
||||
await fs.promises.rm(tempDir, { recursive: true, force: true })
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it("should always exclude .git directories in recursive mode", async () => {
|
||||
// Mock ripgrep spawn
|
||||
const mockSpawn = vi.mocked(childProcess.spawn)
|
||||
mockSpawn.mockImplementation((command: string, args: readonly string[]) => {
|
||||
const mockProcess = {
|
||||
stdout: {
|
||||
on: (event: string, callback: (data: any) => void) => {
|
||||
if (event === "data") {
|
||||
// Simulate ripgrep output
|
||||
setTimeout(() => callback(`${path.join(tempDir, "src", "index.ts")}\n`), 10)
|
||||
}
|
||||
},
|
||||
},
|
||||
stderr: {
|
||||
on: vi.fn(),
|
||||
},
|
||||
on: (event: string, callback: (code: number | null) => void) => {
|
||||
if (event === "close") {
|
||||
setTimeout(() => callback(0), 20)
|
||||
}
|
||||
},
|
||||
kill: vi.fn(),
|
||||
} as any
|
||||
return mockProcess
|
||||
})
|
||||
|
||||
// Call listFiles in recursive mode
|
||||
const [files, limitReached] = await listFiles(tempDir, true, 100)
|
||||
|
||||
// Verify ripgrep was called with the .git exclusion pattern
|
||||
expect(mockSpawn).toHaveBeenCalled()
|
||||
const [rgPath, args] = mockSpawn.mock.calls[0]
|
||||
|
||||
// Check that the arguments include the .git exclusion pattern
|
||||
expect(args).toContain("-g")
|
||||
expect(args).toContain("!**/.git/**")
|
||||
})
|
||||
|
||||
it("should exclude .git directories even when explicitly targeting a hidden directory", async () => {
|
||||
// Create a hidden directory path
|
||||
const hiddenDirPath = path.join(tempDir, ".hidden-dir")
|
||||
|
||||
// Mock ripgrep spawn
|
||||
const mockSpawn = vi.mocked(childProcess.spawn)
|
||||
mockSpawn.mockImplementation((command: string, args: readonly string[]) => {
|
||||
const mockProcess = {
|
||||
stdout: {
|
||||
on: (event: string, callback: (data: any) => void) => {
|
||||
if (event === "data") {
|
||||
// Simulate ripgrep output
|
||||
setTimeout(() => callback(`${path.join(hiddenDirPath, "file.ts")}\n`), 10)
|
||||
}
|
||||
},
|
||||
},
|
||||
stderr: {
|
||||
on: vi.fn(),
|
||||
},
|
||||
on: (event: string, callback: (code: number | null) => void) => {
|
||||
if (event === "close") {
|
||||
setTimeout(() => callback(0), 20)
|
||||
}
|
||||
},
|
||||
kill: vi.fn(),
|
||||
} as any
|
||||
return mockProcess
|
||||
})
|
||||
|
||||
// Call listFiles targeting a hidden directory
|
||||
const [files, limitReached] = await listFiles(hiddenDirPath, true, 100)
|
||||
|
||||
// Verify ripgrep was called with the .git exclusion pattern
|
||||
expect(mockSpawn).toHaveBeenCalled()
|
||||
const [rgPath, args] = mockSpawn.mock.calls[0]
|
||||
|
||||
// Even when targeting a hidden directory, .git should still be excluded
|
||||
expect(args).toContain("-g")
|
||||
expect(args).toContain("!**/.git/**")
|
||||
|
||||
// But the command should have the special flags for hidden directories
|
||||
expect(args).toContain("--no-ignore-vcs")
|
||||
expect(args).toContain("--no-ignore")
|
||||
})
|
||||
|
||||
it("should exclude .git directories in non-recursive mode", async () => {
|
||||
// Mock ripgrep spawn
|
||||
const mockSpawn = vi.mocked(childProcess.spawn)
|
||||
mockSpawn.mockImplementation((command: string, args: readonly string[]) => {
|
||||
const mockProcess = {
|
||||
stdout: {
|
||||
on: (event: string, callback: (data: any) => void) => {
|
||||
if (event === "data") {
|
||||
// Simulate ripgrep output for non-recursive
|
||||
setTimeout(() => callback(`${path.join(tempDir, "file.ts")}\n`), 10)
|
||||
}
|
||||
},
|
||||
},
|
||||
stderr: {
|
||||
on: vi.fn(),
|
||||
},
|
||||
on: (event: string, callback: (code: number | null) => void) => {
|
||||
if (event === "close") {
|
||||
setTimeout(() => callback(0), 20)
|
||||
}
|
||||
},
|
||||
kill: vi.fn(),
|
||||
} as any
|
||||
return mockProcess
|
||||
})
|
||||
|
||||
// Call listFiles in non-recursive mode
|
||||
const [files, limitReached] = await listFiles(tempDir, false, 100)
|
||||
|
||||
// Verify ripgrep was called with the .git exclusion patterns
|
||||
expect(mockSpawn).toHaveBeenCalled()
|
||||
const [rgPath, args] = mockSpawn.mock.calls[0]
|
||||
|
||||
// Check that the arguments include the .git exclusion patterns for non-recursive mode
|
||||
expect(args).toContain("-g")
|
||||
expect(args).toContain("!.git")
|
||||
expect(args).toContain("!.git/**")
|
||||
})
|
||||
|
||||
it("should exclude .git even when it's the target directory", async () => {
|
||||
// Create a .git directory path
|
||||
const gitDirPath = path.join(tempDir, ".git")
|
||||
|
||||
// Mock ripgrep spawn
|
||||
const mockSpawn = vi.mocked(childProcess.spawn)
|
||||
mockSpawn.mockImplementation((command: string, args: readonly string[]) => {
|
||||
const mockProcess = {
|
||||
stdout: {
|
||||
on: (event: string, callback: (data: any) => void) => {
|
||||
if (event === "data") {
|
||||
// Simulate empty output (no files found)
|
||||
setTimeout(() => callback(""), 10)
|
||||
}
|
||||
},
|
||||
},
|
||||
stderr: {
|
||||
on: vi.fn(),
|
||||
},
|
||||
on: (event: string, callback: (code: number | null) => void) => {
|
||||
if (event === "close") {
|
||||
setTimeout(() => callback(0), 20)
|
||||
}
|
||||
},
|
||||
kill: vi.fn(),
|
||||
} as any
|
||||
return mockProcess
|
||||
})
|
||||
|
||||
// Call listFiles targeting .git directory
|
||||
const [files, limitReached] = await listFiles(gitDirPath, true, 100)
|
||||
|
||||
// Verify ripgrep was called with the .git exclusion pattern
|
||||
expect(mockSpawn).toHaveBeenCalled()
|
||||
const [rgPath, args] = mockSpawn.mock.calls[0]
|
||||
|
||||
// .git should still be excluded even when it's the target
|
||||
expect(args).toContain("-g")
|
||||
expect(args).toContain("!**/.git/**")
|
||||
})
|
||||
})
|
||||
|
|
@ -260,14 +260,23 @@ function buildRecursiveArgs(dirPath: string): string[] {
|
|||
args.push("-g", "**/*")
|
||||
}
|
||||
|
||||
// CRITICAL: Always exclude .git directories for performance reasons
|
||||
// This must come before other exclusions to ensure it's always applied
|
||||
args.push("-g", "!**/.git/**")
|
||||
|
||||
// Apply directory exclusions for recursive searches
|
||||
for (const dir of DIRS_TO_IGNORE) {
|
||||
// Skip .git since we already handled it above
|
||||
if (dir === ".git") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Special handling for hidden directories pattern
|
||||
if (dir === ".*") {
|
||||
// If we're explicitly targeting a hidden directory, don't exclude hidden files/dirs
|
||||
// This allows the target hidden directory and all its contents to be listed
|
||||
if (!isTargetingHiddenDir) {
|
||||
// Not targeting hidden dir: exclude all hidden directories
|
||||
// Not targeting hidden dir: exclude all hidden directories (except .git which is already excluded)
|
||||
args.push("-g", `!**/.*/**`)
|
||||
}
|
||||
// If targeting hidden dir: don't add any exclusion for hidden directories
|
||||
|
|
@ -305,8 +314,17 @@ function buildNonRecursiveArgs(): string[] {
|
|||
// Respect .gitignore in non-recursive mode too
|
||||
// (ripgrep respects .gitignore by default)
|
||||
|
||||
// CRITICAL: Always exclude .git directories for performance reasons
|
||||
args.push("-g", "!.git")
|
||||
args.push("-g", "!.git/**")
|
||||
|
||||
// Apply directory exclusions for non-recursive searches
|
||||
for (const dir of DIRS_TO_IGNORE) {
|
||||
// Skip .git since we already handled it above
|
||||
if (dir === ".git") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (dir === ".*") {
|
||||
// For hidden directories in non-recursive mode, we want to show the directories
|
||||
// themselves but not their contents. Since we're using --maxdepth 1, this
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue