SPB Git

Repository Public

patch.ts 2.3 KB 59 lines typescript

1/**2 * Reconstruct the ORIGINAL text of a file from its current text and a unified diff3 * (`git diff` output for that single file), by reverse-applying every hunk.4 * Returns null when the diff cannot be applied cleanly (caller falls back to raw diff text).5 */6export function reverseApplyUnifiedDiff(modified: string, diff: string): string | null {7  const lines = diff.split('\n');8  interface Hunk { newStart: number; newCount: number; oldLines: string[]; newLines: string[] }9  const hunks: Hunk[] = [];10  let cur: Hunk | null = null;11  let oldNoEol = false;12  let newNoEol = false;13  let last: '-' | '+' | ' ' | null = null;14  for (const line of lines) {15    const m = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/.exec(line);16    if (m) {17      cur = { newStart: Number(m[1]), newCount: m[2] === undefined ? 1 : Number(m[2]), oldLines: [], newLines: [] };18      hunks.push(cur);19      last = null;20      continue;21    }22    if (!cur) continue;23    if (line.startsWith('\\')) {24      if (last === '-') oldNoEol = true;25      else if (last === '+') newNoEol = true;26      else oldNoEol = newNoEol = true;27      continue;28    }29    const tag = line[0];30    if (tag === ' ') {31      cur.oldLines.push(line.slice(1));32      cur.newLines.push(line.slice(1));33      last = ' ';34    } else if (tag === '-') {35      cur.oldLines.push(line.slice(1));36      last = '-';37    } else if (tag === '+') {38      cur.newLines.push(line.slice(1));39      last = '+';40    } else if (line === '' ) {41      continue; // trailing split artefact42    } else {43      cur = null; // next file header or garbage44    }45  }46  if (hunks.length === 0) return diff.trim() === '' ? modified : null;47  const endsWithEol = modified.endsWith('\n');48  const body = modified === '' ? [] : (endsWithEol ? modified.slice(0, -1) : modified).split('\n');49  for (const h of [...hunks].reverse()) {50    const idx = h.newCount === 0 ? h.newStart : h.newStart - 1;51    const present = body.slice(idx, idx + h.newLines.length);52    if (present.length !== h.newLines.length || present.some((l, i) => l !== h.newLines[i])) return null;53    body.splice(idx, h.newLines.length, ...h.oldLines);54  }55  if (body.length === 0) return '';56  const origEol = oldNoEol ? false : newNoEol ? true : endsWithEol;57  return body.join('\n') + (origEol ? '\n' : '');58}59
Files main
Menu

Theme