mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-19 00:03:33 +00:00
* fix(parse): retain metadata-only diff files * Address PR review feedback (#3251) Keep detect_changes honest on C-quoted, TAB-terminated, and ambiguous diff --git dests, and fail closed when a header cannot be parsed. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify parseDiffHunks dest-prefix helper (#3251) Drop the unused a/ prefix arm and the redundant empty-parse unparsed-header check. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(parse): ignore +++ inside hunks and accept mixed-quoted git headers Stop treating hunk-body lines as file headers, and parse independently C-quoted src/dest tokens on diff --git. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: ming <silverchris@foxmail.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
108 lines
4.1 KiB
TypeScript
108 lines
4.1 KiB
TypeScript
import { execFileSync } from 'node:child_process';
|
|
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { buildDetectChangesDiffArgs } from '../../src/mcp/local/local-backend.js';
|
|
import { parseDiffHunks } from '../../src/storage/git.js';
|
|
import { diffArgsFor } from '../helpers/detect-changes-diff-args.js';
|
|
import { commitAll, initGitRepo } from '../helpers/temp-git-repo.js';
|
|
|
|
/** Flags every scope carries, ahead of its own ref/staging arguments. */
|
|
const GUARD_FLAGS = [
|
|
'-c',
|
|
'core.quotePath=false',
|
|
'diff',
|
|
'--ignore-cr-at-eol',
|
|
'--no-ext-diff',
|
|
'--color=never',
|
|
'--src-prefix=a/',
|
|
'--dst-prefix=b/',
|
|
];
|
|
|
|
describe('detect_changes EOL filtering', () => {
|
|
it.each([
|
|
['unstaged', undefined, [...GUARD_FLAGS, '-U0']],
|
|
['staged', undefined, [...GUARD_FLAGS, '--staged', '-U0']],
|
|
['all', undefined, [...GUARD_FLAGS, 'HEAD', '-U0']],
|
|
['compare', 'main', [...GUARD_FLAGS, 'main', '-U0']],
|
|
])('adds the EOL and prefix guards for %s scope', (scope, baseRef, expected) => {
|
|
expect(buildDetectChangesDiffArgs(scope, baseRef)).toEqual(expected);
|
|
});
|
|
|
|
it('requires a base ref for compare scope', () => {
|
|
expect(buildDetectChangesDiffArgs('compare')).toBeNull();
|
|
});
|
|
|
|
it('suppresses CRLF-only changes but retains other whitespace changes', () => {
|
|
const repoDir = mkdtempSync(path.join(tmpdir(), 'gitnexus-detect-eol-'));
|
|
try {
|
|
initGitRepo(repoDir);
|
|
writeFileSync(path.join(repoDir, 'sample.ts'), 'const first = 1;\r\nconst second = 2;\r\n');
|
|
commitAll(repoDir, 'initial');
|
|
|
|
writeFileSync(path.join(repoDir, 'sample.ts'), 'const first = 1;\nconst second = 2;\n');
|
|
const diffArgs = diffArgsFor('unstaged');
|
|
expect(
|
|
execFileSync('git', diffArgs, {
|
|
cwd: repoDir,
|
|
encoding: 'utf8',
|
|
}),
|
|
).toBe('');
|
|
|
|
writeFileSync(path.join(repoDir, 'sample.ts'), 'const first = 1;\n const second = 2;\n');
|
|
expect(
|
|
execFileSync('git', diffArgs, {
|
|
cwd: repoDir,
|
|
encoding: 'utf8',
|
|
}),
|
|
).toContain('+ const second = 2;');
|
|
} finally {
|
|
rmSync(repoDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
/**
|
|
* #2915 — the user's own git config could turn the pre-commit gate into a
|
|
* silent all-clear.
|
|
*
|
|
* `parseDiffHunks` recognises a file by its `+++ b/` header, and git only emits
|
|
* that prefix by default: `diff.noprefix` emits `+++ sample.py` and
|
|
* `diff.mnemonicPrefix` emits `+++ w/sample.py`. Either one parses to ZERO
|
|
* files, which `detect_changes` reported as "No changes detected." with exit 0
|
|
* and no `partial`. The flags pin the prefixes the parser matches.
|
|
*/
|
|
describe('detect_changes diff prefix pinning', () => {
|
|
it.each([
|
|
['diff.noprefix', '+++ sample.py'],
|
|
['diff.mnemonicPrefix', '+++ w/sample.py'],
|
|
])('parses the diff even with %s configured', (configKey, hostileHeader) => {
|
|
const repoDir = mkdtempSync(path.join(tmpdir(), 'gitnexus-detect-prefix-'));
|
|
try {
|
|
initGitRepo(repoDir);
|
|
writeFileSync(path.join(repoDir, 'sample.py'), 'def hello():\n return 1\n');
|
|
commitAll(repoDir, 'initial');
|
|
execFileSync('git', ['config', configKey, 'true'], { cwd: repoDir });
|
|
writeFileSync(path.join(repoDir, 'sample.py'), 'def hello():\n return 2\n');
|
|
|
|
// The config really is hostile: without the prefix flags git relabels the
|
|
// headers and the whole diff parses to nothing.
|
|
const unguarded = execFileSync('git', ['diff', '--ignore-cr-at-eol', '-U0'], {
|
|
cwd: repoDir,
|
|
encoding: 'utf8',
|
|
});
|
|
expect(unguarded).toContain(hostileHeader);
|
|
expect(parseDiffHunks(unguarded)).toEqual([]);
|
|
|
|
// Source line 2 is the edit; the hunk header is git's own 1-based space.
|
|
expect(
|
|
parseDiffHunks(
|
|
execFileSync('git', diffArgsFor('unstaged'), { cwd: repoDir, encoding: 'utf8' }),
|
|
),
|
|
).toEqual([{ filePath: 'sample.py', hunks: [{ startLine: 2, endLine: 2 }] }]);
|
|
} finally {
|
|
rmSync(repoDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|