mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-11-14 21:13:11 +08:00
codegraph applies .gitignore rules itself, through the ignore package: on the filesystem walk for every .gitignore, and in git repositories on top of git ls-files for its built-in defaults merged with the root .gitignore and git's exclude files. codegraph.json's include, exclude and includeIgnored patterns go through it too. ignore 7.0.5 read several forms differently from git: a backslash escape (\d was a regex digit), POSIX classes and a ] or ! first in a bracket, and a run of three asterisks. A lone ! line matched every path, so it re-included everything the rules and the built-in defaults leave out, node_modules included. 2,031 of 200,000 random patterns also threw at match time, which readGitignorePatterns has to guard against (issue 682). ignore 7.0.14, with the range raised to ^7.0.14, reads all of these as git does, and none of the 200,000 patterns throws. Its code diff from 7.0.5 was read in full: pattern translation only, with no I/O, no dependencies and no install scripts. The lockfile entry matches the npm registry's own metadata on registry.npmjs.org. The package has no provenance, like every release its maintainer has published. Validation: - New __tests__/gitignore-pattern-parity.test.ts checks scanDirectory against git's own answer for these patterns, on the filesystem walk and in a git repository: its 4 tests fail with 7.0.5 and pass with 7.0.14. - scanDirectory on react, astro and django, in git mode and in walk mode on a copy without .git, returns the same 4,631, 4,946 and 3,027 files with either version. - On a fixture of the changed forms, 7.0.14 leaves out exactly what git leaves out in both modes, node_modules aside, which codegraph's defaults always exclude. With a lone ! line, 7.0.5 kept 16 of the 18 files on the walk, node_modules among them, where git keeps 6. Without that line it differed from git on 8 files on the walk and on 2 in git mode. - The gitignore and scope test files (extraction, git-ignored-dirs-scope, the include, exclude and includeIgnored configs, src-build-folders and others): 853 passed. Raised by PR 2885 (@johnhldiver-beep). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
2.9 KiB
TypeScript
69 lines
2.9 KiB
TypeScript
/**
|
|
* codegraph applies .gitignore rules itself in two places: on the filesystem
|
|
* walk (a project without git), for every .gitignore it meets, and in a git
|
|
* project on top of `git ls-files`, for its built-in defaults merged with the
|
|
* root .gitignore. Both go through the `ignore` package, which before 7.0.14
|
|
* read several pattern forms differently from git:
|
|
* - a backslash escape: `\d` was a regex digit, not a literal `d`;
|
|
* - a POSIX class such as `[[:digit:]]`, and a `]` or `!` first in a bracket;
|
|
* - a run of three asterisks, which git reads as a globstar;
|
|
* - a lone `!` line, which matched every path, so it re-included everything
|
|
* the .gitignore and codegraph's own defaults (node_modules) leave out.
|
|
* Each fixture file is kept or dropped the way git decides, except
|
|
* node_modules, which codegraph's defaults always leave out.
|
|
*/
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
import { execFileSync } from 'child_process';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import { scanDirectory } from '../src/extraction';
|
|
|
|
const roots: string[] = [];
|
|
|
|
afterEach(() => {
|
|
for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });
|
|
});
|
|
|
|
function project(mode: 'walk' | 'git', gitignore: string, files: string[]): string {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), `cg-gitignore-parity-${mode}-`));
|
|
roots.push(root);
|
|
for (const file of files) {
|
|
fs.mkdirSync(path.dirname(path.join(root, file)), { recursive: true });
|
|
fs.writeFileSync(path.join(root, file), 'export const value = 1;\n');
|
|
}
|
|
fs.writeFileSync(path.join(root, '.gitignore'), gitignore);
|
|
if (mode === 'git') execFileSync('git', ['init', '-q'], { cwd: root, stdio: 'ignore' });
|
|
return root;
|
|
}
|
|
|
|
function scanned(root: string): string[] {
|
|
return scanDirectory(root).map((f) => f.replace(/\\/g, '/')).sort();
|
|
}
|
|
|
|
describe.each(['walk', 'git'] as const)('.gitignore patterns follow git (%s)', (mode) => {
|
|
it('reads escapes, POSIX classes, brackets, *** and trailing spaces as git does', () => {
|
|
const root = project(
|
|
mode,
|
|
[
|
|
'\\d.ts', // a literal "d"
|
|
'[[:digit:]]x.ts', // a POSIX class
|
|
'[!a]neg.ts', // a negated bracket
|
|
'[]]bracket.ts', // "]" first in a bracket is a member
|
|
'stars/***/z.ts', // three asterisks are a globstar
|
|
'trail.ts ', // trailing spaces are trimmed
|
|
].join('\n') + '\n',
|
|
[
|
|
'1.ts', 'd.ts', '1x.ts', 'ax.ts', 'aneg.ts', 'bneg.ts', ']bracket.ts',
|
|
'stars/z.ts', 'stars/b/z.ts', 'stars/b/c/z.ts', 'trail.ts', 'other.ts',
|
|
],
|
|
);
|
|
expect(scanned(root)).toEqual(['1.ts', 'aneg.ts', 'ax.ts', 'other.ts']);
|
|
});
|
|
|
|
it('treats a lone "!" line as no pattern, so ignored files and node_modules stay out', () => {
|
|
const root = project(mode, 'skipped.ts\n!\n', ['kept.ts', 'skipped.ts', 'node_modules/pkg/index.ts']);
|
|
expect(scanned(root)).toEqual(['kept.ts']);
|
|
});
|
|
});
|