Skip to content

Commit 775e0f2

Browse files
committed
fix(glob): keep gitignore matching case-sensitive and POSIX-normalized
Routing the non-negated path through the ignore package introduced two parity gaps versus fast-glob's native ignore matching: - The ignore package defaults to case-insensitive matching, while fast-glob (caseSensitiveMatch defaults to true) and git match case-sensitively. Build the matcher with ignorecase derived from caseSensitiveMatch so a `dist/` entry no longer also ignores a differently-cased `Dist/` sibling. - path.relative yields backslash-separated paths on Windows, which never match the forward-slash-anchored patterns. Normalize the relative path with normalizePath before ig.ignores(), matching how the patterns are anchored. Add a case-sensitivity regression test (dist/ vs Dist/).
1 parent 7f313b3 commit 775e0f2

2 files changed

Lines changed: 36 additions & 2 deletions

File tree

src/utils/glob.mts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { parse as yamlParse } from 'yaml'
88
import { isDirSync, safeReadFile } from '@socketsecurity/registry/lib/fs'
99
import { defaultIgnore } from '@socketsecurity/registry/lib/globs'
1010
import { readPackageJson } from '@socketsecurity/registry/lib/packages'
11+
import { normalizePath } from '@socketsecurity/registry/lib/path'
1112
import { transform } from '@socketsecurity/registry/lib/streams'
1213
import { isNonEmptyString } from '@socketsecurity/registry/lib/strings'
1314

@@ -308,7 +309,13 @@ export async function globWithGitIgnore(
308309
// The negated-pattern path already worked this way; routing both cases through
309310
// it removes the asymmetry that left the common, non-negated case crashing on
310311
// large repos.
311-
const ig = ignore().add([...ignores])
312+
// Match fast-glob's case sensitivity (its `caseSensitiveMatch` defaults to
313+
// true) so routing the non-negated path through the `ignore` package does not
314+
// silently start matching case-insensitively, which is the `ignore` package's
315+
// own default.
316+
const ig = ignore({
317+
ignorecase: additionalOptions.caseSensitiveMatch === false,
318+
}).add([...ignores])
312319

313320
const globOptions = {
314321
__proto__: null,
@@ -338,7 +345,12 @@ export async function globWithGitIgnore(
338345
for await (const p of stream) {
339346
// Note: the input files must be INSIDE the cwd. If you get strange looking
340347
// relative path errors here, most likely your path is outside the given cwd.
341-
const relPath = globOptions.absolute ? path.relative(cwd, p) : p
348+
// Normalize to POSIX separators: the `ignore` patterns are forward-slash
349+
// anchored (see ignoreFileLinesToGlobPatterns), so a Windows backslash path
350+
// from path.relative would never match them.
351+
const relPath = normalizePath(
352+
globOptions.absolute ? path.relative(cwd, p) : p,
353+
)
342354
if (ig.ignores(relPath)) {
343355
continue
344356
}

src/utils/glob.test.mts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,28 @@ describe('glob utilities', () => {
210210
])
211211
})
212212

213+
it('matches gitignore entries case-sensitively, like fast-glob', async () => {
214+
// The `ignore` package defaults to case-insensitive matching, but
215+
// fast-glob (caseSensitiveMatch defaults to true) and git treat the
216+
// ignore set case-sensitively. A `dist/` entry must ignore `dist/` but
217+
// leave a differently-cased `Dist/` sibling alone.
218+
mockTestFs({
219+
[`${mockFixturePath}/.gitignore`]: 'dist/\n',
220+
[`${mockFixturePath}/package.json`]: '{}',
221+
[`${mockFixturePath}/dist/a.json`]: '{}',
222+
[`${mockFixturePath}/Dist/b.json`]: '{}',
223+
})
224+
225+
const results = await globWithGitIgnore(['**/*.json'], {
226+
cwd: mockFixturePath,
227+
})
228+
229+
expect(results.map(normalizePath).sort()).toEqual([
230+
`${mockFixturePath}/Dist/b.json`,
231+
`${mockFixturePath}/package.json`,
232+
])
233+
})
234+
213235
it('keeps additionalIgnores anchored even when a gitignore negation forces the streaming path', async () => {
214236
// A bare `tests` pattern means "the entry `tests` at the scan root".
215237
// The streaming path uses the `ignore` package for gitignore-translated

0 commit comments

Comments
 (0)