Polish diff file actions

This commit is contained in:
Bohdan Triapitsyn
2026-06-14 16:17:30 +03:00
parent 7748a75eba
commit 1762c1a289
6 changed files with 370 additions and 253 deletions
+41 -3
View File
@@ -2561,11 +2561,49 @@ const HUNK_ACTION_FLAGS = {
discard: ['--reverse'],
};
const parsePatchPathToken = (line) => {
const value = String(line || '').replace(/^(?:-{3}|\+{3})\s+/, '');
if (!value || value === '/dev/null') {
return null;
}
if (value.startsWith('"')) {
let token = '"';
let escaped = false;
for (let index = 1; index < value.length; index += 1) {
const char = value[index];
token += char;
if (escaped) {
escaped = false;
} else if (char === '\\') {
escaped = true;
} else if (char === '"') {
break;
}
}
try {
return JSON.parse(token);
} catch {
return token.slice(1, token.endsWith('"') ? -1 : undefined);
}
}
return value.split('\t', 1)[0] || null;
};
const normalizePatchTargetPath = (value) => {
if (!value || value === '/dev/null') {
return null;
}
return value.replace(/^[ab]\//, '');
};
const extractPatchTargetPath = (patch) => {
const matches = [...patch.matchAll(/^(?:-{3}|\+{3})\s+(?:[ab]\/)?([^\s\t]+)/gm)];
const matches = [...patch.matchAll(/^(?:-{3}|\+{3})\s+.+$/gm)];
const realTargets = matches
.map((match) => match[1])
.filter((value) => value && value !== '/dev/null');
.map((match) => normalizePatchTargetPath(parsePatchPathToken(match[0])))
.filter(Boolean);
return realTargets[0] || null;
};
@@ -240,6 +240,25 @@ describe('applyHunk', () => {
'patch target path does not match'
);
});
it('accepts hunk patches for files with spaces in their path', async () => {
if (!canRunGit()) return;
const { tmpDir, git } = await createTempRepo();
const filePath = 'file name.txt';
await writeFile(tmpDir, filePath, ORIGINAL_FILE);
await git.add(filePath);
await git.commit('Initial');
await writeFile(tmpDir, filePath, EDITED_FILE);
const diff = await getDiff(tmpDir, { path: filePath });
const hunks = splitHunks(diff);
expect(hunks.length).toBe(2);
await applyHunk(tmpDir, filePath, { patch: hunks[0], action: 'stage' });
const staged = (await git.raw(['show', `:${filePath}`])).replace(/\r\n/g, '\n');
expect(staged).toBe(makeFile('TOP', 'line20'));
});
});
// ---------------------------------------------------------------------------