UNPKG

zephyr-agent

Version:
124 lines (123 loc) • 5.21 kB
import "node:module"; import { spawnSync } from "node:child_process"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { unknownOrigin } from "./git-ai.mjs"; import { comparisonFile, loadSourceRecord } from "./source.mjs"; function originAt(file, line) { return file?.attribution.find((range)=>range.start <= line && range.end >= line)?.origin ?? unknownOrigin; } function compareSourceRecords(directory, before, after) { const left = loadSourceRecord(directory, before); const right = loadSourceRecord(directory, after); return compareSourceContents(left, right, directory); } function compareSourceContents(left, right, directory) { const changes = []; const temporary = mkdtempSync(join(tmpdir(), 'zephyr-source-diff-')); try { for (const file of [ ...new Set([ ...Object.keys(left.files), ...Object.keys(right.files) ]) ].sort()){ if (left.files[file]?.hash === right.files[file]?.hash && left.files[file]?.mode === right.files[file]?.mode) continue; const oldFile = directory ? comparisonFile(directory, left, file) : left.files[file]; const newFile = directory ? comparisonFile(directory, right, file) : right.files[file]; const oldContent = Buffer.from(oldFile?.content ?? '', 'base64'); const newContent = Buffer.from(newFile?.content ?? '', 'base64'); writeFileSync(join(temporary, 'before'), oldContent, { mode: 384 }); writeFileSync(join(temporary, 'after'), newContent, { mode: 384 }); const result = spawnSync('git', [ 'diff', '--no-index', '--no-ext-diff', '--no-textconv', '--no-color', '--unified=3', '--', 'before', 'after' ], { cwd: temporary, encoding: 'utf8', timeout: 5000, maxBuffer: 67108864 }); if (result.error || 0 !== result.status && 1 !== result.status) throw result.error ?? new Error(result.stderr); const lines = []; let oldLine = 0; let newLine = 0; let inHunk = false; for (const line of result.stdout.split('\n')){ const hunk = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line); if (hunk) { oldLine = Number(hunk[1]); newLine = Number(hunk[2]); inHunk = true; continue; } if (inHunk) { if (line.startsWith('+')) lines.push({ operation: 'add', line: newLine++, text: line.slice(1), origin: originAt(newFile, newLine - 1) }); else if (line.startsWith('-')) lines.push({ operation: 'remove', line: oldLine++, text: line.slice(1), origin: originAt(oldFile, oldLine - 1) }); else if (line.startsWith(' ')) { oldLine++; newLine++; } } } const patchLines = result.stdout.split('\n'); const firstHunk = patchLines.findIndex((entry)=>entry.startsWith('@@ ')); changes.push({ file, status: oldFile ? newFile ? 'modified' : 'deleted' : 'added', binary: oldContent.includes(0) || newContent.includes(0), beforeMode: oldFile?.mode, afterMode: newFile?.mode, patch: patchLines.map((line, index)=>{ if (firstHunk >= 0 && index >= firstHunk) return line; if (line.startsWith('diff --git ') || line.startsWith('--- ') || line.startsWith('+++ ')) { if ('diff --git a/before b/after' === line) return `diff --git a/${file} b/${file}`; if ('--- a/before' === line) return `--- a/${file}`; if ('+++ b/after' === line) return `+++ b/${file}`; } if ('Binary files a/before and b/after differ' === line) return `Binary files a/${file} and b/${file} differ`; return line; }).join('\n'), lines }); } } finally{ rmSync(temporary, { recursive: true, force: true }); } return { schemaVersion: 1, before: left.id, after: right.id, beforeFingerprint: left.fingerprint, afterFingerprint: right.fingerprint, sameCommit: left.baseCommit === right.baseCommit, changes }; } export { compareSourceContents, compareSourceRecords }; //# sourceMappingURL=compare.mjs.map