This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / api / rawdiff.ts
4.4 kB 132 lines
1import { diffFileName, type DiffFile, type NiceDiff, type TextFragment } from "./diff"; 2import { hexFromBytes } from "./repo"; 3 4// port of appview/repo/rawdiff.go 5 6const OP_CHARS = [" ", "-", "+"] as const; 7 8const opChar = (op: number): string => OP_CHARS[op] ?? "?"; 9 10const noEol = (line: string): boolean => line.length === 0 || !line.endsWith("\n"); 11 12const fragmentHeader = (fragment: TextFragment): string => { 13 const header = `@@ -${fragment.OldPosition ?? 0},${fragment.OldLines ?? 0} +${fragment.NewPosition ?? 0},${fragment.NewLines ?? 0} @@`; 14 return fragment.Comment ? `${header} ${fragment.Comment}` : header; 15}; 16 17const fragmentString = (fragment: TextFragment): string => { 18 let out = `${fragmentHeader(fragment)}\n`; 19 for (const line of fragment.Lines ?? []) { 20 out += opChar(line.Op) + line.Line; 21 if (noEol(line.Line)) out += "\n\\ No newline at end of file\n"; 22 } 23 return out; 24}; 25 26export const fileStats = (file: DiffFile): { insertions: number; deletions: number } => { 27 let insertions = 0; 28 let deletions = 0; 29 for (const fragment of file.text_fragments ?? []) { 30 insertions += fragment.LinesAdded ?? 0; 31 deletions += fragment.LinesDeleted ?? 0; 32 } 33 return { insertions, deletions }; 34}; 35 36export const renderUnifiedDiff = (niceDiff: NiceDiff | null | undefined): string => { 37 if (!niceDiff) return ""; 38 let out = ""; 39 for (const file of niceDiff.diff ?? []) { 40 const oldName = file.name.old || file.name.new; 41 const newName = file.name.new || file.name.old; 42 43 out += `diff --git a/${oldName} b/${newName}\n`; 44 if (file.is_new) { 45 out += "new file mode 100644\n"; 46 out += "--- /dev/null\n"; 47 out += `+++ b/${newName}\n`; 48 } else if (file.is_delete) { 49 out += "deleted file mode 100644\n"; 50 out += `--- a/${oldName}\n`; 51 out += "+++ /dev/null\n"; 52 } else if (file.is_rename) { 53 out += `rename from ${oldName}\n`; 54 out += `rename to ${newName}\n`; 55 out += `--- a/${oldName}\n`; 56 out += `+++ b/${newName}\n`; 57 } else { 58 out += `--- a/${oldName}\n`; 59 out += `+++ b/${newName}\n`; 60 } 61 62 for (const fragment of file.text_fragments ?? []) { 63 out += fragmentString(fragment); 64 } 65 } 66 return out; 67}; 68 69const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const; 70const MONTHS = [ 71 "Jan", 72 "Feb", 73 "Mar", 74 "Apr", 75 "May", 76 "Jun", 77 "Jul", 78 "Aug", 79 "Sep", 80 "Oct", 81 "Nov", 82 "Dec" 83] as const; 84 85const pad = (value: number): string => String(value).padStart(2, "0"); 86 87// RFC1123Z, always utc 88const formatPatchDate = (input: string): string => { 89 const date = new Date(input); 90 // a missing When is go's zero time, formats to this exact string 91 if (Number.isNaN(date.getTime())) return "Mon, 01 Jan 0001 00:00:00 +0000"; 92 return ( 93 `${WEEKDAYS[date.getUTCDay()]}, ${pad(date.getUTCDate())} ${MONTHS[date.getUTCMonth()]} ` + 94 `${String(date.getUTCFullYear()).padStart(4, "0")} ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())} +0000` 95 ); 96}; 97 98export const renderFormatPatch = (niceDiff: NiceDiff | null | undefined): string => { 99 if (!niceDiff) return ""; 100 const commit = niceDiff.commit ?? {}; 101 const message = commit.message ?? ""; 102 103 // format-patch takes the first LINE as the subject, unlike splitMessage's 104 // first paragraph 105 const newline = message.indexOf("\n"); 106 const subject = newline === -1 ? message : message.slice(0, newline); 107 108 const separator = message.indexOf("\n\n"); 109 const body = separator === -1 ? "" : message.slice(separator + 2).replace(/\n+$/, ""); 110 111 const hash = commit.this || hexFromBytes(commit.hash ?? []); 112 113 let out = `From ${hash} Mon Sep 17 00:00:00 2001\n`; 114 out += `From: ${commit.author?.Name ?? ""} <${commit.author?.Email ?? ""}>\n`; 115 out += `Date: ${formatPatchDate(commit.author?.When ?? "")}\n`; 116 out += `Subject: [PATCH] ${subject}\n`; 117 out += "\n"; 118 if (body !== "") out += `${body}\n`; 119 out += "---\n"; 120 121 for (const file of niceDiff.diff ?? []) { 122 const name = diffFileName(file); 123 const { insertions, deletions } = fileStats(file); 124 out += ` ${name} | ${insertions + deletions} ${"+".repeat(insertions)}${"-".repeat(deletions)}\n`; 125 } 126 const stat = niceDiff.stat ?? { insertions: 0, deletions: 0, files_changed: 0 }; 127 out += ` ${stat.files_changed} file(s) changed, ${stat.insertions} insertion(s)(+), ${stat.deletions} deletion(s)(-)\n\n`; 128 129 out += renderUnifiedDiff(niceDiff); 130 out += "\n--\ntangled.sh\n"; 131 return out; 132};