Fix .gitignore support and Windows path separator bug in scanner
Normalize paths to forward slashes in matchesGlob() and scanDirectory() so glob exclude patterns work on Windows. Add getGitIgnoredDirectories() using git ls-files to skip .gitignore'd directories during indexing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
4825661e02
commit
62a6cf38fe
+40
-2
@@ -8,6 +8,7 @@ import * as fs from 'fs';
|
||||
import * as fsp from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import * as crypto from 'crypto';
|
||||
import { execFileSync } from 'child_process';
|
||||
import {
|
||||
Language,
|
||||
FileRecord,
|
||||
@@ -20,7 +21,7 @@ import { extractFromSource } from './tree-sitter';
|
||||
import { detectLanguage, isLanguageSupported } from './grammars';
|
||||
import { logDebug } from '../errors';
|
||||
import { captureException } from '../sentry';
|
||||
import { validatePathWithinRoot } from '../utils';
|
||||
import { validatePathWithinRoot, normalizePath } from '../utils';
|
||||
|
||||
/**
|
||||
* Progress callback for indexing operations
|
||||
@@ -68,6 +69,9 @@ export function hashContent(content: string): string {
|
||||
* Check if a path matches any glob pattern (simplified)
|
||||
*/
|
||||
function matchesGlob(filePath: string, pattern: string): boolean {
|
||||
// Normalize to forward slashes so Windows backslash paths match glob patterns
|
||||
filePath = normalizePath(filePath);
|
||||
|
||||
// Convert glob to regex using placeholders to avoid conflicts
|
||||
let regexStr = pattern;
|
||||
|
||||
@@ -114,6 +118,31 @@ export function shouldIncludeFile(
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get directories ignored by .gitignore using git ls-files.
|
||||
* Returns a Set of normalized relative directory paths (forward slashes, no trailing slash).
|
||||
* Gracefully returns empty Set on any failure.
|
||||
*/
|
||||
function getGitIgnoredDirectories(rootDir: string): Set<string> {
|
||||
try {
|
||||
const output = execFileSync(
|
||||
'git',
|
||||
['ls-files', '-oi', '--exclude-standard', '--directory'],
|
||||
{ cwd: rootDir, encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] }
|
||||
);
|
||||
const dirs = new Set<string>();
|
||||
for (const line of output.split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed.endsWith('/')) {
|
||||
dirs.add(normalizePath(trimmed.slice(0, -1)));
|
||||
}
|
||||
}
|
||||
return dirs;
|
||||
} catch {
|
||||
return new Set<string>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker file name that indicates a directory (and all children) should be skipped
|
||||
*/
|
||||
@@ -130,6 +159,7 @@ export function scanDirectory(
|
||||
const files: string[] = [];
|
||||
let count = 0;
|
||||
const visitedRealPaths = new Set<string>(); // Symlink cycle detection
|
||||
const gitIgnoredDirs = getGitIgnoredDirectories(rootDir);
|
||||
|
||||
function walk(dir: string): void {
|
||||
// Symlink cycle detection: resolve real path and skip if already visited
|
||||
@@ -163,7 +193,7 @@ export function scanDirectory(
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
const relativePath = path.relative(rootDir, fullPath);
|
||||
const relativePath = normalizePath(path.relative(rootDir, fullPath));
|
||||
|
||||
// Follow symlinked directories, but skip symlinked files to non-project targets
|
||||
if (entry.isSymbolicLink()) {
|
||||
@@ -171,6 +201,10 @@ export function scanDirectory(
|
||||
const realTarget = fs.realpathSync(fullPath);
|
||||
const stat = fs.statSync(realTarget);
|
||||
if (stat.isDirectory()) {
|
||||
// Check gitignore first (fast O(1) lookup)
|
||||
if (gitIgnoredDirs.has(relativePath)) {
|
||||
continue;
|
||||
}
|
||||
// Check exclusion, then recurse (cycle detection handles the rest)
|
||||
const dirPattern = relativePath + '/';
|
||||
let excluded = false;
|
||||
@@ -199,6 +233,10 @@ export function scanDirectory(
|
||||
}
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
// Check gitignore first (fast O(1) lookup)
|
||||
if (gitIgnoredDirs.has(relativePath)) {
|
||||
continue;
|
||||
}
|
||||
// Check if directory should be excluded
|
||||
const dirPattern = relativePath + '/';
|
||||
let excluded = false;
|
||||
|
||||
Reference in New Issue
Block a user