frieren

~ / frieren

a self-hosted git server in one binary — everyone reads, only the owner writes

frieren / web / lib / diff.ts 2.2 KB · raw

// Parses the `git show --stat --patch` text the backend returns into a
// structure the diff view can render with per-side line numbers.

export type DiffLine = {
  kind: "add" | "del" | "ctx" | "hunk";
  old: number | null;
  new: number | null;
  text: string;
};

export type DiffFile = {
  path: string;
  adds: number;
  dels: number;
  binary: boolean;
  lines: DiffLine[];
};

export type ParsedPatch = { stat: string; files: DiffFile[] };

export function parsePatch(patch: string): ParsedPatch {
  const idx = patch.indexOf("diff --git ");
  const stat = (idx === -1 ? patch : patch.slice(0, idx)).trim();
  const body = idx === -1 ? "" : patch.slice(idx);

  const files: DiffFile[] = [];
  let file: DiffFile | null = null;
  let oldN = 0;
  let newN = 0;

  for (const line of body.split("\n")) {
    if (line.startsWith("diff --git ")) {
      // `diff --git a/path b/path` — take the b/ side.
      const m = line.match(/ b\/(.*)$/);
      file = { path: m ? m[1] : line, adds: 0, dels: 0, binary: false, lines: [] };
      files.push(file);
      continue;
    }
    if (!file) continue;
    if (line.startsWith("Binary files ")) {
      file.binary = true;
      continue;
    }
    const hunk = line.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
    if (hunk) {
      oldN = parseInt(hunk[1], 10);
      newN = parseInt(hunk[2], 10);
      file.lines.push({ kind: "hunk", old: null, new: null, text: line });
      continue;
    }
    if (
      line.startsWith("index ") || line.startsWith("--- ") || line.startsWith("+++ ") ||
      line.startsWith("new file") || line.startsWith("deleted file") ||
      line.startsWith("old mode") || line.startsWith("new mode") ||
      line.startsWith("similarity") || line.startsWith("rename ") ||
      line.startsWith("\\ No newline")
    ) {
      continue;
    }
    if (line.startsWith("+")) {
      file.adds++;
      file.lines.push({ kind: "add", old: null, new: newN++, text: line.slice(1) });
    } else if (line.startsWith("-")) {
      file.dels++;
      file.lines.push({ kind: "del", old: oldN++, new: null, text: line.slice(1) });
    } else {
      file.lines.push({ kind: "ctx", old: oldN++, new: newN++, text: line.slice(1) });
    }
  }
  return { stat, files };
}