Files
codegraph/__tests__/git-changed-untracked-dir.test.ts
T
Max HsuandGitHub cc9ce09256 fix(extraction): detect untracked files inside untracked directories (#1213) (#1215)
git status --porcelain collapses an entirely-untracked directory into a
single '?? dir/' entry. collectGitStatus only recurses into such dirs to
find embedded git repos, so source files in a plain untracked directory
were never surfaced to sync — 'codegraph sync' reported 'Already up to
date' and the watcher missed them too.

Add -uall so git lists individual untracked files. Nested untracked git
repos still collapse to '?? repo/' even with -uall (git never crosses a
repo boundary), so the embedded-repo recursion is unaffected.

Export getGitChangedFiles and add regression tests for both the plain
untracked-directory case and the embedded-repo recursion (no -uall
regression).

Root-cause analysis and fix suggested by the reporter in #1213.
2026-08-22 11:53:33 -05:00

69 lines
2.4 KiB
TypeScript

/**
* Regression test for #1213: `codegraph sync` silently skips untracked files
* that live inside an untracked directory.
*
* `git status --porcelain` collapses an entirely-untracked directory into a
* single `?? frontend/` entry. getGitChangedFiles must still surface the source
* files inside it (via `-uall`) rather than dropping the whole directory.
*/
import { describe, it, expect, afterEach } from 'vitest';
import { execFileSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { getGitChangedFiles } from '../src/extraction/index';
function git(cwd: string, args: string[]): void {
execFileSync('git', args, { cwd, stdio: 'pipe' });
}
describe('getGitChangedFiles — untracked directories (#1213)', () => {
const dirs: string[] = [];
function makeRepo(): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-1213-'));
dirs.push(dir);
git(dir, ['init']);
git(dir, ['config', 'user.email', 'test@example.com']);
git(dir, ['config', 'user.name', 'test']);
fs.writeFileSync(path.join(dir, 'root.js'), 'function foo() {}\n');
git(dir, ['add', 'root.js']);
git(dir, ['commit', '-m', 'init']);
return dir;
}
afterEach(() => {
while (dirs.length) {
fs.rmSync(dirs.pop()!, { recursive: true, force: true });
}
});
it('detects source files inside a fully-untracked directory', () => {
const dir = makeRepo();
fs.mkdirSync(path.join(dir, 'frontend'));
fs.writeFileSync(path.join(dir, 'frontend', 'app.js'), 'function bar() {}\n');
const changes = getGitChangedFiles(dir);
expect(changes).not.toBeNull();
expect(changes!.added).toContain('frontend/app.js');
});
it('still recurses into an untracked embedded git repo (no -uall regression)', () => {
// `-uall` must not break the embedded-repo path: git collapses a nested
// repo to `?? embedded/` regardless of `-uall`, so its files are only
// reachable through collectGitStatus's recursion.
const dir = makeRepo();
const embedded = path.join(dir, 'embedded');
fs.mkdirSync(embedded);
git(embedded, ['init']);
fs.writeFileSync(path.join(embedded, 'inner.js'), 'function baz() {}\n');
const changes = getGitChangedFiles(dir);
expect(changes).not.toBeNull();
expect(changes!.added).toContain('embedded/inner.js');
});
});