|
|
@@ -0,0 +1,107 @@
|
|
|
+/**
|
|
|
+ * Regression: git older than 2.36 rejects `ls-files -s --recurse-submodules` (#1549).
|
|
|
+ *
|
|
|
+ * Kept in its own file rather than appended to extraction.test.ts: that suite
|
|
|
+ * loads every tree-sitter grammar in `beforeAll`, and running a git-scan case
|
|
|
+ * after it pushed the worker past its memory ceiling.
|
|
|
+ */
|
|
|
+
|
|
|
+import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
|
+import * as fs from 'fs';
|
|
|
+import * as path from 'path';
|
|
|
+import * as os from 'os';
|
|
|
+import { execFileSync } from 'child_process';
|
|
|
+import { scanDirectory } from '../src/extraction';
|
|
|
+
|
|
|
+function createTempDir(): string {
|
|
|
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-test-'));
|
|
|
+}
|
|
|
+
|
|
|
+// git < 2.36 rejects `ls-files -s --recurse-submodules` outright: the guard in
|
|
|
+// builtin/ls-files.c listed `show_stage` among the modes that die, and it was
|
|
|
+// only dropped in 2.36. The die is unconditional — it does not check whether the
|
|
|
+// repo has submodules — so on Ubuntu 22.04 (git 2.34.1), Debian 11 (2.30.2) and
|
|
|
+// older, every call threw, `getGitVisibleFiles` swallowed it, and the whole
|
|
|
+// git-visible path went with it: `includeIgnored`, gitlink recursion and the
|
|
|
+// `codegraph.json` `include` allowlist all silently stopped applying (#1549).
|
|
|
+//
|
|
|
+// A PATH shim reproduces that on any git version, which is what makes this
|
|
|
+// testable in CI at all.
|
|
|
+describe('Old git without `ls-files -s --recurse-submodules` support (#1549)', () => {
|
|
|
+ let tempDir: string;
|
|
|
+ let originalPath: string | undefined;
|
|
|
+
|
|
|
+ const runGit = (cwd: string, ...args: string[]) =>
|
|
|
+ execFileSync('git', args, { cwd, stdio: 'pipe' });
|
|
|
+
|
|
|
+ const makeRepo = (dir: string, base: string) => {
|
|
|
+ fs.mkdirSync(dir, { recursive: true });
|
|
|
+ runGit(dir, 'init', '-q');
|
|
|
+ runGit(dir, 'config', 'user.email', 'test@test.com');
|
|
|
+ runGit(dir, 'config', 'user.name', 'Test');
|
|
|
+ fs.writeFileSync(path.join(dir, `${base}.ts`), `export const ${base} = 1;`);
|
|
|
+ runGit(dir, 'add', '-A');
|
|
|
+ runGit(dir, 'commit', '-q', '-m', `${base} init`);
|
|
|
+ };
|
|
|
+
|
|
|
+ /** A `git` that dies exactly like < 2.36 when it sees -s with --recurse-submodules. */
|
|
|
+ const installOldGitShim = () => {
|
|
|
+ const shimDir = path.join(tempDir, '.shim');
|
|
|
+ fs.mkdirSync(shimDir, { recursive: true });
|
|
|
+ const realGit = execFileSync('which', ['git']).toString().trim();
|
|
|
+ const shim = path.join(shimDir, 'git');
|
|
|
+ fs.writeFileSync(
|
|
|
+ shim,
|
|
|
+ [
|
|
|
+ '#!/bin/sh',
|
|
|
+ 'for a in "$@"; do',
|
|
|
+ ' [ "$a" = "--recurse-submodules" ] && rs=1',
|
|
|
+ ' [ "$a" = "-s" ] && st=1',
|
|
|
+ 'done',
|
|
|
+ 'if [ -n "$rs" ] && [ -n "$st" ]; then',
|
|
|
+ ' echo "fatal: ls-files --recurse-submodules unsupported mode" >&2',
|
|
|
+ ' exit 128',
|
|
|
+ 'fi',
|
|
|
+ `exec ${JSON.stringify(realGit)} "$@"`,
|
|
|
+ ].join('\n'),
|
|
|
+ );
|
|
|
+ fs.chmodSync(shim, 0o755);
|
|
|
+ originalPath = process.env.PATH;
|
|
|
+ process.env.PATH = `${shimDir}:${originalPath ?? ''}`;
|
|
|
+ };
|
|
|
+
|
|
|
+ beforeEach(() => {
|
|
|
+ tempDir = createTempDir();
|
|
|
+ });
|
|
|
+
|
|
|
+ afterEach(() => {
|
|
|
+ if (originalPath !== undefined) process.env.PATH = originalPath;
|
|
|
+ originalPath = undefined;
|
|
|
+ });
|
|
|
+
|
|
|
+ it('still honours includeIgnored when `ls-files --recurse-submodules` is unsupported', () => {
|
|
|
+ const root = path.join(tempDir, 'root');
|
|
|
+ makeRepo(root, 'a');
|
|
|
+ // An embedded repo that .gitignore excludes but codegraph.json opts back in.
|
|
|
+ makeRepo(path.join(root, 'dir_b'), 'b');
|
|
|
+ fs.writeFileSync(path.join(root, '.gitignore'), 'dir_b/\n');
|
|
|
+ fs.writeFileSync(
|
|
|
+ path.join(root, 'codegraph.json'),
|
|
|
+ JSON.stringify({ includeIgnored: ['dir_b/'] }),
|
|
|
+ );
|
|
|
+ runGit(root, 'add', '-A');
|
|
|
+ runGit(root, 'commit', '-q', '-m', 'ignore dir_b');
|
|
|
+
|
|
|
+ // Baseline: the real git resolves both files.
|
|
|
+ const withRealGit = scanDirectory(root);
|
|
|
+ expect(withRealGit).toContain('a.ts');
|
|
|
+ expect(withRealGit).toContain(path.join('dir_b', 'b.ts'));
|
|
|
+
|
|
|
+ installOldGitShim();
|
|
|
+
|
|
|
+ // The opted-in file must survive the unsupported-mode failure, not vanish.
|
|
|
+ const withOldGit = scanDirectory(root);
|
|
|
+ expect(withOldGit).toContain('a.ts');
|
|
|
+ expect(withOldGit).toContain(path.join('dir_b', 'b.ts'));
|
|
|
+ });
|
|
|
+});
|