| b69ab31 | | | 1 | /** |
| b69ab31 | | | 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| b69ab31 | | | 3 | * |
| b69ab31 | | | 4 | * This source code is licensed under the MIT license found in the |
| b69ab31 | | | 5 | * LICENSE file in the root directory of this source tree. |
| b69ab31 | | | 6 | */ |
| b69ab31 | | | 7 | |
| b69ab31 | | | 8 | import type {Hunk, ParsedDiff} from './types'; |
| b69ab31 | | | 9 | import {DiffType} from './types'; |
| b69ab31 | | | 10 | |
| b69ab31 | | | 11 | /** |
| b69ab31 | | | 12 | * Convert a parsed diff back to git diff format string. |
| b69ab31 | | | 13 | * |
| b69ab31 | | | 14 | * This is the reverse operation of parsePatch in parse.ts. |
| b69ab31 | | | 15 | */ |
| b69ab31 | | | 16 | export function stringifyPatch(parsedDiffs: ParsedDiff[]): string { |
| b69ab31 | | | 17 | return parsedDiffs.map(stringifyDiff).join(''); |
| b69ab31 | | | 18 | } |
| b69ab31 | | | 19 | |
| b69ab31 | | | 20 | function stringifyDiff(diff: ParsedDiff): string { |
| b69ab31 | | | 21 | const parts: string[] = []; |
| b69ab31 | | | 22 | |
| b69ab31 | | | 23 | // diff header |
| b69ab31 | | | 24 | if (diff.oldFileName && diff.newFileName) { |
| b69ab31 | | | 25 | parts.push(`diff --git ${diff.oldFileName} ${diff.newFileName}\n`); |
| b69ab31 | | | 26 | } |
| b69ab31 | | | 27 | |
| b69ab31 | | | 28 | // extended header lines |
| b69ab31 | | | 29 | if (diff.type === DiffType.Renamed) { |
| b69ab31 | | | 30 | const oldName = diff.oldFileName?.replace(/^a\//, '') ?? ''; |
| b69ab31 | | | 31 | const newName = diff.newFileName?.replace(/^b\//, '') ?? ''; |
| b69ab31 | | | 32 | parts.push(`rename from ${oldName}\n`); |
| b69ab31 | | | 33 | parts.push(`rename to ${newName}\n`); |
| b69ab31 | | | 34 | } |
| b69ab31 | | | 35 | |
| b69ab31 | | | 36 | if (diff.type === DiffType.Copied) { |
| b69ab31 | | | 37 | const oldName = diff.oldFileName?.replace(/^a\//, '') ?? ''; |
| b69ab31 | | | 38 | const newName = diff.newFileName?.replace(/^b\//, '') ?? ''; |
| b69ab31 | | | 39 | parts.push(`copy from ${oldName}\n`); |
| b69ab31 | | | 40 | parts.push(`copy to ${newName}\n`); |
| b69ab31 | | | 41 | } |
| b69ab31 | | | 42 | |
| b69ab31 | | | 43 | if (diff.oldMode && diff.newMode && diff.oldMode !== diff.newMode) { |
| b69ab31 | | | 44 | parts.push(`old mode ${diff.oldMode}\n`); |
| b69ab31 | | | 45 | parts.push(`new mode ${diff.newMode}\n`); |
| b69ab31 | | | 46 | } |
| b69ab31 | | | 47 | |
| b69ab31 | | | 48 | if (diff.type === DiffType.Added && diff.newMode) { |
| b69ab31 | | | 49 | parts.push(`new file mode ${diff.newMode}\n`); |
| b69ab31 | | | 50 | } |
| b69ab31 | | | 51 | |
| b69ab31 | | | 52 | if (diff.type === DiffType.Removed && diff.newMode) { |
| b69ab31 | | | 53 | parts.push(`deleted file mode ${diff.newMode}\n`); |
| b69ab31 | | | 54 | } |
| b69ab31 | | | 55 | |
| b69ab31 | | | 56 | // file headers |
| b69ab31 | | | 57 | if (diff.hunks.length > 0) { |
| b69ab31 | | | 58 | const oldFile = diff.type === DiffType.Added ? '/dev/null' : (diff.oldFileName ?? '/dev/null'); |
| b69ab31 | | | 59 | const newFile = |
| b69ab31 | | | 60 | diff.type === DiffType.Removed ? '/dev/null' : (diff.newFileName ?? '/dev/null'); |
| b69ab31 | | | 61 | parts.push(`--- ${oldFile}\n`); |
| b69ab31 | | | 62 | parts.push(`+++ ${newFile}\n`); |
| b69ab31 | | | 63 | } |
| b69ab31 | | | 64 | |
| b69ab31 | | | 65 | // hunks |
| b69ab31 | | | 66 | diff.hunks.forEach(hunk => { |
| b69ab31 | | | 67 | parts.push(stringifyHunk(hunk)); |
| b69ab31 | | | 68 | }); |
| b69ab31 | | | 69 | |
| b69ab31 | | | 70 | return parts.join(''); |
| b69ab31 | | | 71 | } |
| b69ab31 | | | 72 | |
| b69ab31 | | | 73 | function stringifyHunk(hunk: Hunk): string { |
| b69ab31 | | | 74 | const parts: string[] = []; |
| b69ab31 | | | 75 | |
| b69ab31 | | | 76 | // Handle the Unified Diff Format quirk: |
| b69ab31 | | | 77 | // If the hunk size is 0, the start line is one higher than stored |
| b69ab31 | | | 78 | let oldStart = hunk.oldStart; |
| b69ab31 | | | 79 | let newStart = hunk.newStart; |
| b69ab31 | | | 80 | |
| b69ab31 | | | 81 | if (hunk.oldLines === 0) { |
| b69ab31 | | | 82 | oldStart -= 1; |
| b69ab31 | | | 83 | } |
| b69ab31 | | | 84 | if (hunk.newLines === 0) { |
| b69ab31 | | | 85 | newStart -= 1; |
| b69ab31 | | | 86 | } |
| b69ab31 | | | 87 | |
| b69ab31 | | | 88 | // hunk header - always include line count |
| b69ab31 | | | 89 | const oldRange = `${oldStart},${hunk.oldLines}`; |
| b69ab31 | | | 90 | const newRange = `${newStart},${hunk.newLines}`; |
| b69ab31 | | | 91 | parts.push(`@@ -${oldRange} +${newRange} @@\n`); |
| b69ab31 | | | 92 | |
| b69ab31 | | | 93 | // hunk lines |
| b69ab31 | | | 94 | hunk.lines.forEach((line, index) => { |
| b69ab31 | | | 95 | const delimiter = hunk.linedelimiters[index] ?? '\n'; |
| b69ab31 | | | 96 | parts.push(line + delimiter); |
| b69ab31 | | | 97 | }); |
| b69ab31 | | | 98 | |
| b69ab31 | | | 99 | return parts.join(''); |
| b69ab31 | | | 100 | } |