diff --git a/__tests__/mpeg-ts-not-typescript.test.ts b/__tests__/mpeg-ts-not-typescript.test.ts index 2a6e12870..3d45924bd 100644 --- a/__tests__/mpeg-ts-not-typescript.test.ts +++ b/__tests__/mpeg-ts-not-typescript.test.ts @@ -12,6 +12,7 @@ import { describe, it, expect, afterEach } from 'vitest'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; +import { execFileSync } from 'child_process'; import { CodeGraph } from '../src'; import { scanDirectoryAsync, type ScanSkipStats } from '../src/extraction'; import { detectLanguage, isMpegTransportStream, MPEG_TS_SNIFF_BYTES } from '../src/extraction/grammars'; @@ -154,3 +155,50 @@ describe('MPEG-TS video named .ts is skipped, real TypeScript is indexed (#1910) } }); }); + +describe('a video .ts never stays pending (#1910)', () => { + const dirs: string[] = []; + afterEach(() => { for (const d of dirs.splice(0)) fs.rmSync(d, { recursive: true, force: true }); }); + const gitProject = (): string => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-mpegts-git-')); + dirs.push(dir); + const git = (...a: string[]) => execFileSync('git', a, { cwd: dir, stdio: 'pipe' }); + git('init', '-q'); git('config', 'user.email', 't@t'); git('config', 'user.name', 't'); + fs.writeFileSync(path.join(dir, 'app.ts'), 'export const a = 1;\n'); + git('add', '.'); git('commit', '-qm', 'init'); + return dir; + }; + + it('an untracked clip is not reported as added, before or after sync', async () => { + const dir = gitProject(); + const cg = await CodeGraph.init(dir, { index: true }); + try { + fs.writeFileSync(path.join(dir, 'clip.ts'), makeMpegTs(40)); + expect(cg.getChangedFiles()).toEqual({ added: [], modified: [], removed: [] }); + await cg.sync(); + expect(cg.getChangedFiles()).toEqual({ added: [], modified: [], removed: [] }); + expect(cg.getFiles().map((f) => f.path)).toEqual(['app.ts']); + } finally { + cg.close(); + } + }); + + it('a tracked TypeScript file that becomes a clip is removed, on the git path and on a scoped sync', async () => { + for (const scoped of [false, true]) { + const dir = gitProject(); + fs.writeFileSync(path.join(dir, 'clip.ts'), 'export const clip = 1;\n'); + const cg = await CodeGraph.init(dir, { index: true }); + try { + expect(cg.getNodesInFile('clip.ts').some((n) => n.name === 'clip')).toBe(true); + fs.writeFileSync(path.join(dir, 'clip.ts'), makeMpegTs(40)); + if (!scoped) expect(cg.getChangedFiles()).toEqual({ added: [], modified: [], removed: ['clip.ts'] }); + await (scoped ? cg.sync({ paths: ['clip.ts'] }) : cg.sync()); + expect(cg.getFiles().map((f) => f.path)).toEqual(['app.ts']); + expect(cg.getNodesInFile('clip.ts')).toEqual([]); + expect(cg.getChangedFiles()).toEqual({ added: [], modified: [], removed: [] }); + } finally { + cg.close(); + } + } + }); +}); diff --git a/src/extraction/index.ts b/src/extraction/index.ts index 403f426d4..57cd6378c 100644 --- a/src/extraction/index.ts +++ b/src/extraction/index.ts @@ -3156,7 +3156,10 @@ export class ExtractionOrchestrator { (p) => isSourceFile(p, overrides) && !scope.ignores(p) && - fs.existsSync(path.join(this.rootDir, p)) + fs.existsSync(path.join(this.rootDir, p)) && + // Same rule as the scan (#1910): a reported video clip is not a + // source file, so a tracked one is removed and a new one ignored. + !isMpegTsVideoFile(this.rootDir, p) ); trackedFiles = []; for (const p of unique) { @@ -3411,7 +3414,11 @@ export class ExtractionOrchestrator { for (const filePath of candidates) { const tracked = this.queries.getFileByPath(filePath); const fullPath = path.join(this.rootDir, filePath); - if (!isSourceFile(filePath, overrides) || scope.ignores(filePath) || !fs.existsSync(fullPath)) { + // A `.ts` that is an MPEG transport stream is not source (#1910): the + // scan never lists it, so git must not report it as pending either — + // an untracked clip would otherwise stay "added" after every sync. + if (!isSourceFile(filePath, overrides) || scope.ignores(filePath) || !fs.existsSync(fullPath) + || isMpegTsVideoFile(this.rootDir, filePath)) { if (tracked) removed.push(filePath); continue; }