5 Software License Agreement (BSD License)
7 Copyright (c) 2009-2015, Kevin Decker <kpdecker@gmail.com>
11 Redistribution and use of this software in source and binary forms, with or without modification,
12 are permitted provided that the following conditions are met:
14 * Redistributions of source code must retain the above
15 copyright notice, this list of conditions and the
18 * Redistributions in binary form must reproduce the above
19 copyright notice, this list of conditions and the
20 following disclaimer in the documentation and/or other
21 materials provided with the distribution.
23 * Neither the name of Kevin Decker nor the names of its
24 contributors may be used to endorse or promote products
25 derived from this software without specific prior
28 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
29 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
30 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
31 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
34 IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
35 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 (function (global, factory) {
39 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
40 typeof define === 'function' && define.amd ? define(['exports'], factory) :
41 (global = global || self, factory(global.Diff = {}));
42 }(this, function (exports) { 'use strict';
46 diff: function diff(oldString, newString) {
47 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
48 var callback = options.callback;
50 if (typeof options === 'function') {
55 this.options = options;
58 function done(value) {
60 setTimeout(function () {
61 callback(undefined, value);
67 } // Allow subclasses to massage the input prior to running
70 oldString = this.castInput(oldString);
71 newString = this.castInput(newString);
72 oldString = this.removeEmpty(this.tokenize(oldString));
73 newString = this.removeEmpty(this.tokenize(newString));
74 var newLen = newString.length,
75 oldLen = oldString.length;
77 var maxEditLength = newLen + oldLen;
81 }]; // Seed editLength = 0, i.e. the content starts with the same values
83 var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0);
85 if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) {
86 // Identity per the equality and tokenizer
88 value: this.join(newString),
89 count: newString.length
91 } // Main worker method. checks all permutations of a given edit length for acceptance.
94 function execEditLength() {
95 for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) {
96 var basePath = void 0;
98 var addPath = bestPath[diagonalPath - 1],
99 removePath = bestPath[diagonalPath + 1],
100 _oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;
103 // No one else is going to attempt to use this value, clear it
104 bestPath[diagonalPath - 1] = undefined;
107 var canAdd = addPath && addPath.newPos + 1 < newLen,
108 canRemove = removePath && 0 <= _oldPos && _oldPos < oldLen;
110 if (!canAdd && !canRemove) {
111 // If this path is a terminal then prune
112 bestPath[diagonalPath] = undefined;
114 } // Select the diagonal that we want to branch from. We select the prior
115 // path whose position in the new string is the farthest from the origin
116 // and does not pass the bounds of the diff graph
119 if (!canAdd || canRemove && addPath.newPos < removePath.newPos) {
120 basePath = clonePath(removePath);
121 self.pushComponent(basePath.components, undefined, true);
123 basePath = addPath; // No need to clone, we've pulled it from the list
126 self.pushComponent(basePath.components, true, undefined);
129 _oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); // If we have hit the end of both strings, then we are done
131 if (basePath.newPos + 1 >= newLen && _oldPos + 1 >= oldLen) {
132 return done(buildValues(self, basePath.components, newString, oldString, self.useLongestToken));
134 // Otherwise track this path as a potential candidate and continue.
135 bestPath[diagonalPath] = basePath;
140 } // Performs the length of edit iteration. Is a bit fugly as this has to support the
141 // sync and async mode which is never fun. Loops over execEditLength until a value
147 setTimeout(function () {
148 // This should not happen, but we want to be safe.
150 /* istanbul ignore next */
151 if (editLength > maxEditLength) {
155 if (!execEditLength()) {
161 while (editLength <= maxEditLength) {
162 var ret = execEditLength();
170 pushComponent: function pushComponent(components, added, removed) {
171 var last = components[components.length - 1];
173 if (last && last.added === added && last.removed === removed) {
174 // We need to clone here as the component clone operation is just
175 // as shallow array clone
176 components[components.length - 1] = {
177 count: last.count + 1,
189 extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath) {
190 var newLen = newString.length,
191 oldLen = oldString.length,
192 newPos = basePath.newPos,
193 oldPos = newPos - diagonalPath,
196 while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) {
203 basePath.components.push({
208 basePath.newPos = newPos;
211 equals: function equals(left, right) {
212 if (this.options.comparator) {
213 return this.options.comparator(left, right);
215 return left === right || this.options.ignoreCase && left.toLowerCase() === right.toLowerCase();
218 removeEmpty: function removeEmpty(array) {
221 for (var i = 0; i < array.length; i++) {
229 castInput: function castInput(value) {
232 tokenize: function tokenize(value) {
233 return value.split('');
235 join: function join(chars) {
236 return chars.join('');
240 function buildValues(diff, components, newString, oldString, useLongestToken) {
241 var componentPos = 0,
242 componentLen = components.length,
246 for (; componentPos < componentLen; componentPos++) {
247 var component = components[componentPos];
249 if (!component.removed) {
250 if (!component.added && useLongestToken) {
251 var value = newString.slice(newPos, newPos + component.count);
252 value = value.map(function (value, i) {
253 var oldValue = oldString[oldPos + i];
254 return oldValue.length > value.length ? oldValue : value;
256 component.value = diff.join(value);
258 component.value = diff.join(newString.slice(newPos, newPos + component.count));
261 newPos += component.count; // Common case
263 if (!component.added) {
264 oldPos += component.count;
267 component.value = diff.join(oldString.slice(oldPos, oldPos + component.count));
268 oldPos += component.count; // Reverse add and remove so removes are output first to match common convention
269 // The diffing algorithm is tied to add then remove output and this is the simplest
270 // route to get the desired output with minimal overhead.
272 if (componentPos && components[componentPos - 1].added) {
273 var tmp = components[componentPos - 1];
274 components[componentPos - 1] = components[componentPos];
275 components[componentPos] = tmp;
278 } // Special case handle for when one terminal is ignored (i.e. whitespace).
279 // For this case we merge the terminal into the prior string and drop the change.
280 // This is only available for string mode.
283 var lastComponent = components[componentLen - 1];
285 if (componentLen > 1 && typeof lastComponent.value === 'string' && (lastComponent.added || lastComponent.removed) && diff.equals('', lastComponent.value)) {
286 components[componentLen - 2].value += lastComponent.value;
293 function clonePath(path) {
296 components: path.components.slice(0)
300 var characterDiff = new Diff();
301 function diffChars(oldStr, newStr, options) {
302 return characterDiff.diff(oldStr, newStr, options);
305 function generateOptions(options, defaults) {
306 if (typeof options === 'function') {
307 defaults.callback = options;
308 } else if (options) {
309 for (var name in options) {
310 /* istanbul ignore else */
311 if (options.hasOwnProperty(name)) {
312 defaults[name] = options[name];
321 // Ranges and exceptions:
322 // Latin-1 Supplement, 0080–00FF
323 // - U+00D7 × Multiplication sign
324 // - U+00F7 ÷ Division sign
325 // Latin Extended-A, 0100–017F
326 // Latin Extended-B, 0180–024F
327 // IPA Extensions, 0250–02AF
328 // Spacing Modifier Letters, 02B0–02FF
329 // - U+02C7 ˇ ˇ Caron
330 // - U+02D8 ˘ ˘ Breve
331 // - U+02D9 ˙ ˙ Dot Above
332 // - U+02DA ˚ ˚ Ring Above
333 // - U+02DB ˛ ˛ Ogonek
334 // - U+02DC ˜ ˜ Small Tilde
335 // - U+02DD ˝ ˝ Double Acute Accent
336 // Latin Extended Additional, 1E00–1EFF
338 var extendedWordChars = /^[A-Za-z\xC0-\u02C6\u02C8-\u02D7\u02DE-\u02FF\u1E00-\u1EFF]+$/;
339 var reWhitespace = /\S/;
340 var wordDiff = new Diff();
342 wordDiff.equals = function (left, right) {
343 if (this.options.ignoreCase) {
344 left = left.toLowerCase();
345 right = right.toLowerCase();
348 return left === right || this.options.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right);
351 wordDiff.tokenize = function (value) {
352 var tokens = value.split(/(\s+|[()[\]{}'"]|\b)/); // Join the boundary splits that we do not consider to be boundaries. This is primarily the extended Latin character set.
354 for (var i = 0; i < tokens.length - 1; i++) {
355 // If we have an empty string in the next field and we have only word chars before and after, merge
356 if (!tokens[i + 1] && tokens[i + 2] && extendedWordChars.test(tokens[i]) && extendedWordChars.test(tokens[i + 2])) {
357 tokens[i] += tokens[i + 2];
358 tokens.splice(i + 1, 2);
366 function diffWords(oldStr, newStr, options) {
367 options = generateOptions(options, {
368 ignoreWhitespace: true
370 return wordDiff.diff(oldStr, newStr, options);
372 function diffWordsWithSpace(oldStr, newStr, options) {
373 return wordDiff.diff(oldStr, newStr, options);
376 var lineDiff = new Diff();
378 lineDiff.tokenize = function (value) {
380 linesAndNewlines = value.split(/(\n|\r\n)/); // Ignore the final empty token that occurs if the string ends with a new line
382 if (!linesAndNewlines[linesAndNewlines.length - 1]) {
383 linesAndNewlines.pop();
384 } // Merge the content and line separators into single tokens
387 for (var i = 0; i < linesAndNewlines.length; i++) {
388 var line = linesAndNewlines[i];
390 if (i % 2 && !this.options.newlineIsToken) {
391 retLines[retLines.length - 1] += line;
393 if (this.options.ignoreWhitespace) {
404 function diffLines(oldStr, newStr, callback) {
405 return lineDiff.diff(oldStr, newStr, callback);
407 function diffTrimmedLines(oldStr, newStr, callback) {
408 var options = generateOptions(callback, {
409 ignoreWhitespace: true
411 return lineDiff.diff(oldStr, newStr, options);
414 var sentenceDiff = new Diff();
416 sentenceDiff.tokenize = function (value) {
417 return value.split(/(\S.+?[.!?])(?=\s+|$)/);
420 function diffSentences(oldStr, newStr, callback) {
421 return sentenceDiff.diff(oldStr, newStr, callback);
424 var cssDiff = new Diff();
426 cssDiff.tokenize = function (value) {
427 return value.split(/([{}:;,]|\s+)/);
430 function diffCss(oldStr, newStr, callback) {
431 return cssDiff.diff(oldStr, newStr, callback);
434 function _typeof(obj) {
435 if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
436 _typeof = function (obj) {
440 _typeof = function (obj) {
441 return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
448 function _toConsumableArray(arr) {
449 return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread();
452 function _arrayWithoutHoles(arr) {
453 if (Array.isArray(arr)) {
454 for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
460 function _iterableToArray(iter) {
461 if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter);
464 function _nonIterableSpread() {
465 throw new TypeError("Invalid attempt to spread non-iterable instance");
468 var objectPrototypeToString = Object.prototype.toString;
469 var jsonDiff = new Diff(); // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a
470 // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output:
472 jsonDiff.useLongestToken = true;
473 jsonDiff.tokenize = lineDiff.tokenize;
475 jsonDiff.castInput = function (value) {
476 var _this$options = this.options,
477 undefinedReplacement = _this$options.undefinedReplacement,
478 _this$options$stringi = _this$options.stringifyReplacer,
479 stringifyReplacer = _this$options$stringi === void 0 ? function (k, v) {
480 return typeof v === 'undefined' ? undefinedReplacement : v;
481 } : _this$options$stringi;
482 return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' ');
485 jsonDiff.equals = function (left, right) {
486 return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1'));
489 function diffJson(oldObj, newObj, options) {
490 return jsonDiff.diff(oldObj, newObj, options);
491 } // This function handles the presence of circular references by bailing out when encountering an
492 // object that is already on the "stack" of items being processed. Accepts an optional replacer
494 function canonicalize(obj, stack, replacementStack, replacer, key) {
496 replacementStack = replacementStack || [];
499 obj = replacer(key, obj);
504 for (i = 0; i < stack.length; i += 1) {
505 if (stack[i] === obj) {
506 return replacementStack[i];
510 var canonicalizedObj;
512 if ('[object Array]' === objectPrototypeToString.call(obj)) {
514 canonicalizedObj = new Array(obj.length);
515 replacementStack.push(canonicalizedObj);
517 for (i = 0; i < obj.length; i += 1) {
518 canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key);
522 replacementStack.pop();
523 return canonicalizedObj;
526 if (obj && obj.toJSON) {
530 if (_typeof(obj) === 'object' && obj !== null) {
532 canonicalizedObj = {};
533 replacementStack.push(canonicalizedObj);
539 /* istanbul ignore else */
540 if (obj.hasOwnProperty(_key)) {
541 sortedKeys.push(_key);
547 for (i = 0; i < sortedKeys.length; i += 1) {
548 _key = sortedKeys[i];
549 canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key);
553 replacementStack.pop();
555 canonicalizedObj = obj;
558 return canonicalizedObj;
561 var arrayDiff = new Diff();
563 arrayDiff.tokenize = function (value) {
564 return value.slice();
567 arrayDiff.join = arrayDiff.removeEmpty = function (value) {
571 function diffArrays(oldArr, newArr, callback) {
572 return arrayDiff.diff(oldArr, newArr, callback);
575 function parsePatch(uniDiff) {
576 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
577 var diffstr = uniDiff.split(/\r\n|[\n\v\f\r\x85]/),
578 delimiters = uniDiff.match(/\r\n|[\n\v\f\r\x85]/g) || [],
582 function parseIndex() {
584 list.push(index); // Parse diff metadata
586 while (i < diffstr.length) {
587 var line = diffstr[i]; // File header found, end parsing diff metadata
589 if (/^(\-\-\-|\+\+\+|@@)\s/.test(line)) {
594 var header = /^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/.exec(line);
597 index.index = header[1];
601 } // Parse file headers if they are defined. Unified diff requires them, but
602 // there's no technical issues to have an isolated hunk without file header
605 parseFileHeader(index);
606 parseFileHeader(index); // Parse hunks
610 while (i < diffstr.length) {
611 var _line = diffstr[i];
613 if (/^(Index:|diff|\-\-\-|\+\+\+)\s/.test(_line)) {
615 } else if (/^@@/.test(_line)) {
616 index.hunks.push(parseHunk());
617 } else if (_line && options.strict) {
618 // Ignore unexpected content unless in strict mode
619 throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(_line));
624 } // Parses the --- and +++ headers, if none are found, no lines
628 function parseFileHeader(index) {
629 var fileHeader = /^(---|\+\+\+)\s+(.*)$/.exec(diffstr[i]);
632 var keyPrefix = fileHeader[1] === '---' ? 'old' : 'new';
633 var data = fileHeader[2].split('\t', 2);
634 var fileName = data[0].replace(/\\\\/g, '\\');
636 if (/^".*"$/.test(fileName)) {
637 fileName = fileName.substr(1, fileName.length - 2);
640 index[keyPrefix + 'FileName'] = fileName;
641 index[keyPrefix + 'Header'] = (data[1] || '').trim();
645 // This assumes that we are at the start of a hunk.
648 function parseHunk() {
649 var chunkHeaderIndex = i,
650 chunkHeaderLine = diffstr[i++],
651 chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
653 oldStart: +chunkHeader[1],
654 oldLines: +chunkHeader[2] || 1,
655 newStart: +chunkHeader[3],
656 newLines: +chunkHeader[4] || 1,
663 for (; i < diffstr.length; i++) {
664 // Lines starting with '---' could be mistaken for the "remove line" operation
665 // But they could be the header for the next file. Therefore prune such cases out.
666 if (diffstr[i].indexOf('--- ') === 0 && i + 2 < diffstr.length && diffstr[i + 1].indexOf('+++ ') === 0 && diffstr[i + 2].indexOf('@@') === 0) {
670 var operation = diffstr[i].length == 0 && i != diffstr.length - 1 ? ' ' : diffstr[i][0];
672 if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') {
673 hunk.lines.push(diffstr[i]);
674 hunk.linedelimiters.push(delimiters[i] || '\n');
676 if (operation === '+') {
678 } else if (operation === '-') {
680 } else if (operation === ' ') {
687 } // Handle the empty block count case
690 if (!addCount && hunk.newLines === 1) {
694 if (!removeCount && hunk.oldLines === 1) {
696 } // Perform optional sanity checking
699 if (options.strict) {
700 if (addCount !== hunk.newLines) {
701 throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
704 if (removeCount !== hunk.oldLines) {
705 throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1));
712 while (i < diffstr.length) {
719 // Iterator that traverses in the range of [min, max], stepping
720 // by distance from a given start position. I.e. for [0, 4], with
721 // start of 2, this will iterate 2, 3, 1, 4, 0.
722 function distanceIterator (start, minLine, maxLine) {
723 var wantForward = true,
724 backwardExhausted = false,
725 forwardExhausted = false,
727 return function iterator() {
728 if (wantForward && !forwardExhausted) {
729 if (backwardExhausted) {
733 } // Check if trying to fit beyond text length, and if not, check it fits
734 // after offset location (or desired location on first iteration)
737 if (start + localOffset <= maxLine) {
741 forwardExhausted = true;
744 if (!backwardExhausted) {
745 if (!forwardExhausted) {
747 } // Check if trying to fit before text beginning, and if not, check it fits
748 // before offset location
751 if (minLine <= start - localOffset) {
752 return -localOffset++;
755 backwardExhausted = true;
757 } // We tried to fit hunk before text beginning and beyond text length, then
758 // hunk can't fit on the text. Return undefined
763 function applyPatch(source, uniDiff) {
764 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
766 if (typeof uniDiff === 'string') {
767 uniDiff = parsePatch(uniDiff);
770 if (Array.isArray(uniDiff)) {
771 if (uniDiff.length > 1) {
772 throw new Error('applyPatch only works with a single input.');
775 uniDiff = uniDiff[0];
776 } // Apply the diff to the input
779 var lines = source.split(/\r\n|[\n\v\f\r\x85]/),
780 delimiters = source.match(/\r\n|[\n\v\f\r\x85]/g) || [],
781 hunks = uniDiff.hunks,
782 compareLine = options.compareLine || function (lineNumber, line, operation, patchContent) {
783 return line === patchContent;
786 fuzzFactor = options.fuzzFactor || 0,
792 * Checks if the hunk exactly fits on the provided location
796 function hunkFits(hunk, toPos) {
797 for (var j = 0; j < hunk.lines.length; j++) {
798 var line = hunk.lines[j],
799 operation = line.length > 0 ? line[0] : ' ',
800 content = line.length > 0 ? line.substr(1) : line;
802 if (operation === ' ' || operation === '-') {
803 // Context sanity check
804 if (!compareLine(toPos + 1, lines[toPos], operation, content)) {
807 if (errorCount > fuzzFactor) {
817 } // Search best fit offsets for each hunk based on the previous ones
820 for (var i = 0; i < hunks.length; i++) {
822 maxLine = lines.length - hunk.oldLines,
824 toPos = offset + hunk.oldStart - 1;
825 var iterator = distanceIterator(toPos, minLine, maxLine);
827 for (; localOffset !== undefined; localOffset = iterator()) {
828 if (hunkFits(hunk, toPos + localOffset)) {
829 hunk.offset = offset += localOffset;
834 if (localOffset === undefined) {
836 } // Set lower text limit to end of the current hunk, so next ones don't try
837 // to fit over already patched text
840 minLine = hunk.offset + hunk.oldStart + hunk.oldLines;
841 } // Apply patch hunks
846 for (var _i = 0; _i < hunks.length; _i++) {
847 var _hunk = hunks[_i],
848 _toPos = _hunk.oldStart + _hunk.offset + diffOffset - 1;
850 diffOffset += _hunk.newLines - _hunk.oldLines;
853 // Creating a new file
857 for (var j = 0; j < _hunk.lines.length; j++) {
858 var line = _hunk.lines[j],
859 operation = line.length > 0 ? line[0] : ' ',
860 content = line.length > 0 ? line.substr(1) : line,
861 delimiter = _hunk.linedelimiters[j];
863 if (operation === ' ') {
865 } else if (operation === '-') {
866 lines.splice(_toPos, 1);
867 delimiters.splice(_toPos, 1);
868 /* istanbul ignore else */
869 } else if (operation === '+') {
870 lines.splice(_toPos, 0, content);
871 delimiters.splice(_toPos, 0, delimiter);
873 } else if (operation === '\\') {
874 var previousOperation = _hunk.lines[j - 1] ? _hunk.lines[j - 1][0] : null;
876 if (previousOperation === '+') {
878 } else if (previousOperation === '-') {
883 } // Handle EOFNL insertion/removal
887 while (!lines[lines.length - 1]) {
891 } else if (addEOFNL) {
893 delimiters.push('\n');
896 for (var _k = 0; _k < lines.length - 1; _k++) {
897 lines[_k] = lines[_k] + delimiters[_k];
900 return lines.join('');
901 } // Wrapper that supports multiple file patches via callbacks.
903 function applyPatches(uniDiff, options) {
904 if (typeof uniDiff === 'string') {
905 uniDiff = parsePatch(uniDiff);
908 var currentIndex = 0;
910 function processIndex() {
911 var index = uniDiff[currentIndex++];
914 return options.complete();
917 options.loadFile(index, function (err, data) {
919 return options.complete(err);
922 var updatedContent = applyPatch(data, index, options);
923 options.patched(index, updatedContent, function (err) {
925 return options.complete(err);
936 function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
941 if (typeof options.context === 'undefined') {
945 var diff = diffLines(oldStr, newStr, options);
949 }); // Append an empty value to make cleanup easier
951 function contextLines(lines) {
952 return lines.map(function (entry) {
958 var oldRangeStart = 0,
964 var _loop = function _loop(i) {
965 var current = diff[i],
966 lines = current.lines || current.value.replace(/\n$/, '').split('\n');
967 current.lines = lines;
969 if (current.added || current.removed) {
972 // If we have previous context, start with that
973 if (!oldRangeStart) {
974 var prev = diff[i - 1];
975 oldRangeStart = oldLine;
976 newRangeStart = newLine;
979 curRange = options.context > 0 ? contextLines(prev.lines.slice(-options.context)) : [];
980 oldRangeStart -= curRange.length;
981 newRangeStart -= curRange.length;
983 } // Output our changes
986 (_curRange = curRange).push.apply(_curRange, _toConsumableArray(lines.map(function (entry) {
987 return (current.added ? '+' : '-') + entry;
988 }))); // Track the updated file position
992 newLine += lines.length;
994 oldLine += lines.length;
997 // Identical context lines. Track line changes
999 // Close out any changes that have been output (or join overlapping)
1000 if (lines.length <= options.context * 2 && i < diff.length - 2) {
1004 (_curRange2 = curRange).push.apply(_curRange2, _toConsumableArray(contextLines(lines)));
1008 // end the range and output
1009 var contextSize = Math.min(lines.length, options.context);
1011 (_curRange3 = curRange).push.apply(_curRange3, _toConsumableArray(contextLines(lines.slice(0, contextSize))));
1014 oldStart: oldRangeStart,
1015 oldLines: oldLine - oldRangeStart + contextSize,
1016 newStart: newRangeStart,
1017 newLines: newLine - newRangeStart + contextSize,
1021 if (i >= diff.length - 2 && lines.length <= options.context) {
1022 // EOF is inside this hunk
1023 var oldEOFNewline = /\n$/.test(oldStr);
1024 var newEOFNewline = /\n$/.test(newStr);
1025 var noNlBeforeAdds = lines.length == 0 && curRange.length > hunk.oldLines;
1027 if (!oldEOFNewline && noNlBeforeAdds) {
1028 // special case: old has no eol and no trailing context; no-nl can end up before adds
1029 curRange.splice(hunk.oldLines, 0, '\\ No newline at end of file');
1032 if (!oldEOFNewline && !noNlBeforeAdds || !newEOFNewline) {
1033 curRange.push('\\ No newline at end of file');
1044 oldLine += lines.length;
1045 newLine += lines.length;
1049 for (var i = 0; i < diff.length; i++) {
1054 oldFileName: oldFileName,
1055 newFileName: newFileName,
1056 oldHeader: oldHeader,
1057 newHeader: newHeader,
1061 function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
1062 var diff = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options);
1065 if (oldFileName == newFileName) {
1066 ret.push('Index: ' + oldFileName);
1069 ret.push('===================================================================');
1070 ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader));
1071 ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader));
1073 for (var i = 0; i < diff.hunks.length; i++) {
1074 var hunk = diff.hunks[i];
1075 ret.push('@@ -' + hunk.oldStart + ',' + hunk.oldLines + ' +' + hunk.newStart + ',' + hunk.newLines + ' @@');
1076 ret.push.apply(ret, hunk.lines);
1079 return ret.join('\n') + '\n';
1081 function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) {
1082 return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options);
1085 function arrayEqual(a, b) {
1086 if (a.length !== b.length) {
1090 return arrayStartsWith(a, b);
1092 function arrayStartsWith(array, start) {
1093 if (start.length > array.length) {
1097 for (var i = 0; i < start.length; i++) {
1098 if (start[i] !== array[i]) {
1106 function calcLineCount(hunk) {
1107 var _calcOldNewLineCount = calcOldNewLineCount(hunk.lines),
1108 oldLines = _calcOldNewLineCount.oldLines,
1109 newLines = _calcOldNewLineCount.newLines;
1111 if (oldLines !== undefined) {
1112 hunk.oldLines = oldLines;
1114 delete hunk.oldLines;
1117 if (newLines !== undefined) {
1118 hunk.newLines = newLines;
1120 delete hunk.newLines;
1123 function merge(mine, theirs, base) {
1124 mine = loadPatch(mine, base);
1125 theirs = loadPatch(theirs, base);
1126 var ret = {}; // For index we just let it pass through as it doesn't have any necessary meaning.
1127 // Leaving sanity checks on this to the API consumer that may know more about the
1128 // meaning in their own context.
1130 if (mine.index || theirs.index) {
1131 ret.index = mine.index || theirs.index;
1134 if (mine.newFileName || theirs.newFileName) {
1135 if (!fileNameChanged(mine)) {
1136 // No header or no change in ours, use theirs (and ours if theirs does not exist)
1137 ret.oldFileName = theirs.oldFileName || mine.oldFileName;
1138 ret.newFileName = theirs.newFileName || mine.newFileName;
1139 ret.oldHeader = theirs.oldHeader || mine.oldHeader;
1140 ret.newHeader = theirs.newHeader || mine.newHeader;
1141 } else if (!fileNameChanged(theirs)) {
1142 // No header or no change in theirs, use ours
1143 ret.oldFileName = mine.oldFileName;
1144 ret.newFileName = mine.newFileName;
1145 ret.oldHeader = mine.oldHeader;
1146 ret.newHeader = mine.newHeader;
1148 // Both changed... figure it out
1149 ret.oldFileName = selectField(ret, mine.oldFileName, theirs.oldFileName);
1150 ret.newFileName = selectField(ret, mine.newFileName, theirs.newFileName);
1151 ret.oldHeader = selectField(ret, mine.oldHeader, theirs.oldHeader);
1152 ret.newHeader = selectField(ret, mine.newHeader, theirs.newHeader);
1162 while (mineIndex < mine.hunks.length || theirsIndex < theirs.hunks.length) {
1163 var mineCurrent = mine.hunks[mineIndex] || {
1166 theirsCurrent = theirs.hunks[theirsIndex] || {
1170 if (hunkBefore(mineCurrent, theirsCurrent)) {
1171 // This patch does not overlap with any of the others, yay.
1172 ret.hunks.push(cloneHunk(mineCurrent, mineOffset));
1174 theirsOffset += mineCurrent.newLines - mineCurrent.oldLines;
1175 } else if (hunkBefore(theirsCurrent, mineCurrent)) {
1176 // This patch does not overlap with any of the others, yay.
1177 ret.hunks.push(cloneHunk(theirsCurrent, theirsOffset));
1179 mineOffset += theirsCurrent.newLines - theirsCurrent.oldLines;
1181 // Overlap, merge as best we can
1183 oldStart: Math.min(mineCurrent.oldStart, theirsCurrent.oldStart),
1185 newStart: Math.min(mineCurrent.newStart + mineOffset, theirsCurrent.oldStart + theirsOffset),
1189 mergeLines(mergedHunk, mineCurrent.oldStart, mineCurrent.lines, theirsCurrent.oldStart, theirsCurrent.lines);
1192 ret.hunks.push(mergedHunk);
1199 function loadPatch(param, base) {
1200 if (typeof param === 'string') {
1201 if (/^@@/m.test(param) || /^Index:/m.test(param)) {
1202 return parsePatch(param)[0];
1206 throw new Error('Must provide a base reference or pass in a patch');
1209 return structuredPatch(undefined, undefined, base, param);
1215 function fileNameChanged(patch) {
1216 return patch.newFileName && patch.newFileName !== patch.oldFileName;
1219 function selectField(index, mine, theirs) {
1220 if (mine === theirs) {
1223 index.conflict = true;
1231 function hunkBefore(test, check) {
1232 return test.oldStart < check.oldStart && test.oldStart + test.oldLines < check.oldStart;
1235 function cloneHunk(hunk, offset) {
1237 oldStart: hunk.oldStart,
1238 oldLines: hunk.oldLines,
1239 newStart: hunk.newStart + offset,
1240 newLines: hunk.newLines,
1245 function mergeLines(hunk, mineOffset, mineLines, theirOffset, theirLines) {
1246 // This will generally result in a conflicted hunk, but there are cases where the context
1247 // is the only overlap where we can successfully merge the content here.
1254 offset: theirOffset,
1257 }; // Handle any leading content
1259 insertLeading(hunk, mine, their);
1260 insertLeading(hunk, their, mine); // Now in the overlap content. Scan through and select the best changes from each.
1262 while (mine.index < mine.lines.length && their.index < their.lines.length) {
1263 var mineCurrent = mine.lines[mine.index],
1264 theirCurrent = their.lines[their.index];
1266 if ((mineCurrent[0] === '-' || mineCurrent[0] === '+') && (theirCurrent[0] === '-' || theirCurrent[0] === '+')) {
1267 // Both modified ...
1268 mutualChange(hunk, mine, their);
1269 } else if (mineCurrent[0] === '+' && theirCurrent[0] === ' ') {
1273 (_hunk$lines = hunk.lines).push.apply(_hunk$lines, _toConsumableArray(collectChange(mine)));
1274 } else if (theirCurrent[0] === '+' && mineCurrent[0] === ' ') {
1278 (_hunk$lines2 = hunk.lines).push.apply(_hunk$lines2, _toConsumableArray(collectChange(their)));
1279 } else if (mineCurrent[0] === '-' && theirCurrent[0] === ' ') {
1280 // Mine removed or edited
1281 removal(hunk, mine, their);
1282 } else if (theirCurrent[0] === '-' && mineCurrent[0] === ' ') {
1283 // Their removed or edited
1284 removal(hunk, their, mine, true);
1285 } else if (mineCurrent === theirCurrent) {
1287 hunk.lines.push(mineCurrent);
1292 conflict(hunk, collectChange(mine), collectChange(their));
1294 } // Now push anything that may be remaining
1297 insertTrailing(hunk, mine);
1298 insertTrailing(hunk, their);
1299 calcLineCount(hunk);
1302 function mutualChange(hunk, mine, their) {
1303 var myChanges = collectChange(mine),
1304 theirChanges = collectChange(their);
1306 if (allRemoves(myChanges) && allRemoves(theirChanges)) {
1307 // Special case for remove changes that are supersets of one another
1308 if (arrayStartsWith(myChanges, theirChanges) && skipRemoveSuperset(their, myChanges, myChanges.length - theirChanges.length)) {
1311 (_hunk$lines3 = hunk.lines).push.apply(_hunk$lines3, _toConsumableArray(myChanges));
1314 } else if (arrayStartsWith(theirChanges, myChanges) && skipRemoveSuperset(mine, theirChanges, theirChanges.length - myChanges.length)) {
1317 (_hunk$lines4 = hunk.lines).push.apply(_hunk$lines4, _toConsumableArray(theirChanges));
1321 } else if (arrayEqual(myChanges, theirChanges)) {
1324 (_hunk$lines5 = hunk.lines).push.apply(_hunk$lines5, _toConsumableArray(myChanges));
1329 conflict(hunk, myChanges, theirChanges);
1332 function removal(hunk, mine, their, swap) {
1333 var myChanges = collectChange(mine),
1334 theirChanges = collectContext(their, myChanges);
1336 if (theirChanges.merged) {
1339 (_hunk$lines6 = hunk.lines).push.apply(_hunk$lines6, _toConsumableArray(theirChanges.merged));
1341 conflict(hunk, swap ? theirChanges : myChanges, swap ? myChanges : theirChanges);
1345 function conflict(hunk, mine, their) {
1346 hunk.conflict = true;
1354 function insertLeading(hunk, insert, their) {
1355 while (insert.offset < their.offset && insert.index < insert.lines.length) {
1356 var line = insert.lines[insert.index++];
1357 hunk.lines.push(line);
1362 function insertTrailing(hunk, insert) {
1363 while (insert.index < insert.lines.length) {
1364 var line = insert.lines[insert.index++];
1365 hunk.lines.push(line);
1369 function collectChange(state) {
1371 operation = state.lines[state.index][0];
1373 while (state.index < state.lines.length) {
1374 var line = state.lines[state.index]; // Group additions that are immediately after subtractions and treat them as one "atomic" modify change.
1376 if (operation === '-' && line[0] === '+') {
1380 if (operation === line[0]) {
1391 function collectContext(state, matchChanges) {
1395 contextChanges = false,
1398 while (matchIndex < matchChanges.length && state.index < state.lines.length) {
1399 var change = state.lines[state.index],
1400 match = matchChanges[matchIndex]; // Once we've hit our add, then we are done
1402 if (match[0] === '+') {
1406 contextChanges = contextChanges || change[0] !== ' ';
1408 matchIndex++; // Consume any additions in the other block as a conflict to attempt
1409 // to pull in the remaining context after this
1411 if (change[0] === '+') {
1414 while (change[0] === '+') {
1415 changes.push(change);
1416 change = state.lines[++state.index];
1420 if (match.substr(1) === change.substr(1)) {
1421 changes.push(change);
1428 if ((matchChanges[matchIndex] || '')[0] === '+' && contextChanges) {
1436 while (matchIndex < matchChanges.length) {
1437 merged.push(matchChanges[matchIndex++]);
1446 function allRemoves(changes) {
1447 return changes.reduce(function (prev, change) {
1448 return prev && change[0] === '-';
1452 function skipRemoveSuperset(state, removeChanges, delta) {
1453 for (var i = 0; i < delta; i++) {
1454 var changeContent = removeChanges[removeChanges.length - delta + i].substr(1);
1456 if (state.lines[state.index + i] !== ' ' + changeContent) {
1461 state.index += delta;
1465 function calcOldNewLineCount(lines) {
1468 lines.forEach(function (line) {
1469 if (typeof line !== 'string') {
1470 var myCount = calcOldNewLineCount(line.mine);
1471 var theirCount = calcOldNewLineCount(line.theirs);
1473 if (oldLines !== undefined) {
1474 if (myCount.oldLines === theirCount.oldLines) {
1475 oldLines += myCount.oldLines;
1477 oldLines = undefined;
1481 if (newLines !== undefined) {
1482 if (myCount.newLines === theirCount.newLines) {
1483 newLines += myCount.newLines;
1485 newLines = undefined;
1489 if (newLines !== undefined && (line[0] === '+' || line[0] === ' ')) {
1493 if (oldLines !== undefined && (line[0] === '-' || line[0] === ' ')) {
1504 // See: http://code.google.com/p/google-diff-match-patch/wiki/API
1505 function convertChangesToDMP(changes) {
1510 for (var i = 0; i < changes.length; i++) {
1511 change = changes[i];
1515 } else if (change.removed) {
1521 ret.push([operation, change.value]);
1527 function convertChangesToXML(changes) {
1530 for (var i = 0; i < changes.length; i++) {
1531 var change = changes[i];
1535 } else if (change.removed) {
1539 ret.push(escapeHTML(change.value));
1543 } else if (change.removed) {
1548 return ret.join('');
1551 function escapeHTML(s) {
1553 n = n.replace(/&/g, '&');
1554 n = n.replace(/</g, '<');
1555 n = n.replace(/>/g, '>');
1556 n = n.replace(/"/g, '"');
1560 /* See LICENSE file for terms of use */
1562 exports.Diff = Diff;
1563 exports.diffChars = diffChars;
1564 exports.diffWords = diffWords;
1565 exports.diffWordsWithSpace = diffWordsWithSpace;
1566 exports.diffLines = diffLines;
1567 exports.diffTrimmedLines = diffTrimmedLines;
1568 exports.diffSentences = diffSentences;
1569 exports.diffCss = diffCss;
1570 exports.diffJson = diffJson;
1571 exports.diffArrays = diffArrays;
1572 exports.structuredPatch = structuredPatch;
1573 exports.createTwoFilesPatch = createTwoFilesPatch;
1574 exports.createPatch = createPatch;
1575 exports.applyPatch = applyPatch;
1576 exports.applyPatches = applyPatches;
1577 exports.parsePatch = parsePatch;
1578 exports.merge = merge;
1579 exports.convertChangesToDMP = convertChangesToDMP;
1580 exports.convertChangesToXML = convertChangesToXML;
1581 exports.canonicalize = canonicalize;
1583 Object.defineProperty(exports, '__esModule', { value: true });