64 lines · 2.3 KB
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<CommitSummary, "sha" | "message" | "authorName" | "authorEmail" | "authoredAt">;
    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]";