codekingpro/portable-devtools
115k
1/**
2 * Parses a patch into structured data, in the same structure returned by `structuredPatch`.
3 *
4 * @return a JSON object representation of the a patch, suitable for use with the `applyPatch` method.
5 */
6export function parsePatch(uniDiff) {
7 const diffstr = uniDiff.split(/\n/), list = [];
8 let i = 0;
9 function parseIndex() {
10 const index = {};
11 list.push(index);
12 // Parse diff metadata
13 while (i < diffstr.length) {
14 const line = diffstr[i];
15 // File header found, end parsing diff metadata
16 if ((/^(---|\+\+\+|@@)\s/).test(line)) {
17 break;
18 }
19 // Try to parse the line as a diff header, like
20 // Index: README.md
21 // or
22 // diff -r 9117c6561b0b -r 273ce12ad8f1 .hgignore
23 // or
24 // Index: something with multiple words
25 // and extract the filename (or whatever else is used as an index name)
26 // from the end (i.e. 'README.md', '.hgignore', or
27 // 'something with multiple words' in the examples above).
28 //
29 // TODO: It seems awkward that we indiscriminately trim off trailing
30 // whitespace here. Theoretically, couldn't that be meaningful -
31 // e.g. if the patch represents a diff of a file whose name ends
32 // with a space? Seems wrong to nuke it.
33 // But this behaviour has been around since v2.2.1 in 2015, so if
34 // it's going to change, it should be done cautiously and in a new
35 // major release, for backwards-compat reasons.
36 // -- ExplodingCabbage
37 const headerMatch = (/^(?:Index:|diff(?: -r \w+)+)\s+/).exec(line);
38 if (headerMatch) {
39 index.index = line.substring(headerMatch[0].length).trim();
40 }
41 i++;
42 }
43 // Parse file headers if they are defined. Unified diff requires them, but
44 // there's no technical issues to have an isolated hunk without file header
45 parseFileHeader(index);
46 parseFileHeader(index);
47 // Parse hunks
48 index.hunks = [];
49 while (i < diffstr.length) {
50 const line = diffstr[i];
51 if ((/^(Index:\s|diff\s|---\s|\+\+\+\s|===================================================================)/).test(line)) {
52 break;
53 }
54 else if ((/^@@/).test(line)) {
55 index.hunks.push(parseHunk());
56 }
57 else if (line) {
58 throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(line));
59 }
60 else {
61 i++;
62 }
63 }
64 }
65 // Parses the --- and +++ headers, if none are found, no lines
66 // are consumed.
67 function parseFileHeader(index) {
68 const fileHeaderMatch = (/^(---|\+\+\+)\s+/).exec(diffstr[i]);
69 if (fileHeaderMatch) {
70 const prefix = fileHeaderMatch[1], data = diffstr[i].substring(3).trim().split('\t', 2), header = (data[1] || '').trim();
71 let fileName = data[0].replace(/\\\\/g, '\\');
72 if (fileName.startsWith('"') && fileName.endsWith('"')) {
73 fileName = fileName.substr(1, fileName.length - 2);
74 }
75 if (prefix === '---') {
76 index.oldFileName = fileName;
77 index.oldHeader = header;
78 }
79 else {
80 index.newFileName = fileName;
81 index.newHeader = header;
82 }
83 i++;
84 }
85 }
86 // Parses a hunk
87 // This assumes that we are at the start of a hunk.
88 function parseHunk() {
89 var _a;
90 const chunkHeaderIndex = i, chunkHeaderLine = diffstr[i++], chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
91 const hunk = {
92 oldStart: +chunkHeader[1],
93 oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2],
94 newStart: +chunkHeader[3],
95 newLines: typeof chunkHeader[4] === 'undefined' ? 1 : +chunkHeader[4],
96 lines: []
97 };
98 // Unified Diff Format quirk: If the chunk size is 0,
99 // the first number is one lower than one would expect.
100 // https://www.artima.com/weblogs/viewpost.jsp?thread=164293
101 if (hunk.oldLines === 0) {
102 hunk.oldStart += 1;
103 }
104 if (hunk.newLines === 0) {
105 hunk.newStart += 1;
106 }
107 let addCount = 0, removeCount = 0;
108 for (; i < diffstr.length && (removeCount < hunk.oldLines || addCount < hunk.newLines || ((_a = diffstr[i]) === null || _a === void 0 ? void 0 : _a.startsWith('\\'))); i++) {
109 const operation = (diffstr[i].length == 0 && i != (diffstr.length - 1)) ? ' ' : diffstr[i][0];
110 if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') {
111 hunk.lines.push(diffstr[i]);
112 if (operation === '+') {
113 addCount++;
114 }
115 else if (operation === '-') {
116 removeCount++;
117 }
118 else if (operation === ' ') {
119 addCount++;
120 removeCount++;
121 }
122 }
123 else {
124 throw new Error(`Hunk at line ${chunkHeaderIndex + 1} contained invalid line ${diffstr[i]}`);
125 }
126 }
127 // Handle the empty block count case
128 if (!addCount && hunk.newLines === 1) {
129 hunk.newLines = 0;
130 }
131 if (!removeCount && hunk.oldLines === 1) {
132 hunk.oldLines = 0;
133 }
134 // Perform sanity checking
135 if (addCount !== hunk.newLines) {
136 throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
137 }
138 if (removeCount !== hunk.oldLines) {
139 throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
140 }
141 return hunk;
142 }
143 while (i < diffstr.length) {
144 parseIndex();
145 }
146 return list;
147}
148 