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