mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
fix(mcp): ignore CR-only line ending diffs (#2839)
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
This commit is contained in:
parent
9372b17049
commit
905a1e191a
2 changed files with 72 additions and 18 deletions
|
|
@ -799,6 +799,21 @@ export function resolveWorktreeCwd(repoPath: string, launchCwd: string): string
|
|||
return repoPath;
|
||||
}
|
||||
|
||||
export function buildDetectChangesDiffArgs(scope: string, baseRef?: string): string[] | null {
|
||||
const args = ['diff', '--ignore-cr-at-eol'];
|
||||
switch (scope) {
|
||||
case 'staged':
|
||||
return [...args, '--staged', '-U0'];
|
||||
case 'all':
|
||||
return [...args, 'HEAD', '-U0'];
|
||||
case 'compare':
|
||||
return baseRef ? [...args, baseRef, '-U0'] : null;
|
||||
case 'unstaged':
|
||||
default:
|
||||
return [...args, '-U0'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of the path-derived suffix appended to a colliding repo id.
|
||||
* Exported so tests can pin the suffix shape without re-deriving the
|
||||
|
|
@ -4917,24 +4932,10 @@ export class LocalBackend {
|
|||
const scope = params.scope || 'unstaged';
|
||||
const { execFileSync } = await import('child_process');
|
||||
|
||||
// Build git diff args based on scope (using execFileSync to avoid shell injection)
|
||||
let diffArgs: string[];
|
||||
switch (scope) {
|
||||
case 'staged':
|
||||
diffArgs = ['diff', '--staged', '-U0'];
|
||||
break;
|
||||
case 'all':
|
||||
diffArgs = ['diff', 'HEAD', '-U0'];
|
||||
break;
|
||||
case 'compare':
|
||||
if (!params.base_ref) return { error: 'base_ref is required for "compare" scope' };
|
||||
diffArgs = ['diff', params.base_ref, '-U0'];
|
||||
break;
|
||||
case 'unstaged':
|
||||
default:
|
||||
diffArgs = ['diff', '-U0'];
|
||||
break;
|
||||
}
|
||||
// Ignore CR-only EOL differences, while preserving meaningful whitespace changes.
|
||||
// execFileSync receives an argv array, so refs never pass through a shell.
|
||||
const diffArgs = buildDetectChangesDiffArgs(scope, params.base_ref);
|
||||
if (!diffArgs) return { error: 'base_ref is required for "compare" scope' };
|
||||
|
||||
let diffOutput: string;
|
||||
try {
|
||||
|
|
|
|||
53
gitnexus/test/unit/detect-changes-eol.test.ts
Normal file
53
gitnexus/test/unit/detect-changes-eol.test.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
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';
|
||||
|
||||
describe('detect_changes EOL filtering', () => {
|
||||
it.each([
|
||||
['unstaged', undefined, ['diff', '--ignore-cr-at-eol', '-U0']],
|
||||
['staged', undefined, ['diff', '--ignore-cr-at-eol', '--staged', '-U0']],
|
||||
['all', undefined, ['diff', '--ignore-cr-at-eol', 'HEAD', '-U0']],
|
||||
['compare', 'main', ['diff', '--ignore-cr-at-eol', 'main', '-U0']],
|
||||
])('adds the EOL guard 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 {
|
||||
execFileSync('git', ['init', '-q'], { cwd: repoDir });
|
||||
execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: repoDir });
|
||||
execFileSync('git', ['config', 'user.name', 'Test'], { cwd: repoDir });
|
||||
writeFileSync(path.join(repoDir, 'sample.ts'), 'const first = 1;\r\nconst second = 2;\r\n');
|
||||
execFileSync('git', ['add', 'sample.ts'], { cwd: repoDir });
|
||||
execFileSync('git', ['commit', '-q', '-m', 'initial'], { cwd: repoDir });
|
||||
|
||||
writeFileSync(path.join(repoDir, 'sample.ts'), 'const first = 1;\nconst second = 2;\n');
|
||||
const diffArgs = buildDetectChangesDiffArgs('unstaged');
|
||||
if (!diffArgs) throw new Error('unstaged scope must produce git diff arguments');
|
||||
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 });
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue