import type { CommitSummary } from "~/lib/types"; export type PatchKind = "diff" | "patch"; /** RFC 2822 date as git writes it. Author time zones are not kept, so dates are given in UTC. */ function mailDate(timestamp: number): string { return new Date(timestamp).toUTCString().replace("GMT", "+0000"); } function withTrailingNewline(text: string): string { return text === "" || text.endsWith("\n") ? text : `${text}\n`; } /** Splits a commit message into the subject line and the body after the first blank line, like git does. */ function splitMessage(message: string): { subject: string; body: string } { const lines = message.replace(/\r\n/g, "\n").split("\n"); const blank = lines.findIndex((line) => line.trim() === ""); const subjectLines = blank === -1 ? lines : lines.slice(0, blank); const body = blank === -1 ? "" : lines .slice(blank + 1) .join("\n") .trim(); return { subject: subjectLines.join(" ").trim(), body }; } /** One `git format-patch` mbox message per commit, numbered `[PATCH i/n]` when there is more than one. */ export function formatPatchSeries( entries: Array<{ commit: Pick; patch: string; }>, ): string { const total = entries.length; return entries .map(({ commit, patch }, index) => { const { subject, body } = splitMessage(commit.message); const prefix = total > 1 ? `[PATCH ${index + 1}/${total}]` : "[PATCH]"; const header = [ `From ${commit.sha} Mon Sep 17 00:00:00 2001`, `From: ${commit.authorName} <${commit.authorEmail}>`, `Date: ${mailDate(commit.authoredAt)}`, `Subject: ${prefix} ${subject}`, "", ]; const message = body ? `${body}\n` : ""; return `${header.join("\n")}\n${message}---\n${withTrailingNewline(patch)}-- \ngitfor\n`; }) .join("\n"); } export function patchResponse(kind: PatchKind, text: string, name: string): Response { return new Response(text, { headers: { "content-type": kind === "patch" ? "text/x-patch; charset=utf-8" : "text/x-diff; charset=utf-8", "content-disposition": `inline; filename="${name}.${kind}"`, "x-content-type-options": "nosniff", "cache-control": "private, max-age=60", }, }); }