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