This repository has no description
0

Configure Feed

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

core / web / src / lib / components / repo / fileDiff.ts
4.4 kB 146 lines
1import type { ChangeContent, ContextContent, FileDiffMetadata, Hunk } from "@pierre/diffs"; 2import { LineOp, type DiffFile } from "$lib/api/diff"; 3 4// old alone collides when one file is deleted and another renamed onto its 5// path in the same commit so we need new too 6export const diffRowKey = (file: DiffFile): string => `${file.name.old}${file.name.new}`; 7 8// pierre parses patch text into FileDiffMetadata, but the wire already hands 9// us hunks, so we can skip the text roundtrip by mapping 10export const toFileDiffMetadata = (file: DiffFile): FileDiffMetadata => { 11 const fragments = file.text_fragments ?? []; 12 13 const hunks: Hunk[] = []; 14 const deletionLines: string[] = []; 15 const additionLines: string[] = []; 16 let splitLineCount = 0; 17 let unifiedLineCount = 0; 18 let lastHunkEnd = 0; 19 20 for (const fragment of fragments) { 21 const additionStart = fragment.NewPosition ?? 0; 22 const additionCount = fragment.NewLines ?? 0; 23 const deletionStart = fragment.OldPosition ?? 0; 24 const deletionCount = fragment.OldLines ?? 0; 25 26 const collapsedBefore = Math.max(additionStart - 1 - lastHunkEnd, 0); 27 lastHunkEnd = additionStart + additionCount - 1; 28 29 const hunk: Hunk = { 30 collapsedBefore, 31 additionStart, 32 additionCount, 33 additionLines: 0, 34 additionLineIndex: additionLines.length, 35 deletionStart, 36 deletionCount, 37 deletionLines: 0, 38 deletionLineIndex: deletionLines.length, 39 hunkContent: [], 40 hunkContext: fragment.Comment, 41 hunkSpecs: `@@ -${deletionStart},${deletionCount} +${additionStart},${additionCount} @@`, 42 splitLineStart: 0, 43 splitLineCount: 0, 44 unifiedLineStart: 0, 45 unifiedLineCount: 0, 46 noEOFCRDeletions: false, 47 noEOFCRAdditions: false 48 }; 49 50 let current: ContextContent | ChangeContent | null = null; 51 let lastOp: LineOp | null = null; 52 for (const { Op: op, Line: raw } of fragment.Lines ?? []) { 53 if (op === LineOp.Context) { 54 if (current?.type !== "context") { 55 current = { 56 type: "context", 57 lines: 0, 58 additionLineIndex: additionLines.length, 59 deletionLineIndex: deletionLines.length 60 }; 61 hunk.hunkContent.push(current); 62 } 63 current.lines++; 64 additionLines.push(raw); 65 deletionLines.push(raw); 66 } else { 67 if (current?.type !== "change") { 68 current = { 69 type: "change", 70 additions: 0, 71 deletions: 0, 72 additionLineIndex: additionLines.length, 73 deletionLineIndex: deletionLines.length 74 }; 75 hunk.hunkContent.push(current); 76 } 77 if (op === LineOp.Add) { 78 current.additions++; 79 hunk.additionLines++; 80 additionLines.push(raw); 81 } else { 82 current.deletions++; 83 hunk.deletionLines++; 84 deletionLines.push(raw); 85 } 86 } 87 lastOp = op; 88 } 89 90 // a line without a trailing newline is the patch's "\ No newline" 91 // marker, only possible at the end of the file 92 const last = fragment.Lines?.[fragment.Lines.length - 1]; 93 if (last && !last.Line.endsWith("\n")) { 94 if (last.Op === LineOp.Context) { 95 hunk.noEOFCRAdditions = true; 96 hunk.noEOFCRDeletions = true; 97 } else if (last.Op === LineOp.Delete) { 98 hunk.noEOFCRDeletions = true; 99 } else { 100 hunk.noEOFCRAdditions = true; 101 } 102 if (lastOp === LineOp.Add || lastOp === LineOp.Context) { 103 additionLines[additionLines.length - 1] = last.Line; 104 } 105 if (lastOp === LineOp.Delete || lastOp === LineOp.Context) { 106 deletionLines[deletionLines.length - 1] = last.Line; 107 } 108 } 109 110 for (const content of hunk.hunkContent) { 111 if (content.type === "context") { 112 hunk.splitLineCount += content.lines; 113 hunk.unifiedLineCount += content.lines; 114 } else { 115 hunk.splitLineCount += Math.max(content.additions, content.deletions); 116 hunk.unifiedLineCount += content.deletions + content.additions; 117 } 118 } 119 hunk.splitLineStart = splitLineCount + collapsedBefore; 120 hunk.unifiedLineStart = unifiedLineCount + collapsedBefore; 121 splitLineCount += collapsedBefore + hunk.splitLineCount; 122 unifiedLineCount += collapsedBefore + hunk.unifiedLineCount; 123 124 hunks.push(hunk); 125 } 126 127 const type: FileDiffMetadata["type"] = file.is_new 128 ? "new" 129 : file.is_delete 130 ? "deleted" 131 : file.is_rename 132 ? "rename-changed" 133 : "change"; 134 135 return { 136 name: file.name.new || file.name.old, 137 prevName: type === "rename-changed" ? file.name.old : undefined, 138 type, 139 hunks, 140 splitLineCount, 141 unifiedLineCount, 142 isPartial: true, 143 deletionLines, 144 additionLines 145 }; 146};