blob: bba91954f4b23baf9da103e4dd2b3b57223ce610 [file] [log] [blame]
Peter Marshall0b95ea12020-07-02 18:50:04 +02001(function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3 typeof define === 'function' && define.amd ? define(['exports'], factory) :
4 (global = global || self, factory(global.Diff = {}));
Tim van der Lippe6d109a92021-02-16 16:00:32 +00005}(this, (function (exports) { 'use strict';
Peter Marshall0b95ea12020-07-02 18:50:04 +02006
7 function Diff() {}
8 Diff.prototype = {
9 diff: function diff(oldString, newString) {
10 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
11 var callback = options.callback;
12
13 if (typeof options === 'function') {
14 callback = options;
15 options = {};
16 }
17
18 this.options = options;
19 var self = this;
20
21 function done(value) {
22 if (callback) {
23 setTimeout(function () {
24 callback(undefined, value);
25 }, 0);
26 return true;
27 } else {
28 return value;
29 }
30 } // Allow subclasses to massage the input prior to running
31
32
33 oldString = this.castInput(oldString);
34 newString = this.castInput(newString);
35 oldString = this.removeEmpty(this.tokenize(oldString));
36 newString = this.removeEmpty(this.tokenize(newString));
37 var newLen = newString.length,
38 oldLen = oldString.length;
39 var editLength = 1;
40 var maxEditLength = newLen + oldLen;
41 var bestPath = [{
42 newPos: -1,
43 components: []
44 }]; // Seed editLength = 0, i.e. the content starts with the same values
45
46 var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0);
47
48 if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) {
49 // Identity per the equality and tokenizer
50 return done([{
51 value: this.join(newString),
52 count: newString.length
53 }]);
54 } // Main worker method. checks all permutations of a given edit length for acceptance.
55
56
57 function execEditLength() {
58 for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) {
59 var basePath = void 0;
60
61 var addPath = bestPath[diagonalPath - 1],
62 removePath = bestPath[diagonalPath + 1],
63 _oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;
64
65 if (addPath) {
66 // No one else is going to attempt to use this value, clear it
67 bestPath[diagonalPath - 1] = undefined;
68 }
69
70 var canAdd = addPath && addPath.newPos + 1 < newLen,
71 canRemove = removePath && 0 <= _oldPos && _oldPos < oldLen;
72
73 if (!canAdd && !canRemove) {
74 // If this path is a terminal then prune
75 bestPath[diagonalPath] = undefined;
76 continue;
77 } // Select the diagonal that we want to branch from. We select the prior
78 // path whose position in the new string is the farthest from the origin
79 // and does not pass the bounds of the diff graph
80
81
82 if (!canAdd || canRemove && addPath.newPos < removePath.newPos) {
83 basePath = clonePath(removePath);
84 self.pushComponent(basePath.components, undefined, true);
85 } else {
86 basePath = addPath; // No need to clone, we've pulled it from the list
87
88 basePath.newPos++;
89 self.pushComponent(basePath.components, true, undefined);
90 }
91
92 _oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); // If we have hit the end of both strings, then we are done
93
94 if (basePath.newPos + 1 >= newLen && _oldPos + 1 >= oldLen) {
95 return done(buildValues(self, basePath.components, newString, oldString, self.useLongestToken));
96 } else {
97 // Otherwise track this path as a potential candidate and continue.
98 bestPath[diagonalPath] = basePath;
99 }
100 }
101
102 editLength++;
103 } // Performs the length of edit iteration. Is a bit fugly as this has to support the
104 // sync and async mode which is never fun. Loops over execEditLength until a value
105 // is produced.
106
107
108 if (callback) {
109 (function exec() {
110 setTimeout(function () {
111 // This should not happen, but we want to be safe.
112
113 /* istanbul ignore next */
114 if (editLength > maxEditLength) {
115 return callback();
116 }
117
118 if (!execEditLength()) {
119 exec();
120 }
121 }, 0);
122 })();
123 } else {
124 while (editLength <= maxEditLength) {
125 var ret = execEditLength();
126
127 if (ret) {
128 return ret;
129 }
130 }
131 }
132 },
133 pushComponent: function pushComponent(components, added, removed) {
134 var last = components[components.length - 1];
135
136 if (last && last.added === added && last.removed === removed) {
137 // We need to clone here as the component clone operation is just
138 // as shallow array clone
139 components[components.length - 1] = {
140 count: last.count + 1,
141 added: added,
142 removed: removed
143 };
144 } else {
145 components.push({
146 count: 1,
147 added: added,
148 removed: removed
149 });
150 }
151 },
152 extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath) {
153 var newLen = newString.length,
154 oldLen = oldString.length,
155 newPos = basePath.newPos,
156 oldPos = newPos - diagonalPath,
157 commonCount = 0;
158
159 while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) {
160 newPos++;
161 oldPos++;
162 commonCount++;
163 }
164
165 if (commonCount) {
166 basePath.components.push({
167 count: commonCount
168 });
169 }
170
171 basePath.newPos = newPos;
172 return oldPos;
173 },
174 equals: function equals(left, right) {
175 if (this.options.comparator) {
176 return this.options.comparator(left, right);
177 } else {
178 return left === right || this.options.ignoreCase && left.toLowerCase() === right.toLowerCase();
179 }
180 },
181 removeEmpty: function removeEmpty(array) {
182 var ret = [];
183
184 for (var i = 0; i < array.length; i++) {
185 if (array[i]) {
186 ret.push(array[i]);
187 }
188 }
189
190 return ret;
191 },
192 castInput: function castInput(value) {
193 return value;
194 },
195 tokenize: function tokenize(value) {
196 return value.split('');
197 },
198 join: function join(chars) {
199 return chars.join('');
200 }
201 };
202
203 function buildValues(diff, components, newString, oldString, useLongestToken) {
204 var componentPos = 0,
205 componentLen = components.length,
206 newPos = 0,
207 oldPos = 0;
208
209 for (; componentPos < componentLen; componentPos++) {
210 var component = components[componentPos];
211
212 if (!component.removed) {
213 if (!component.added && useLongestToken) {
214 var value = newString.slice(newPos, newPos + component.count);
215 value = value.map(function (value, i) {
216 var oldValue = oldString[oldPos + i];
217 return oldValue.length > value.length ? oldValue : value;
218 });
219 component.value = diff.join(value);
220 } else {
221 component.value = diff.join(newString.slice(newPos, newPos + component.count));
222 }
223
224 newPos += component.count; // Common case
225
226 if (!component.added) {
227 oldPos += component.count;
228 }
229 } else {
230 component.value = diff.join(oldString.slice(oldPos, oldPos + component.count));
231 oldPos += component.count; // Reverse add and remove so removes are output first to match common convention
232 // The diffing algorithm is tied to add then remove output and this is the simplest
233 // route to get the desired output with minimal overhead.
234
235 if (componentPos && components[componentPos - 1].added) {
236 var tmp = components[componentPos - 1];
237 components[componentPos - 1] = components[componentPos];
238 components[componentPos] = tmp;
239 }
240 }
241 } // Special case handle for when one terminal is ignored (i.e. whitespace).
242 // For this case we merge the terminal into the prior string and drop the change.
243 // This is only available for string mode.
244
245
246 var lastComponent = components[componentLen - 1];
247
248 if (componentLen > 1 && typeof lastComponent.value === 'string' && (lastComponent.added || lastComponent.removed) && diff.equals('', lastComponent.value)) {
249 components[componentLen - 2].value += lastComponent.value;
250 components.pop();
251 }
252
253 return components;
254 }
255
256 function clonePath(path) {
257 return {
258 newPos: path.newPos,
259 components: path.components.slice(0)
260 };
261 }
262
263 var characterDiff = new Diff();
264 function diffChars(oldStr, newStr, options) {
265 return characterDiff.diff(oldStr, newStr, options);
266 }
267
268 function generateOptions(options, defaults) {
269 if (typeof options === 'function') {
270 defaults.callback = options;
271 } else if (options) {
272 for (var name in options) {
273 /* istanbul ignore else */
274 if (options.hasOwnProperty(name)) {
275 defaults[name] = options[name];
276 }
277 }
278 }
279
280 return defaults;
281 }
282
283 //
284 // Ranges and exceptions:
285 // Latin-1 Supplement, 0080–00FF
286 // - U+00D7 × Multiplication sign
287 // - U+00F7 ÷ Division sign
288 // Latin Extended-A, 0100–017F
289 // Latin Extended-B, 0180–024F
290 // IPA Extensions, 0250–02AF
291 // Spacing Modifier Letters, 02B0–02FF
292 // - U+02C7 ˇ &#711; Caron
293 // - U+02D8 ˘ &#728; Breve
294 // - U+02D9 ˙ &#729; Dot Above
295 // - U+02DA ˚ &#730; Ring Above
296 // - U+02DB ˛ &#731; Ogonek
297 // - U+02DC ˜ &#732; Small Tilde
298 // - U+02DD ˝ &#733; Double Acute Accent
299 // Latin Extended Additional, 1E00–1EFF
300
301 var extendedWordChars = /^[A-Za-z\xC0-\u02C6\u02C8-\u02D7\u02DE-\u02FF\u1E00-\u1EFF]+$/;
302 var reWhitespace = /\S/;
303 var wordDiff = new Diff();
304
305 wordDiff.equals = function (left, right) {
306 if (this.options.ignoreCase) {
307 left = left.toLowerCase();
308 right = right.toLowerCase();
309 }
310
311 return left === right || this.options.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right);
312 };
313
314 wordDiff.tokenize = function (value) {
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000315 // All whitespace symbols except newline group into one token, each newline - in separate token
316 var tokens = value.split(/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/); // Join the boundary splits that we do not consider to be boundaries. This is primarily the extended Latin character set.
Peter Marshall0b95ea12020-07-02 18:50:04 +0200317
318 for (var i = 0; i < tokens.length - 1; i++) {
319 // If we have an empty string in the next field and we have only word chars before and after, merge
320 if (!tokens[i + 1] && tokens[i + 2] && extendedWordChars.test(tokens[i]) && extendedWordChars.test(tokens[i + 2])) {
321 tokens[i] += tokens[i + 2];
322 tokens.splice(i + 1, 2);
323 i--;
324 }
325 }
326
327 return tokens;
328 };
329
330 function diffWords(oldStr, newStr, options) {
331 options = generateOptions(options, {
332 ignoreWhitespace: true
333 });
334 return wordDiff.diff(oldStr, newStr, options);
335 }
336 function diffWordsWithSpace(oldStr, newStr, options) {
337 return wordDiff.diff(oldStr, newStr, options);
338 }
339
340 var lineDiff = new Diff();
341
342 lineDiff.tokenize = function (value) {
343 var retLines = [],
344 linesAndNewlines = value.split(/(\n|\r\n)/); // Ignore the final empty token that occurs if the string ends with a new line
345
346 if (!linesAndNewlines[linesAndNewlines.length - 1]) {
347 linesAndNewlines.pop();
348 } // Merge the content and line separators into single tokens
349
350
351 for (var i = 0; i < linesAndNewlines.length; i++) {
352 var line = linesAndNewlines[i];
353
354 if (i % 2 && !this.options.newlineIsToken) {
355 retLines[retLines.length - 1] += line;
356 } else {
357 if (this.options.ignoreWhitespace) {
358 line = line.trim();
359 }
360
361 retLines.push(line);
362 }
363 }
364
365 return retLines;
366 };
367
368 function diffLines(oldStr, newStr, callback) {
369 return lineDiff.diff(oldStr, newStr, callback);
370 }
371 function diffTrimmedLines(oldStr, newStr, callback) {
372 var options = generateOptions(callback, {
373 ignoreWhitespace: true
374 });
375 return lineDiff.diff(oldStr, newStr, options);
376 }
377
378 var sentenceDiff = new Diff();
379
380 sentenceDiff.tokenize = function (value) {
381 return value.split(/(\S.+?[.!?])(?=\s+|$)/);
382 };
383
384 function diffSentences(oldStr, newStr, callback) {
385 return sentenceDiff.diff(oldStr, newStr, callback);
386 }
387
388 var cssDiff = new Diff();
389
390 cssDiff.tokenize = function (value) {
391 return value.split(/([{}:;,]|\s+)/);
392 };
393
394 function diffCss(oldStr, newStr, callback) {
395 return cssDiff.diff(oldStr, newStr, callback);
396 }
397
398 function _typeof(obj) {
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000399 "@babel/helpers - typeof";
400
Peter Marshall0b95ea12020-07-02 18:50:04 +0200401 if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
402 _typeof = function (obj) {
403 return typeof obj;
404 };
405 } else {
406 _typeof = function (obj) {
407 return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
408 };
409 }
410
411 return _typeof(obj);
412 }
413
414 function _toConsumableArray(arr) {
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000415 return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
Peter Marshall0b95ea12020-07-02 18:50:04 +0200416 }
417
418 function _arrayWithoutHoles(arr) {
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000419 if (Array.isArray(arr)) return _arrayLikeToArray(arr);
Peter Marshall0b95ea12020-07-02 18:50:04 +0200420 }
421
422 function _iterableToArray(iter) {
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000423 if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
424 }
425
426 function _unsupportedIterableToArray(o, minLen) {
427 if (!o) return;
428 if (typeof o === "string") return _arrayLikeToArray(o, minLen);
429 var n = Object.prototype.toString.call(o).slice(8, -1);
430 if (n === "Object" && o.constructor) n = o.constructor.name;
431 if (n === "Map" || n === "Set") return Array.from(o);
432 if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
433 }
434
435 function _arrayLikeToArray(arr, len) {
436 if (len == null || len > arr.length) len = arr.length;
437
438 for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
439
440 return arr2;
Peter Marshall0b95ea12020-07-02 18:50:04 +0200441 }
442
443 function _nonIterableSpread() {
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000444 throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
Peter Marshall0b95ea12020-07-02 18:50:04 +0200445 }
446
447 var objectPrototypeToString = Object.prototype.toString;
448 var jsonDiff = new Diff(); // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a
449 // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output:
450
451 jsonDiff.useLongestToken = true;
452 jsonDiff.tokenize = lineDiff.tokenize;
453
454 jsonDiff.castInput = function (value) {
455 var _this$options = this.options,
456 undefinedReplacement = _this$options.undefinedReplacement,
457 _this$options$stringi = _this$options.stringifyReplacer,
458 stringifyReplacer = _this$options$stringi === void 0 ? function (k, v) {
459 return typeof v === 'undefined' ? undefinedReplacement : v;
460 } : _this$options$stringi;
461 return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' ');
462 };
463
464 jsonDiff.equals = function (left, right) {
465 return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'));
466 };
467
468 function diffJson(oldObj, newObj, options) {
469 return jsonDiff.diff(oldObj, newObj, options);
470 } // This function handles the presence of circular references by bailing out when encountering an
471 // object that is already on the "stack" of items being processed. Accepts an optional replacer
472
473 function canonicalize(obj, stack, replacementStack, replacer, key) {
474 stack = stack || [];
475 replacementStack = replacementStack || [];
476
477 if (replacer) {
478 obj = replacer(key, obj);
479 }
480
481 var i;
482
483 for (i = 0; i < stack.length; i += 1) {
484 if (stack[i] === obj) {
485 return replacementStack[i];
486 }
487 }
488
489 var canonicalizedObj;
490
491 if ('[object Array]' === objectPrototypeToString.call(obj)) {
492 stack.push(obj);
493 canonicalizedObj = new Array(obj.length);
494 replacementStack.push(canonicalizedObj);
495
496 for (i = 0; i < obj.length; i += 1) {
497 canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key);
498 }
499
500 stack.pop();
501 replacementStack.pop();
502 return canonicalizedObj;
503 }
504
505 if (obj && obj.toJSON) {
506 obj = obj.toJSON();
507 }
508
509 if (_typeof(obj) === 'object' && obj !== null) {
510 stack.push(obj);
511 canonicalizedObj = {};
512 replacementStack.push(canonicalizedObj);
513
514 var sortedKeys = [],
515 _key;
516
517 for (_key in obj) {
518 /* istanbul ignore else */
519 if (obj.hasOwnProperty(_key)) {
520 sortedKeys.push(_key);
521 }
522 }
523
524 sortedKeys.sort();
525
526 for (i = 0; i < sortedKeys.length; i += 1) {
527 _key = sortedKeys[i];
528 canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key);
529 }
530
531 stack.pop();
532 replacementStack.pop();
533 } else {
534 canonicalizedObj = obj;
535 }
536
537 return canonicalizedObj;
538 }
539
540 var arrayDiff = new Diff();
541
542 arrayDiff.tokenize = function (value) {
543 return value.slice();
544 };
545
546 arrayDiff.join = arrayDiff.removeEmpty = function (value) {
547 return value;
548 };
549
550 function diffArrays(oldArr, newArr, callback) {
551 return arrayDiff.diff(oldArr, newArr, callback);
552 }
553
554 function parsePatch(uniDiff) {
555 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
556 var diffstr = uniDiff.split(/\r\n|[\n\v\f\r\x85]/),
557 delimiters = uniDiff.match(/\r\n|[\n\v\f\r\x85]/g) || [],
558 list = [],
559 i = 0;
560
561 function parseIndex() {
562 var index = {};
563 list.push(index); // Parse diff metadata
564
565 while (i < diffstr.length) {
566 var line = diffstr[i]; // File header found, end parsing diff metadata
567
568 if (/^(\-\-\-|\+\+\+|@@)\s/.test(line)) {
569 break;
570 } // Diff index
571
572
573 var header = /^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/.exec(line);
574
575 if (header) {
576 index.index = header[1];
577 }
578
579 i++;
580 } // Parse file headers if they are defined. Unified diff requires them, but
581 // there's no technical issues to have an isolated hunk without file header
582
583
584 parseFileHeader(index);
585 parseFileHeader(index); // Parse hunks
586
587 index.hunks = [];
588
589 while (i < diffstr.length) {
590 var _line = diffstr[i];
591
592 if (/^(Index:|diff|\-\-\-|\+\+\+)\s/.test(_line)) {
593 break;
594 } else if (/^@@/.test(_line)) {
595 index.hunks.push(parseHunk());
596 } else if (_line && options.strict) {
597 // Ignore unexpected content unless in strict mode
598 throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(_line));
599 } else {
600 i++;
601 }
602 }
603 } // Parses the --- and +++ headers, if none are found, no lines
604 // are consumed.
605
606
607 function parseFileHeader(index) {
608 var fileHeader = /^(---|\+\+\+)\s+(.*)$/.exec(diffstr[i]);
609
610 if (fileHeader) {
611 var keyPrefix = fileHeader[1] === '---' ? 'old' : 'new';
612 var data = fileHeader[2].split('\t', 2);
613 var fileName = data[0].replace(/\\\\/g, '\\');
614
615 if (/^".*"$/.test(fileName)) {
616 fileName = fileName.substr(1, fileName.length - 2);
617 }
618
619 index[keyPrefix + 'FileName'] = fileName;
620 index[keyPrefix + 'Header'] = (data[1] || '').trim();
621 i++;
622 }
623 } // Parses a hunk
624 // This assumes that we are at the start of a hunk.
625
626
627 function parseHunk() {
628 var chunkHeaderIndex = i,
629 chunkHeaderLine = diffstr[i++],
630 chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
631 var hunk = {
632 oldStart: +chunkHeader[1],
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000633 oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2],
Peter Marshall0b95ea12020-07-02 18:50:04 +0200634 newStart: +chunkHeader[3],
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000635 newLines: typeof chunkHeader[4] === 'undefined' ? 1 : +chunkHeader[4],
Peter Marshall0b95ea12020-07-02 18:50:04 +0200636 lines: [],
637 linedelimiters: []
Tim van der Lippe6d109a92021-02-16 16:00:32 +0000638 }; // Unified Diff Format quirk: If the chunk size is 0,
639 // the first number is one lower than one would expect.
640 // https://www.artima.com/weblogs/viewpost.jsp?thread=164293
641
642 if (hunk.oldLines === 0) {
643 hunk.oldStart += 1;
644 }
645
646 if (hunk.newLines === 0) {
647 hunk.newStart += 1;
648 }
649
Peter Marshall0b95ea12020-07-02 18:50:04 +0200650 var addCount = 0,
651 removeCount = 0;
652
653 for (; i < diffstr.length; i++) {
654 // Lines starting with '---' could be mistaken for the "remove line" operation
655 // But they could be the header for the next file. Therefore prune such cases out.
656 if (diffstr[i].indexOf('--- ') === 0 && i + 2 < diffstr.length && diffstr[i + 1].indexOf('+++ ') === 0 && diffstr[i + 2].indexOf('@@') === 0) {
657 break;
658 }
659
660 var operation = diffstr[i].length == 0 && i != diffstr.length - 1 ? ' ' : diffstr[i][0];
661
662 if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') {
663 hunk.lines.push(diffstr[i]);
664 hunk.linedelimiters.push(delimiters[i] || '\n');
665
666 if (operation === '+') {
667 addCount++;
668 } else if (operation === '-') {
669 removeCount++;
670 } else if (operation === ' ') {
671 addCount++;
672 removeCount++;
673 }
674 } else {
675 break;
676 }
677 } // Handle the empty block count case
678
679
680 if (!addCount && hunk.newLines === 1) {
681 hunk.newLines = 0;
682 }
683
684 if (!removeCount && hunk.oldLines === 1) {
685 hunk.oldLines = 0;
686 } // Perform optional sanity checking
687
688
689 if (options.strict) {
690 if (addCount !== hunk.newLines) {
691 throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
692 }
693
694 if (removeCount !== hunk.oldLines) {
695 throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
696 }
697 }
698
699 return hunk;
700 }
701
702 while (i < diffstr.length) {
703 parseIndex();
704 }
705
706 return list;
707 }
708
709 // Iterator that traverses in the range of [min, max], stepping
710 // by distance from a given start position. I.e. for [0, 4], with
711 // start of 2, this will iterate 2, 3, 1, 4, 0.
712 function distanceIterator (start, minLine, maxLine) {
713 var wantForward = true,
714 backwardExhausted = false,
715 forwardExhausted = false,
716 localOffset = 1;
717 return function iterator() {
718 if (wantForward && !forwardExhausted) {
719 if (backwardExhausted) {
720 localOffset++;
721 } else {
722 wantForward = false;
723 } // Check if trying to fit beyond text length, and if not, check it fits
724 // after offset location (or desired location on first iteration)
725
726
727 if (start + localOffset <= maxLine) {
728 return localOffset;
729 }
730
731 forwardExhausted = true;
732 }
733
734 if (!backwardExhausted) {
735 if (!forwardExhausted) {
736 wantForward = true;
737 } // Check if trying to fit before text beginning, and if not, check it fits
738 // before offset location
739
740
741 if (minLine <= start - localOffset) {
742 return -localOffset++;
743 }
744
745 backwardExhausted = true;
746 return iterator();
747 } // We tried to fit hunk before text beginning and beyond text length, then
748 // hunk can't fit on the text. Return undefined
749
750 };
751 }
752
753 function applyPatch(source, uniDiff) {
754 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
755
756 if (typeof uniDiff === 'string') {
757 uniDiff = parsePatch(uniDiff);
758 }
759
760 if (Array.isArray(uniDiff)) {
761 if (uniDiff.length > 1) {
762 throw new Error('applyPatch only works with a single input.');
763 }
764
765 uniDiff = uniDiff[0];
766 } // Apply the diff to the input
767
768
769 var lines = source.split(/\r\n|[\n\v\f\r\x85]/),
770 delimiters = source.match(/\r\n|[\n\v\f\r\x85]/g) || [],
771 hunks = uniDiff.hunks,
772 compareLine = options.compareLine || function (lineNumber, line, operation, patchContent) {
773 return line === patchContent;
774 },
775 errorCount = 0,
776 fuzzFactor = options.fuzzFactor || 0,
777 minLine = 0,
778 offset = 0,
779 removeEOFNL,
780 addEOFNL;
781 /**
782 * Checks if the hunk exactly fits on the provided location
783 */
784
785
786 function hunkFits(hunk, toPos) {
787 for (var j = 0; j < hunk.lines.length; j++) {
788 var line = hunk.lines[j],
789 operation = line.length > 0 ? line[0] : ' ',
790 content = line.length > 0 ? line.substr(1) : line;
791
792 if (operation === ' ' || operation === '-') {
793 // Context sanity check
794 if (!compareLine(toPos + 1, lines[toPos], operation, content)) {
795 errorCount++;
796
797 if (errorCount > fuzzFactor) {
798 return false;
799 }
800 }
801
802 toPos++;
803 }
804 }
805
806 return true;
807 } // Search best fit offsets for each hunk based on the previous ones
808
809
810 for (var i = 0; i < hunks.length; i++) {
811 var hunk = hunks[i],
812 maxLine = lines.length - hunk.oldLines,
813 localOffset = 0,
814 toPos = offset + hunk.oldStart - 1;
815 var iterator = distanceIterator(toPos, minLine, maxLine);
816
817 for (; localOffset !== undefined; localOffset = iterator()) {
818 if (hunkFits(hunk, toPos + localOffset)) {
819 hunk.offset = offset += localOffset;
820 break;
821 }
822 }
823
824 if (localOffset === undefined) {
825 return false;
826 } // Set lower text limit to end of the current hunk, so next ones don't try
827 // to fit over already patched text
828
829
830 minLine = hunk.offset + hunk.oldStart + hunk.oldLines;
831 } // Apply patch hunks
832
833
834 var diffOffset = 0;
835
836 for (var _i = 0; _i < hunks.length; _i++) {
837 var _hunk = hunks[_i],
838 _toPos = _hunk.oldStart + _hunk.offset + diffOffset - 1;
839
840 diffOffset += _hunk.newLines - _hunk.oldLines;
841
Peter Marshall0b95ea12020-07-02 18:50:04 +0200842 for (var j = 0; j < _hunk.lines.length; j++) {
843 var line = _hunk.lines[j],
844 operation = line.length > 0 ? line[0] : ' ',
845 content = line.length > 0 ? line.substr(1) : line,
846 delimiter = _hunk.linedelimiters[j];
847
848 if (operation === ' ') {
849 _toPos++;
850 } else if (operation === '-') {
851 lines.splice(_toPos, 1);
852 delimiters.splice(_toPos, 1);
853 /* istanbul ignore else */
854 } else if (operation === '+') {
855 lines.splice(_toPos, 0, content);
856 delimiters.splice(_toPos, 0, delimiter);
857 _toPos++;
858 } else if (operation === '\\') {
859 var previousOperation = _hunk.lines[j - 1] ? _hunk.lines[j - 1][0] : null;
860
861 if (previousOperation === '+') {
862 removeEOFNL = true;
863 } else if (previousOperation === '-') {
864 addEOFNL = true;
865 }
866 }
867 }
868 } // Handle EOFNL insertion/removal
869
870
871 if (removeEOFNL) {
872 while (!lines[lines.length - 1]) {
873 lines.pop();
874 delimiters.pop();
875 }
876 } else if (addEOFNL) {
877 lines.push('');
878 delimiters.push('\n');
879 }
880
881 for (var _k = 0; _k < lines.length - 1; _k++) {
882 lines[_k] = lines[_k] + delimiters[_k];
883 }
884
885 return lines.join('');
886 } // Wrapper that supports multiple file patches via callbacks.
887
888 function applyPatches(uniDiff, options) {
889 if (typeof uniDiff === 'string') {
890 uniDiff = parsePatch(uniDiff);
891 }
892
893 var currentIndex = 0;
894
895 function processIndex() {
896 var index = uniDiff[currentIndex++];
897
898 if (!index) {
899 return options.complete();
900 }
901
902 options.loadFile(index, function (err, data) {
903 if (err) {
904 return options.complete(err);
905 }
906
907 var updatedContent = applyPatch(data, index, options);
908 options.patched(index, updatedContent, function (err) {
909 if (err) {
910 return options.complete(err);
911 }
912
913 processIndex();
914 });
915 });
916 }
917
918 processIndex();
919 }
920
921 function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
922 if (!options) {
923 options = {};
924 }
925
926 if (typeof options.context === 'undefined') {
927 options.context = 4;
928 }
929
930 var diff = diffLines(oldStr, newStr, options);
931 diff.push({
932 value: '',
933 lines: []
934 }); // Append an empty value to make cleanup easier
935
936 function contextLines(lines) {
937 return lines.map(function (entry) {
938 return ' ' + entry;
939 });
940 }
941
942 var hunks = [];
943 var oldRangeStart = 0,
944 newRangeStart = 0,
945 curRange = [],
946 oldLine = 1,
947 newLine = 1;
948
949 var _loop = function _loop(i) {
950 var current = diff[i],
951 lines = current.lines || current.value.replace(/\n$/, '').split('\n');
952 current.lines = lines;
953
954 if (current.added || current.removed) {
955 var _curRange;
956
957 // If we have previous context, start with that
958 if (!oldRangeStart) {
959 var prev = diff[i - 1];
960 oldRangeStart = oldLine;
961 newRangeStart = newLine;
962
963 if (prev) {
964 curRange = options.context > 0 ? contextLines(prev.lines.slice(-options.context)) : [];
965 oldRangeStart -= curRange.length;
966 newRangeStart -= curRange.length;
967 }
968 } // Output our changes
969
970
971 (_curRange = curRange).push.apply(_curRange, _toConsumableArray(lines.map(function (entry) {
972 return (current.added ? '+' : '-') + entry;
973 }))); // Track the updated file position
974
975
976 if (current.added) {
977 newLine += lines.length;
978 } else {
979 oldLine += lines.length;
980 }
981 } else {
982 // Identical context lines. Track line changes
983 if (oldRangeStart) {
984 // Close out any changes that have been output (or join overlapping)
985 if (lines.length <= options.context * 2 && i < diff.length - 2) {
986 var _curRange2;
987
988 // Overlapping
989 (_curRange2 = curRange).push.apply(_curRange2, _toConsumableArray(contextLines(lines)));
990 } else {
991 var _curRange3;
992
993 // end the range and output
994 var contextSize = Math.min(lines.length, options.context);
995
996 (_curRange3 = curRange).push.apply(_curRange3, _toConsumableArray(contextLines(lines.slice(0, contextSize))));
997
998 var hunk = {
999 oldStart: oldRangeStart,
1000 oldLines: oldLine - oldRangeStart + contextSize,
1001 newStart: newRangeStart,
1002 newLines: newLine - newRangeStart + contextSize,
1003 lines: curRange
1004 };
1005
1006 if (i >= diff.length - 2 && lines.length <= options.context) {
1007 // EOF is inside this hunk
1008 var oldEOFNewline = /\n$/.test(oldStr);
1009 var newEOFNewline = /\n$/.test(newStr);
1010 var noNlBeforeAdds = lines.length == 0 && curRange.length > hunk.oldLines;
1011
Tim van der Lippe6d109a92021-02-16 16:00:32 +00001012 if (!oldEOFNewline && noNlBeforeAdds && oldStr.length > 0) {
Peter Marshall0b95ea12020-07-02 18:50:04 +02001013 // special case: old has no eol and no trailing context; no-nl can end up before adds
Tim van der Lippe6d109a92021-02-16 16:00:32 +00001014 // however, if the old file is empty, do not output the no-nl line
Peter Marshall0b95ea12020-07-02 18:50:04 +02001015 curRange.splice(hunk.oldLines, 0, '\\ No newline at end of file');
1016 }
1017
1018 if (!oldEOFNewline && !noNlBeforeAdds || !newEOFNewline) {
1019 curRange.push('\\ No newline at end of file');
1020 }
1021 }
1022
1023 hunks.push(hunk);
1024 oldRangeStart = 0;
1025 newRangeStart = 0;
1026 curRange = [];
1027 }
1028 }
1029
1030 oldLine += lines.length;
1031 newLine += lines.length;
1032 }
1033 };
1034
1035 for (var i = 0; i < diff.length; i++) {
1036 _loop(i);
1037 }
1038
1039 return {
1040 oldFileName: oldFileName,
1041 newFileName: newFileName,
1042 oldHeader: oldHeader,
1043 newHeader: newHeader,
1044 hunks: hunks
1045 };
1046 }
Tim van der Lippe6d109a92021-02-16 16:00:32 +00001047 function formatPatch(diff) {
Peter Marshall0b95ea12020-07-02 18:50:04 +02001048 var ret = [];
1049
Tim van der Lippe6d109a92021-02-16 16:00:32 +00001050 if (diff.oldFileName == diff.newFileName) {
1051 ret.push('Index: ' + diff.oldFileName);
Peter Marshall0b95ea12020-07-02 18:50:04 +02001052 }
1053
1054 ret.push('===================================================================');
1055 ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader));
1056 ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader));
1057
1058 for (var i = 0; i < diff.hunks.length; i++) {
Tim van der Lippe6d109a92021-02-16 16:00:32 +00001059 var hunk = diff.hunks[i]; // Unified Diff Format quirk: If the chunk size is 0,
1060 // the first number is one lower than one would expect.
1061 // https://www.artima.com/weblogs/viewpost.jsp?thread=164293
1062
1063 if (hunk.oldLines === 0) {
1064 hunk.oldStart -= 1;
1065 }
1066
1067 if (hunk.newLines === 0) {
1068 hunk.newStart -= 1;
1069 }
1070
Peter Marshall0b95ea12020-07-02 18:50:04 +02001071 ret.push('@@ -' + hunk.oldStart + ',' + hunk.oldLines + ' +' + hunk.newStart + ',' + hunk.newLines + ' @@');
1072 ret.push.apply(ret, hunk.lines);
1073 }
1074
1075 return ret.join('\n') + '\n';
1076 }
Tim van der Lippe6d109a92021-02-16 16:00:32 +00001077 function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
1078 return formatPatch(structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options));
1079 }
Peter Marshall0b95ea12020-07-02 18:50:04 +02001080 function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) {
1081 return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options);
1082 }
1083
1084 function arrayEqual(a, b) {
1085 if (a.length !== b.length) {
1086 return false;
1087 }
1088
1089 return arrayStartsWith(a, b);
1090 }
1091 function arrayStartsWith(array, start) {
1092 if (start.length > array.length) {
1093 return false;
1094 }
1095
1096 for (var i = 0; i < start.length; i++) {
1097 if (start[i] !== array[i]) {
1098 return false;
1099 }
1100 }
1101
1102 return true;
1103 }
1104
1105 function calcLineCount(hunk) {
1106 var _calcOldNewLineCount = calcOldNewLineCount(hunk.lines),
1107 oldLines = _calcOldNewLineCount.oldLines,
1108 newLines = _calcOldNewLineCount.newLines;
1109
1110 if (oldLines !== undefined) {
1111 hunk.oldLines = oldLines;
1112 } else {
1113 delete hunk.oldLines;
1114 }
1115
1116 if (newLines !== undefined) {
1117 hunk.newLines = newLines;
1118 } else {
1119 delete hunk.newLines;
1120 }
1121 }
1122 function merge(mine, theirs, base) {
1123 mine = loadPatch(mine, base);
1124 theirs = loadPatch(theirs, base);
1125 var ret = {}; // For index we just let it pass through as it doesn't have any necessary meaning.
1126 // Leaving sanity checks on this to the API consumer that may know more about the
1127 // meaning in their own context.
1128
1129 if (mine.index || theirs.index) {
1130 ret.index = mine.index || theirs.index;
1131 }
1132
1133 if (mine.newFileName || theirs.newFileName) {
1134 if (!fileNameChanged(mine)) {
1135 // No header or no change in ours, use theirs (and ours if theirs does not exist)
1136 ret.oldFileName = theirs.oldFileName || mine.oldFileName;
1137 ret.newFileName = theirs.newFileName || mine.newFileName;
1138 ret.oldHeader = theirs.oldHeader || mine.oldHeader;
1139 ret.newHeader = theirs.newHeader || mine.newHeader;
1140 } else if (!fileNameChanged(theirs)) {
1141 // No header or no change in theirs, use ours
1142 ret.oldFileName = mine.oldFileName;
1143 ret.newFileName = mine.newFileName;
1144 ret.oldHeader = mine.oldHeader;
1145 ret.newHeader = mine.newHeader;
1146 } else {
1147 // Both changed... figure it out
1148 ret.oldFileName = selectField(ret, mine.oldFileName, theirs.oldFileName);
1149 ret.newFileName = selectField(ret, mine.newFileName, theirs.newFileName);
1150 ret.oldHeader = selectField(ret, mine.oldHeader, theirs.oldHeader);
1151 ret.newHeader = selectField(ret, mine.newHeader, theirs.newHeader);
1152 }
1153 }
1154
1155 ret.hunks = [];
1156 var mineIndex = 0,
1157 theirsIndex = 0,
1158 mineOffset = 0,
1159 theirsOffset = 0;
1160
1161 while (mineIndex < mine.hunks.length || theirsIndex < theirs.hunks.length) {
1162 var mineCurrent = mine.hunks[mineIndex] || {
1163 oldStart: Infinity
1164 },
1165 theirsCurrent = theirs.hunks[theirsIndex] || {
1166 oldStart: Infinity
1167 };
1168
1169 if (hunkBefore(mineCurrent, theirsCurrent)) {
1170 // This patch does not overlap with any of the others, yay.
1171 ret.hunks.push(cloneHunk(mineCurrent, mineOffset));
1172 mineIndex++;
1173 theirsOffset += mineCurrent.newLines - mineCurrent.oldLines;
1174 } else if (hunkBefore(theirsCurrent, mineCurrent)) {
1175 // This patch does not overlap with any of the others, yay.
1176 ret.hunks.push(cloneHunk(theirsCurrent, theirsOffset));
1177 theirsIndex++;
1178 mineOffset += theirsCurrent.newLines - theirsCurrent.oldLines;
1179 } else {
1180 // Overlap, merge as best we can
1181 var mergedHunk = {
1182 oldStart: Math.min(mineCurrent.oldStart, theirsCurrent.oldStart),
1183 oldLines: 0,
1184 newStart: Math.min(mineCurrent.newStart + mineOffset, theirsCurrent.oldStart + theirsOffset),
1185 newLines: 0,
1186 lines: []
1187 };
1188 mergeLines(mergedHunk, mineCurrent.oldStart, mineCurrent.lines, theirsCurrent.oldStart, theirsCurrent.lines);
1189 theirsIndex++;
1190 mineIndex++;
1191 ret.hunks.push(mergedHunk);
1192 }
1193 }
1194
1195 return ret;
1196 }
1197
1198 function loadPatch(param, base) {
1199 if (typeof param === 'string') {
1200 if (/^@@/m.test(param) || /^Index:/m.test(param)) {
1201 return parsePatch(param)[0];
1202 }
1203
1204 if (!base) {
1205 throw new Error('Must provide a base reference or pass in a patch');
1206 }
1207
1208 return structuredPatch(undefined, undefined, base, param);
1209 }
1210
1211 return param;
1212 }
1213
1214 function fileNameChanged(patch) {
1215 return patch.newFileName && patch.newFileName !== patch.oldFileName;
1216 }
1217
1218 function selectField(index, mine, theirs) {
1219 if (mine === theirs) {
1220 return mine;
1221 } else {
1222 index.conflict = true;
1223 return {
1224 mine: mine,
1225 theirs: theirs
1226 };
1227 }
1228 }
1229
1230 function hunkBefore(test, check) {
1231 return test.oldStart < check.oldStart && test.oldStart + test.oldLines < check.oldStart;
1232 }
1233
1234 function cloneHunk(hunk, offset) {
1235 return {
1236 oldStart: hunk.oldStart,
1237 oldLines: hunk.oldLines,
1238 newStart: hunk.newStart + offset,
1239 newLines: hunk.newLines,
1240 lines: hunk.lines
1241 };
1242 }
1243
1244 function mergeLines(hunk, mineOffset, mineLines, theirOffset, theirLines) {
1245 // This will generally result in a conflicted hunk, but there are cases where the context
1246 // is the only overlap where we can successfully merge the content here.
1247 var mine = {
1248 offset: mineOffset,
1249 lines: mineLines,
1250 index: 0
1251 },
1252 their = {
1253 offset: theirOffset,
1254 lines: theirLines,
1255 index: 0
1256 }; // Handle any leading content
1257
1258 insertLeading(hunk, mine, their);
1259 insertLeading(hunk, their, mine); // Now in the overlap content. Scan through and select the best changes from each.
1260
1261 while (mine.index < mine.lines.length && their.index < their.lines.length) {
1262 var mineCurrent = mine.lines[mine.index],
1263 theirCurrent = their.lines[their.index];
1264
1265 if ((mineCurrent[0] === '-' || mineCurrent[0] === '+') && (theirCurrent[0] === '-' || theirCurrent[0] === '+')) {
1266 // Both modified ...
1267 mutualChange(hunk, mine, their);
1268 } else if (mineCurrent[0] === '+' && theirCurrent[0] === ' ') {
1269 var _hunk$lines;
1270
1271 // Mine inserted
1272 (_hunk$lines = hunk.lines).push.apply(_hunk$lines, _toConsumableArray(collectChange(mine)));
1273 } else if (theirCurrent[0] === '+' && mineCurrent[0] === ' ') {
1274 var _hunk$lines2;
1275
1276 // Theirs inserted
1277 (_hunk$lines2 = hunk.lines).push.apply(_hunk$lines2, _toConsumableArray(collectChange(their)));
1278 } else if (mineCurrent[0] === '-' && theirCurrent[0] === ' ') {
1279 // Mine removed or edited
1280 removal(hunk, mine, their);
1281 } else if (theirCurrent[0] === '-' && mineCurrent[0] === ' ') {
1282 // Their removed or edited
1283 removal(hunk, their, mine, true);
1284 } else if (mineCurrent === theirCurrent) {
1285 // Context identity
1286 hunk.lines.push(mineCurrent);
1287 mine.index++;
1288 their.index++;
1289 } else {
1290 // Context mismatch
1291 conflict(hunk, collectChange(mine), collectChange(their));
1292 }
1293 } // Now push anything that may be remaining
1294
1295
1296 insertTrailing(hunk, mine);
1297 insertTrailing(hunk, their);
1298 calcLineCount(hunk);
1299 }
1300
1301 function mutualChange(hunk, mine, their) {
1302 var myChanges = collectChange(mine),
1303 theirChanges = collectChange(their);
1304
1305 if (allRemoves(myChanges) && allRemoves(theirChanges)) {
1306 // Special case for remove changes that are supersets of one another
1307 if (arrayStartsWith(myChanges, theirChanges) && skipRemoveSuperset(their, myChanges, myChanges.length - theirChanges.length)) {
1308 var _hunk$lines3;
1309
1310 (_hunk$lines3 = hunk.lines).push.apply(_hunk$lines3, _toConsumableArray(myChanges));
1311
1312 return;
1313 } else if (arrayStartsWith(theirChanges, myChanges) && skipRemoveSuperset(mine, theirChanges, theirChanges.length - myChanges.length)) {
1314 var _hunk$lines4;
1315
1316 (_hunk$lines4 = hunk.lines).push.apply(_hunk$lines4, _toConsumableArray(theirChanges));
1317
1318 return;
1319 }
1320 } else if (arrayEqual(myChanges, theirChanges)) {
1321 var _hunk$lines5;
1322
1323 (_hunk$lines5 = hunk.lines).push.apply(_hunk$lines5, _toConsumableArray(myChanges));
1324
1325 return;
1326 }
1327
1328 conflict(hunk, myChanges, theirChanges);
1329 }
1330
1331 function removal(hunk, mine, their, swap) {
1332 var myChanges = collectChange(mine),
1333 theirChanges = collectContext(their, myChanges);
1334
1335 if (theirChanges.merged) {
1336 var _hunk$lines6;
1337
1338 (_hunk$lines6 = hunk.lines).push.apply(_hunk$lines6, _toConsumableArray(theirChanges.merged));
1339 } else {
1340 conflict(hunk, swap ? theirChanges : myChanges, swap ? myChanges : theirChanges);
1341 }
1342 }
1343
1344 function conflict(hunk, mine, their) {
1345 hunk.conflict = true;
1346 hunk.lines.push({
1347 conflict: true,
1348 mine: mine,
1349 theirs: their
1350 });
1351 }
1352
1353 function insertLeading(hunk, insert, their) {
1354 while (insert.offset < their.offset && insert.index < insert.lines.length) {
1355 var line = insert.lines[insert.index++];
1356 hunk.lines.push(line);
1357 insert.offset++;
1358 }
1359 }
1360
1361 function insertTrailing(hunk, insert) {
1362 while (insert.index < insert.lines.length) {
1363 var line = insert.lines[insert.index++];
1364 hunk.lines.push(line);
1365 }
1366 }
1367
1368 function collectChange(state) {
1369 var ret = [],
1370 operation = state.lines[state.index][0];
1371
1372 while (state.index < state.lines.length) {
1373 var line = state.lines[state.index]; // Group additions that are immediately after subtractions and treat them as one "atomic" modify change.
1374
1375 if (operation === '-' && line[0] === '+') {
1376 operation = '+';
1377 }
1378
1379 if (operation === line[0]) {
1380 ret.push(line);
1381 state.index++;
1382 } else {
1383 break;
1384 }
1385 }
1386
1387 return ret;
1388 }
1389
1390 function collectContext(state, matchChanges) {
1391 var changes = [],
1392 merged = [],
1393 matchIndex = 0,
1394 contextChanges = false,
1395 conflicted = false;
1396
1397 while (matchIndex < matchChanges.length && state.index < state.lines.length) {
1398 var change = state.lines[state.index],
1399 match = matchChanges[matchIndex]; // Once we've hit our add, then we are done
1400
1401 if (match[0] === '+') {
1402 break;
1403 }
1404
1405 contextChanges = contextChanges || change[0] !== ' ';
1406 merged.push(match);
1407 matchIndex++; // Consume any additions in the other block as a conflict to attempt
1408 // to pull in the remaining context after this
1409
1410 if (change[0] === '+') {
1411 conflicted = true;
1412
1413 while (change[0] === '+') {
1414 changes.push(change);
1415 change = state.lines[++state.index];
1416 }
1417 }
1418
1419 if (match.substr(1) === change.substr(1)) {
1420 changes.push(change);
1421 state.index++;
1422 } else {
1423 conflicted = true;
1424 }
1425 }
1426
1427 if ((matchChanges[matchIndex] || '')[0] === '+' && contextChanges) {
1428 conflicted = true;
1429 }
1430
1431 if (conflicted) {
1432 return changes;
1433 }
1434
1435 while (matchIndex < matchChanges.length) {
1436 merged.push(matchChanges[matchIndex++]);
1437 }
1438
1439 return {
1440 merged: merged,
1441 changes: changes
1442 };
1443 }
1444
1445 function allRemoves(changes) {
1446 return changes.reduce(function (prev, change) {
1447 return prev && change[0] === '-';
1448 }, true);
1449 }
1450
1451 function skipRemoveSuperset(state, removeChanges, delta) {
1452 for (var i = 0; i < delta; i++) {
1453 var changeContent = removeChanges[removeChanges.length - delta + i].substr(1);
1454
1455 if (state.lines[state.index + i] !== ' ' + changeContent) {
1456 return false;
1457 }
1458 }
1459
1460 state.index += delta;
1461 return true;
1462 }
1463
1464 function calcOldNewLineCount(lines) {
1465 var oldLines = 0;
1466 var newLines = 0;
1467 lines.forEach(function (line) {
1468 if (typeof line !== 'string') {
1469 var myCount = calcOldNewLineCount(line.mine);
1470 var theirCount = calcOldNewLineCount(line.theirs);
1471
1472 if (oldLines !== undefined) {
1473 if (myCount.oldLines === theirCount.oldLines) {
1474 oldLines += myCount.oldLines;
1475 } else {
1476 oldLines = undefined;
1477 }
1478 }
1479
1480 if (newLines !== undefined) {
1481 if (myCount.newLines === theirCount.newLines) {
1482 newLines += myCount.newLines;
1483 } else {
1484 newLines = undefined;
1485 }
1486 }
1487 } else {
1488 if (newLines !== undefined && (line[0] === '+' || line[0] === ' ')) {
1489 newLines++;
1490 }
1491
1492 if (oldLines !== undefined && (line[0] === '-' || line[0] === ' ')) {
1493 oldLines++;
1494 }
1495 }
1496 });
1497 return {
1498 oldLines: oldLines,
1499 newLines: newLines
1500 };
1501 }
1502
1503 // See: http://code.google.com/p/google-diff-match-patch/wiki/API
1504 function convertChangesToDMP(changes) {
1505 var ret = [],
1506 change,
1507 operation;
1508
1509 for (var i = 0; i < changes.length; i++) {
1510 change = changes[i];
1511
1512 if (change.added) {
1513 operation = 1;
1514 } else if (change.removed) {
1515 operation = -1;
1516 } else {
1517 operation = 0;
1518 }
1519
1520 ret.push([operation, change.value]);
1521 }
1522
1523 return ret;
1524 }
1525
1526 function convertChangesToXML(changes) {
1527 var ret = [];
1528
1529 for (var i = 0; i < changes.length; i++) {
1530 var change = changes[i];
1531
1532 if (change.added) {
1533 ret.push('<ins>');
1534 } else if (change.removed) {
1535 ret.push('<del>');
1536 }
1537
1538 ret.push(escapeHTML(change.value));
1539
1540 if (change.added) {
1541 ret.push('</ins>');
1542 } else if (change.removed) {
1543 ret.push('</del>');
1544 }
1545 }
1546
1547 return ret.join('');
1548 }
1549
1550 function escapeHTML(s) {
1551 var n = s;
1552 n = n.replace(/&/g, '&amp;');
1553 n = n.replace(/</g, '&lt;');
1554 n = n.replace(/>/g, '&gt;');
1555 n = n.replace(/"/g, '&quot;');
1556 return n;
1557 }
1558
Peter Marshall0b95ea12020-07-02 18:50:04 +02001559 exports.Diff = Diff;
Peter Marshall0b95ea12020-07-02 18:50:04 +02001560 exports.applyPatch = applyPatch;
1561 exports.applyPatches = applyPatches;
Tim van der Lippe6d109a92021-02-16 16:00:32 +00001562 exports.canonicalize = canonicalize;
Peter Marshall0b95ea12020-07-02 18:50:04 +02001563 exports.convertChangesToDMP = convertChangesToDMP;
1564 exports.convertChangesToXML = convertChangesToXML;
Tim van der Lippe6d109a92021-02-16 16:00:32 +00001565 exports.createPatch = createPatch;
1566 exports.createTwoFilesPatch = createTwoFilesPatch;
1567 exports.diffArrays = diffArrays;
1568 exports.diffChars = diffChars;
1569 exports.diffCss = diffCss;
1570 exports.diffJson = diffJson;
1571 exports.diffLines = diffLines;
1572 exports.diffSentences = diffSentences;
1573 exports.diffTrimmedLines = diffTrimmedLines;
1574 exports.diffWords = diffWords;
1575 exports.diffWordsWithSpace = diffWordsWithSpace;
1576 exports.merge = merge;
1577 exports.parsePatch = parsePatch;
1578 exports.structuredPatch = structuredPatch;
Peter Marshall0b95ea12020-07-02 18:50:04 +02001579
1580 Object.defineProperty(exports, '__esModule', { value: true });
1581
Tim van der Lippe6d109a92021-02-16 16:00:32 +00001582})));