From b75f9fd369b67825e2b27f36c4dae7db9216ef1e Mon Sep 17 00:00:00 2001 From: Raphael Geissert Date: Sun, 3 May 2015 17:21:56 +0200 Subject: [PATCH 001/436] Add createTwoFilesPatch, to be able to use two file names --- diff.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/diff.js b/diff.js index 22040858..881edf0e 100644 --- a/diff.js +++ b/diff.js @@ -370,13 +370,15 @@ ); }, - createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { + createTwoFilesPatch: function(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader) { var ret = []; - ret.push('Index: ' + fileName); + if (oldFileName == newFileName) { + ret.push('Index: ' + oldFileName); + } ret.push('==================================================================='); - ret.push('--- ' + fileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader)); - ret.push('+++ ' + fileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader)); + ret.push('--- ' + oldFileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader)); + ret.push('+++ ' + newFileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader)); var diff = LineDiff.diff(oldStr, newStr); if (!diff[diff.length-1].value) { @@ -455,6 +457,10 @@ return ret.join('\n') + '\n'; }, + createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { + return JsDiff.createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader); + }, + applyPatch: function(oldStr, uniDiff) { var diffstr = uniDiff.split('\n'); var diff = []; From beede57817dabd12c735c42222ce65716afe8a08 Mon Sep 17 00:00:00 2001 From: Raphael Geissert Date: Sun, 3 May 2015 17:30:55 +0200 Subject: [PATCH 002/436] Document the new createTwoFilesPatch --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ffaa0607..b867e19a 100644 --- a/README.md +++ b/README.md @@ -53,15 +53,20 @@ or Returns a list of change objects (See below). -* `JsDiff.createPatch(fileName, oldStr, newStr, oldHeader, newHeader)` - creates a unified diff patch. +* `JsDiff.createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader)` - creates a unified diff patch. Parameters: - * `fileName` : String to be output in the filename sections of the patch + * `oldFileName` : String to be output in the filename section of the patch for the removals + * `newFileName` : String to be output in the filename section of the patch for the additions * `oldStr` : Original string value * `newStr` : New string value * `oldHeader` : Additional information to include in the old file header * `newHeader` : Additional information to include in thew new file header +* `JsDiff.createPatch(fileName, oldStr, newStr, oldHeader, newHeader)` - creates a unified diff patch. + + Just like JsDiff.createTwoFilesPatch, but with oldFileName being equal to newFileName. + * `JsDiff.applyPatch(oldStr, diffStr)` - applies a unified diff patch. Return a string containing new version of provided data. From 86ba741b52fa9d5a4b17344284f96bd3fa44b228 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 4 May 2015 13:54:16 -0500 Subject: [PATCH 003/436] Update dev dependencies to latest --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 6b5f1c05..d748af96 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,10 @@ }, "dependencies": {}, "devDependencies": { - "colors": "~0.6.2", + "colors": "^1.1.0", "istanbul": "^0.3.2", - "mocha": "~1.6", - "should": "~1.2" + "mocha": "^2.2.4", + "should": "^6.0.1" }, "optionalDependencies": {}, "files": [ From a04889dbf5df6ad970d17bce74a6927cbddce903 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 4 May 2015 18:05:01 -0500 Subject: [PATCH 004/436] Simple style cleanup --- diff.js | 131 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 73 insertions(+), 58 deletions(-) diff --git a/diff.js b/diff.js index 881edf0e..a1016e7b 100644 --- a/diff.js +++ b/diff.js @@ -15,6 +15,7 @@ * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 */ (function(global, undefined) { + var objectPrototypeToString = Object.prototype.toString; var JsDiff = (function() { /*jshint maxparams: 5*/ @@ -88,9 +89,9 @@ return components; } - var Diff = function(ignoreWhitespace) { + function Diff(ignoreWhitespace) { this.ignoreWhitespace = ignoreWhitespace; - }; + } Diff.prototype = { diff: function(oldString, newString, callback) { var self = this; @@ -119,6 +120,7 @@ oldString = this.tokenize(oldString); var newLen = newString.length, oldLen = oldString.length; + var editLength = 1; var maxEditLength = newLen + oldLen; var bestPath = [{ newPos: -1, components: [] }]; @@ -131,17 +133,17 @@ // Main worker method. checks all permutations of a given edit length for acceptance. function execEditLength() { - for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) { + for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) { var basePath; - var addPath = bestPath[diagonalPath-1], - removePath = bestPath[diagonalPath+1]; - oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; + var addPath = bestPath[diagonalPath - 1], + removePath = bestPath[diagonalPath + 1], + oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; if (addPath) { // No one else is going to attempt to use this value, clear it - bestPath[diagonalPath-1] = undefined; + bestPath[diagonalPath - 1] = undefined; } - var canAdd = addPath && addPath.newPos+1 < newLen; + var canAdd = addPath && addPath.newPos + 1 < newLen; var canRemove = removePath && 0 <= oldPos && oldPos < oldLen; if (!canAdd && !canRemove) { // If this path is a terminal then prune @@ -161,10 +163,10 @@ self.pushComponent(basePath.components, true, undefined); } - var oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); + oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); // If we have hit the end of both strings, then we are done - if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) { + if (basePath.newPos + 1 >= newLen && oldPos + 1 >= oldLen) { return done(buildValues(basePath.components, newString, oldString, self.useLongestToken)); } else { // Otherwise track this path as a potential candidate and continue. @@ -178,7 +180,6 @@ // Performs the length of edit iteration. Is a bit fugly as this has to support the // sync and async mode which is never fun. Loops over execEditLength until a value // is produced. - var editLength = 1; if (callback) { (function exec() { setTimeout(function() { @@ -192,9 +193,9 @@ exec(); } }, 0); - })(); + }()); } else { - while(editLength <= maxEditLength) { + while (editLength <= maxEditLength) { var ret = execEditLength(); if (ret) { return ret; @@ -204,11 +205,11 @@ }, pushComponent: function(components, added, removed) { - var last = components[components.length-1]; + var last = components[components.length - 1]; if (last && last.added === added && last.removed === removed) { // We need to clone here as the component clone operation is just // as shallow array clone - components[components.length-1] = {count: last.count + 1, added: added, removed: removed }; + components[components.length - 1] = {count: last.count + 1, added: added, removed: removed }; } else { components.push({count: 1, added: added, removed: removed }); } @@ -220,7 +221,7 @@ oldPos = newPos - diagonalPath, commonCount = 0; - while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) { + while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) { newPos++; oldPos++; commonCount++; @@ -264,18 +265,18 @@ LineDiff.tokenize = TrimmedLineDiff.tokenize = function(value) { var retLines = [], lines = value.split(/^/m); - for(var i = 0; i < lines.length; i++) { + for (var i = 0; i < lines.length; i++) { var line = lines[i], lastLine = lines[i - 1], lastLineLastChar = lastLine ? lastLine[lastLine.length - 1] : ''; // Merge lines that may contain windows new lines if (line === '\n' && lastLineLastChar === '\r') { - retLines[retLines.length - 1] = retLines[retLines.length - 1].slice(0,-1) + '\r\n'; + retLines[retLines.length - 1] = retLines[retLines.length - 1].slice(0, -1) + '\r\n'; } else if (line) { if (this.ignoreTrim) { line = line.trim(); - //add a newline unless this is the last line. + // add a newline unless this is the last line. if (i < lines.length - 1) { line += '\n'; } @@ -289,7 +290,7 @@ var SentenceDiff = new Diff(); - SentenceDiff.tokenize = function (value) { + SentenceDiff.tokenize = function(value) { return removeEmpty(value.split(/(\S.+?[.!?])(?=\s+|$)/)); }; @@ -302,8 +303,6 @@ return LineDiff.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')); }; - var objectPrototypeToString = Object.prototype.toString; - // This function handles the presence of circular references by bailing out when encountering an // object that is already on the "stack" of items being processed. function canonicalize(obj, stack, replacementStack) { @@ -375,27 +374,30 @@ if (oldFileName == newFileName) { ret.push('Index: ' + oldFileName); - } + } ret.push('==================================================================='); ret.push('--- ' + oldFileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader)); ret.push('+++ ' + newFileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader)); var diff = LineDiff.diff(oldStr, newStr); - if (!diff[diff.length-1].value) { + if (!diff[diff.length - 1].value) { diff.pop(); // Remove trailing newline add } diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier + // Formats a given set of lines for printing as context lines in a patch function contextLines(lines) { return map(lines, function(entry) { return ' ' + entry; }); } + + // Outputs the no newline at end of file warning if needed function eofNL(curRange, i, current) { - var last = diff[diff.length-2], - isLast = i === diff.length-2, - isLastOfType = i === diff.length-3 && (current.added !== last.added || current.removed !== last.removed); + var last = diff[diff.length - 2], + isLast = i === diff.length - 2, + isLastOfType = i === diff.length - 3 && (current.added !== last.added || current.removed !== last.removed); // Figure out if this is the last line for the given file and missing NL - if (!/\n$/.test(current.value) && (isLast || isLastOfType)) { + if (!(/\n$/.test(current.value)) && (isLast || isLastOfType)) { curRange.push('\\ No newline at end of file'); } } @@ -409,7 +411,7 @@ if (current.added || current.removed) { if (!oldRangeStart) { - var prev = diff[i-1]; + var prev = diff[i - 1]; oldRangeStart = oldLine; newRangeStart = newLine; @@ -419,7 +421,9 @@ newRangeStart -= curRange.length; } } - curRange.push.apply(curRange, map(lines, function(entry) { return (current.added?'+':'-') + entry; })); + curRange.push.apply(curRange, map(lines, function(entry) { + return (current.added ? '+' : '-') + entry; + })); eofNL(curRange, i, current); if (current.added) { @@ -430,15 +434,15 @@ } else { if (oldRangeStart) { // Close out any changes that have been output (or join overlapping) - if (lines.length <= 8 && i < diff.length-2) { + if (lines.length <= 8 && i < diff.length - 2) { // Overlapping curRange.push.apply(curRange, contextLines(lines)); } else { // end the range and output var contextSize = Math.min(lines.length, 4); ret.push( - '@@ -' + oldRangeStart + ',' + (oldLine-oldRangeStart+contextSize) - + ' +' + newRangeStart + ',' + (newLine-newRangeStart+contextSize) + '@@ -' + oldRangeStart + ',' + (oldLine - oldRangeStart + contextSize) + + ' +' + newRangeStart + ',' + (newLine - newRangeStart + contextSize) + ' @@'); ret.push.apply(ret, curRange); ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); @@ -446,7 +450,9 @@ eofNL(ret, i, current); } - oldRangeStart = 0; newRangeStart = 0; curRange = []; + oldRangeStart = 0; + newRangeStart = 0; + curRange = []; } } oldLine += lines.length; @@ -469,19 +475,19 @@ addEOFNL = false; // Skip to the first change chunk - while (i < diffstr.length && !/^@@/.test(diffstr[i])) { + while (i < diffstr.length && !(/^@@/.test(diffstr[i]))) { i++; } for (; i < diffstr.length; i++) { if (diffstr[i][0] === '@') { - var meh = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/); + var chnukHeader = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/); diff.unshift({ - start:meh[3], - oldlength:meh[2], - oldlines:[], - newlength:meh[4], - newlines:[] + start: chnukHeader[3], + oldlength: chnukHeader[2], + oldlines: [], + newlength: chnukHeader[4], + newlines: [] }); } else if (diffstr[i][0] === '+') { diff[0].newlines.push(diffstr[i].substr(1)); @@ -491,27 +497,27 @@ diff[0].newlines.push(diffstr[i].substr(1)); diff[0].oldlines.push(diffstr[i].substr(1)); } else if (diffstr[i][0] === '\\') { - if (diffstr[i-1][0] === '+') { + if (diffstr[i - 1][0] === '+') { remEOFNL = true; - } else if (diffstr[i-1][0] === '-') { + } else if (diffstr[i - 1][0] === '-') { addEOFNL = true; } } } var str = oldStr.split('\n'); - for (var i = diff.length - 1; i >= 0; i--) { + for (i = diff.length - 1; i >= 0; i--) { var d = diff[i]; for (var j = 0; j < d.oldlength; j++) { - if (str[d.start-1+j] !== d.oldlines[j]) { + if (str[d.start - 1 + j] !== d.oldlines[j]) { return false; } } - Array.prototype.splice.apply(str,[d.start-1,+d.oldlength].concat(d.newlines)); + Array.prototype.splice.apply(str, [d.start - 1, +d.oldlength].concat(d.newlines)); } if (remEOFNL) { - while (!str[str.length-1]) { + while (!str[str.length - 1]) { str.pop(); } } else if (addEOFNL) { @@ -520,9 +526,9 @@ return str.join('\n'); }, - convertChangesToXML: function(changes){ + convertChangesToXML: function(changes) { var ret = []; - for ( var i = 0; i < changes.length; i++) { + for (var i = 0; i < changes.length; i++) { var change = changes[i]; if (change.added) { ret.push(''); @@ -542,11 +548,21 @@ }, // See: http://code.google.com/p/google-diff-match-patch/wiki/API - convertChangesToDMP: function(changes){ - var ret = [], change; - for ( var i = 0; i < changes.length; i++) { + convertChangesToDMP: function(changes) { + var ret = [], + change, + operation; + for (var i = 0; i < changes.length; i++) { change = changes[i]; - ret.push([(change.added ? 1 : change.removed ? -1 : 0), change.value]); + if (change.added) { + operation = 1; + } else if (change.removed) { + operation = -1; + } else { + operation = 0; + } + + ret.push([operation, change.value]); } return ret; }, @@ -556,14 +572,13 @@ })(); /*istanbul ignore next */ + /*global module */ if (typeof module !== 'undefined' && module.exports) { module.exports = JsDiff; - } - else if (typeof define === 'function' && define.amd) { + } else if (typeof define === 'function' && define.amd) { /*global define */ define([], function() { return JsDiff; }); - } - else if (typeof global.JsDiff === 'undefined') { + } else if (typeof global.JsDiff === 'undefined') { global.JsDiff = JsDiff; } -})(this); +}(this)); From 477e0643a46342b2042e64990a464ff5105274ab Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 4 May 2015 18:10:21 -0500 Subject: [PATCH 005/436] Remove unnecessary inner IIFE --- diff.js | 958 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 478 insertions(+), 480 deletions(-) diff --git a/diff.js b/diff.js index a1016e7b..2864489f 100644 --- a/diff.js +++ b/diff.js @@ -17,559 +17,557 @@ (function(global, undefined) { var objectPrototypeToString = Object.prototype.toString; - var JsDiff = (function() { - /*jshint maxparams: 5*/ - /*istanbul ignore next*/ - function map(arr, mapper, that) { - if (Array.prototype.map) { - return Array.prototype.map.call(arr, mapper, that); - } + /*istanbul ignore next*/ + function map(arr, mapper, that) { + if (Array.prototype.map) { + return Array.prototype.map.call(arr, mapper, that); + } - var other = new Array(arr.length); + var other = new Array(arr.length); - for (var i = 0, n = arr.length; i < n; i++) { - other[i] = mapper.call(that, arr[i], i, arr); - } - return other; - } - function clonePath(path) { - return { newPos: path.newPos, components: path.components.slice(0) }; + for (var i = 0, n = arr.length; i < n; i++) { + other[i] = mapper.call(that, arr[i], i, arr); } - function removeEmpty(array) { - var ret = []; - for (var i = 0; i < array.length; i++) { - if (array[i]) { - ret.push(array[i]); - } + return other; + } + function clonePath(path) { + return { newPos: path.newPos, components: path.components.slice(0) }; + } + function removeEmpty(array) { + var ret = []; + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); } - return ret; - } - function escapeHTML(s) { - var n = s; - n = n.replace(/&/g, '&'); - n = n.replace(//g, '>'); - n = n.replace(/"/g, '"'); - - return n; } + return ret; + } + function escapeHTML(s) { + var n = s; + n = n.replace(/&/g, '&'); + n = n.replace(//g, '>'); + n = n.replace(/"/g, '"'); + + return n; + } - function buildValues(components, newString, oldString, useLongestToken) { - var componentPos = 0, - componentLen = components.length, - newPos = 0, - oldPos = 0; - - for (; componentPos < componentLen; componentPos++) { - var component = components[componentPos]; - if (!component.removed) { - if (!component.added && useLongestToken) { - var value = newString.slice(newPos, newPos + component.count); - value = map(value, function(value, i) { - var oldValue = oldString[oldPos + i]; - return oldValue.length > value.length ? oldValue : value; - }); - - component.value = value.join(''); - } else { - component.value = newString.slice(newPos, newPos + component.count).join(''); - } - newPos += component.count; + // This function handles the presence of circular references by bailing out when encountering an + // object that is already on the "stack" of items being processed. + function canonicalize(obj, stack, replacementStack) { + stack = stack || []; + replacementStack = replacementStack || []; - // Common case - if (!component.added) { - oldPos += component.count; - } - } else { - component.value = oldString.slice(oldPos, oldPos + component.count).join(''); - oldPos += component.count; - } - } + var i; - return components; + for (i = 0; i < stack.length; i += 1) { + if (stack[i] === obj) { + return replacementStack[i]; + } } - function Diff(ignoreWhitespace) { - this.ignoreWhitespace = ignoreWhitespace; + var canonicalizedObj; + + if ('[object Array]' === objectPrototypeToString.call(obj)) { + stack.push(obj); + canonicalizedObj = new Array(obj.length); + replacementStack.push(canonicalizedObj); + for (i = 0; i < obj.length; i += 1) { + canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack); + } + stack.pop(); + replacementStack.pop(); + } else if (typeof obj === 'object' && obj !== null) { + stack.push(obj); + canonicalizedObj = {}; + replacementStack.push(canonicalizedObj); + var sortedKeys = [], + key; + for (key in obj) { + sortedKeys.push(key); + } + sortedKeys.sort(); + for (i = 0; i < sortedKeys.length; i += 1) { + key = sortedKeys[i]; + canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack); + } + stack.pop(); + replacementStack.pop(); + } else { + canonicalizedObj = obj; } - Diff.prototype = { - diff: function(oldString, newString, callback) { - var self = this; - - function done(value) { - if (callback) { - setTimeout(function() { callback(undefined, value); }, 0); - return true; - } else { - return value; - } - } + return canonicalizedObj; + } - // Handle the identity case (this is due to unrolling editLength == 0 - if (newString === oldString) { - return done([{ value: newString }]); - } - if (!newString) { - return done([{ value: oldString, removed: true }]); - } - if (!oldString) { - return done([{ value: newString, added: true }]); - } + function buildValues(components, newString, oldString, useLongestToken) { + var componentPos = 0, + componentLen = components.length, + newPos = 0, + oldPos = 0; + + for (; componentPos < componentLen; componentPos++) { + var component = components[componentPos]; + if (!component.removed) { + if (!component.added && useLongestToken) { + var value = newString.slice(newPos, newPos + component.count); + value = map(value, function(value, i) { + var oldValue = oldString[oldPos + i]; + return oldValue.length > value.length ? oldValue : value; + }); + + component.value = value.join(''); + } else { + component.value = newString.slice(newPos, newPos + component.count).join(''); + } + newPos += component.count; - newString = this.tokenize(newString); - oldString = this.tokenize(oldString); + // Common case + if (!component.added) { + oldPos += component.count; + } + } else { + component.value = oldString.slice(oldPos, oldPos + component.count).join(''); + oldPos += component.count; + } + } - var newLen = newString.length, oldLen = oldString.length; - var editLength = 1; - var maxEditLength = newLen + oldLen; - var bestPath = [{ newPos: -1, components: [] }]; + return components; + } - // Seed editLength = 0, i.e. the content starts with the same values - var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); - if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) { - // Identity per the equality and tokenizer - return done([{value: newString.join('')}]); - } + function Diff(ignoreWhitespace) { + this.ignoreWhitespace = ignoreWhitespace; + } + Diff.prototype = { + diff: function(oldString, newString, callback) { + var self = this; + + function done(value) { + if (callback) { + setTimeout(function() { callback(undefined, value); }, 0); + return true; + } else { + return value; + } + } - // Main worker method. checks all permutations of a given edit length for acceptance. - function execEditLength() { - for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) { - var basePath; - var addPath = bestPath[diagonalPath - 1], - removePath = bestPath[diagonalPath + 1], - oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; - if (addPath) { - // No one else is going to attempt to use this value, clear it - bestPath[diagonalPath - 1] = undefined; - } + // Handle the identity case (this is due to unrolling editLength == 0 + if (newString === oldString) { + return done([{ value: newString }]); + } + if (!newString) { + return done([{ value: oldString, removed: true }]); + } + if (!oldString) { + return done([{ value: newString, added: true }]); + } - var canAdd = addPath && addPath.newPos + 1 < newLen; - var canRemove = removePath && 0 <= oldPos && oldPos < oldLen; - if (!canAdd && !canRemove) { - // If this path is a terminal then prune - bestPath[diagonalPath] = undefined; - continue; - } + newString = this.tokenize(newString); + oldString = this.tokenize(oldString); - // Select the diagonal that we want to branch from. We select the prior - // path whose position in the new string is the farthest from the origin - // and does not pass the bounds of the diff graph - if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { - basePath = clonePath(removePath); - self.pushComponent(basePath.components, undefined, true); - } else { - basePath = addPath; // No need to clone, we've pulled it from the list - basePath.newPos++; - self.pushComponent(basePath.components, true, undefined); - } + var newLen = newString.length, oldLen = oldString.length; + var editLength = 1; + var maxEditLength = newLen + oldLen; + var bestPath = [{ newPos: -1, components: [] }]; - oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); + // Seed editLength = 0, i.e. the content starts with the same values + var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); + if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) { + // Identity per the equality and tokenizer + return done([{value: newString.join('')}]); + } - // If we have hit the end of both strings, then we are done - if (basePath.newPos + 1 >= newLen && oldPos + 1 >= oldLen) { - return done(buildValues(basePath.components, newString, oldString, self.useLongestToken)); - } else { - // Otherwise track this path as a potential candidate and continue. - bestPath[diagonalPath] = basePath; - } - } + // Main worker method. checks all permutations of a given edit length for acceptance. + function execEditLength() { + for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) { + var basePath; + var addPath = bestPath[diagonalPath - 1], + removePath = bestPath[diagonalPath + 1], + oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; + if (addPath) { + // No one else is going to attempt to use this value, clear it + bestPath[diagonalPath - 1] = undefined; + } - editLength++; + var canAdd = addPath && addPath.newPos + 1 < newLen; + var canRemove = removePath && 0 <= oldPos && oldPos < oldLen; + if (!canAdd && !canRemove) { + // If this path is a terminal then prune + bestPath[diagonalPath] = undefined; + continue; } - // Performs the length of edit iteration. Is a bit fugly as this has to support the - // sync and async mode which is never fun. Loops over execEditLength until a value - // is produced. - if (callback) { - (function exec() { - setTimeout(function() { - // This should not happen, but we want to be safe. - /*istanbul ignore next */ - if (editLength > maxEditLength) { - return callback(); - } - - if (!execEditLength()) { - exec(); - } - }, 0); - }()); + // Select the diagonal that we want to branch from. We select the prior + // path whose position in the new string is the farthest from the origin + // and does not pass the bounds of the diff graph + if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { + basePath = clonePath(removePath); + self.pushComponent(basePath.components, undefined, true); } else { - while (editLength <= maxEditLength) { - var ret = execEditLength(); - if (ret) { - return ret; - } - } + basePath = addPath; // No need to clone, we've pulled it from the list + basePath.newPos++; + self.pushComponent(basePath.components, true, undefined); } - }, - - pushComponent: function(components, added, removed) { - var last = components[components.length - 1]; - if (last && last.added === added && last.removed === removed) { - // We need to clone here as the component clone operation is just - // as shallow array clone - components[components.length - 1] = {count: last.count + 1, added: added, removed: removed }; + + oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); + + // If we have hit the end of both strings, then we are done + if (basePath.newPos + 1 >= newLen && oldPos + 1 >= oldLen) { + return done(buildValues(basePath.components, newString, oldString, self.useLongestToken)); } else { - components.push({count: 1, added: added, removed: removed }); - } - }, - extractCommon: function(basePath, newString, oldString, diagonalPath) { - var newLen = newString.length, - oldLen = oldString.length, - newPos = basePath.newPos, - oldPos = newPos - diagonalPath, - - commonCount = 0; - while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) { - newPos++; - oldPos++; - commonCount++; + // Otherwise track this path as a potential candidate and continue. + bestPath[diagonalPath] = basePath; } + } - if (commonCount) { - basePath.components.push({count: commonCount}); - } + editLength++; + } - basePath.newPos = newPos; - return oldPos; - }, + // Performs the length of edit iteration. Is a bit fugly as this has to support the + // sync and async mode which is never fun. Loops over execEditLength until a value + // is produced. + if (callback) { + (function exec() { + setTimeout(function() { + // This should not happen, but we want to be safe. + /*istanbul ignore next */ + if (editLength > maxEditLength) { + return callback(); + } - equals: function(left, right) { - var reWhitespace = /\S/; - return left === right || (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)); - }, - tokenize: function(value) { - return value.split(''); - } - }; - - var CharDiff = new Diff(); - - var WordDiff = new Diff(true); - var WordWithSpaceDiff = new Diff(); - WordDiff.tokenize = WordWithSpaceDiff.tokenize = function(value) { - return removeEmpty(value.split(/(\s+|\b)/)); - }; - - var CssDiff = new Diff(true); - CssDiff.tokenize = function(value) { - return removeEmpty(value.split(/([{}:;,]|\s+)/)); - }; - - var LineDiff = new Diff(); - - var TrimmedLineDiff = new Diff(); - TrimmedLineDiff.ignoreTrim = true; - - LineDiff.tokenize = TrimmedLineDiff.tokenize = function(value) { - var retLines = [], - lines = value.split(/^/m); - for (var i = 0; i < lines.length; i++) { - var line = lines[i], - lastLine = lines[i - 1], - lastLineLastChar = lastLine ? lastLine[lastLine.length - 1] : ''; - - // Merge lines that may contain windows new lines - if (line === '\n' && lastLineLastChar === '\r') { - retLines[retLines.length - 1] = retLines[retLines.length - 1].slice(0, -1) + '\r\n'; - } else if (line) { - if (this.ignoreTrim) { - line = line.trim(); - // add a newline unless this is the last line. - if (i < lines.length - 1) { - line += '\n'; + if (!execEditLength()) { + exec(); } + }, 0); + }()); + } else { + while (editLength <= maxEditLength) { + var ret = execEditLength(); + if (ret) { + return ret; } - retLines.push(line); } } + }, + + pushComponent: function(components, added, removed) { + var last = components[components.length - 1]; + if (last && last.added === added && last.removed === removed) { + // We need to clone here as the component clone operation is just + // as shallow array clone + components[components.length - 1] = {count: last.count + 1, added: added, removed: removed }; + } else { + components.push({count: 1, added: added, removed: removed }); + } + }, + extractCommon: function(basePath, newString, oldString, diagonalPath) { + var newLen = newString.length, + oldLen = oldString.length, + newPos = basePath.newPos, + oldPos = newPos - diagonalPath, + + commonCount = 0; + while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) { + newPos++; + oldPos++; + commonCount++; + } - return retLines; - }; - - - var SentenceDiff = new Diff(); - SentenceDiff.tokenize = function(value) { - return removeEmpty(value.split(/(\S.+?[.!?])(?=\s+|$)/)); - }; - - var JsonDiff = new Diff(); - // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a - // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: - JsonDiff.useLongestToken = true; - JsonDiff.tokenize = LineDiff.tokenize; - JsonDiff.equals = function(left, right) { - return LineDiff.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')); - }; - - // This function handles the presence of circular references by bailing out when encountering an - // object that is already on the "stack" of items being processed. - function canonicalize(obj, stack, replacementStack) { - stack = stack || []; - replacementStack = replacementStack || []; - - var i; - - for (var i = 0 ; i < stack.length ; i += 1) { - if (stack[i] === obj) { - return replacementStack[i]; - } + if (commonCount) { + basePath.components.push({count: commonCount}); } - var canonicalizedObj; + basePath.newPos = newPos; + return oldPos; + }, - if ('[object Array]' === objectPrototypeToString.call(obj)) { - stack.push(obj); - canonicalizedObj = new Array(obj.length); - replacementStack.push(canonicalizedObj); - for (i = 0 ; i < obj.length ; i += 1) { - canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack); - } - stack.pop(); - replacementStack.pop(); - } else if (typeof obj === 'object' && obj !== null) { - stack.push(obj); - canonicalizedObj = {}; - replacementStack.push(canonicalizedObj); - var sortedKeys = []; - for (var key in obj) { - sortedKeys.push(key); - } - sortedKeys.sort(); - for (i = 0 ; i < sortedKeys.length ; i += 1) { - var key = sortedKeys[i]; - canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack); + equals: function(left, right) { + var reWhitespace = /\S/; + return left === right || (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)); + }, + tokenize: function(value) { + return value.split(''); + } + }; + + var CharDiff = new Diff(); + + var WordDiff = new Diff(true); + var WordWithSpaceDiff = new Diff(); + WordDiff.tokenize = WordWithSpaceDiff.tokenize = function(value) { + return removeEmpty(value.split(/(\s+|\b)/)); + }; + + var CssDiff = new Diff(true); + CssDiff.tokenize = function(value) { + return removeEmpty(value.split(/([{}:;,]|\s+)/)); + }; + + var LineDiff = new Diff(); + + var TrimmedLineDiff = new Diff(); + TrimmedLineDiff.ignoreTrim = true; + + LineDiff.tokenize = TrimmedLineDiff.tokenize = function(value) { + var retLines = [], + lines = value.split(/^/m); + for (var i = 0; i < lines.length; i++) { + var line = lines[i], + lastLine = lines[i - 1], + lastLineLastChar = lastLine ? lastLine[lastLine.length - 1] : ''; + + // Merge lines that may contain windows new lines + if (line === '\n' && lastLineLastChar === '\r') { + retLines[retLines.length - 1] = retLines[retLines.length - 1].slice(0, -1) + '\r\n'; + } else if (line) { + if (this.ignoreTrim) { + line = line.trim(); + // add a newline unless this is the last line. + if (i < lines.length - 1) { + line += '\n'; + } } - stack.pop(); - replacementStack.pop(); - } else { - canonicalizedObj = obj; + retLines.push(line); } - return canonicalizedObj; } - return { - Diff: Diff, - - diffChars: function(oldStr, newStr, callback) { return CharDiff.diff(oldStr, newStr, callback); }, - diffWords: function(oldStr, newStr, callback) { return WordDiff.diff(oldStr, newStr, callback); }, - diffWordsWithSpace: function(oldStr, newStr, callback) { return WordWithSpaceDiff.diff(oldStr, newStr, callback); }, - diffLines: function(oldStr, newStr, callback) { return LineDiff.diff(oldStr, newStr, callback); }, - diffTrimmedLines: function(oldStr, newStr, callback) { return TrimmedLineDiff.diff(oldStr, newStr, callback); }, - - diffSentences: function(oldStr, newStr, callback) { return SentenceDiff.diff(oldStr, newStr, callback); }, - - diffCss: function(oldStr, newStr, callback) { return CssDiff.diff(oldStr, newStr, callback); }, - diffJson: function(oldObj, newObj, callback) { - return JsonDiff.diff( - typeof oldObj === 'string' ? oldObj : JSON.stringify(canonicalize(oldObj), undefined, ' '), - typeof newObj === 'string' ? newObj : JSON.stringify(canonicalize(newObj), undefined, ' '), - callback - ); - }, - - createTwoFilesPatch: function(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader) { - var ret = []; + return retLines; + }; + + + var SentenceDiff = new Diff(); + SentenceDiff.tokenize = function(value) { + return removeEmpty(value.split(/(\S.+?[.!?])(?=\s+|$)/)); + }; + + var JsonDiff = new Diff(); + // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a + // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: + JsonDiff.useLongestToken = true; + JsonDiff.tokenize = LineDiff.tokenize; + JsonDiff.equals = function(left, right) { + return LineDiff.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')); + }; + + var JsDiff = { + Diff: Diff, + + diffChars: function(oldStr, newStr, callback) { return CharDiff.diff(oldStr, newStr, callback); }, + diffWords: function(oldStr, newStr, callback) { return WordDiff.diff(oldStr, newStr, callback); }, + diffWordsWithSpace: function(oldStr, newStr, callback) { return WordWithSpaceDiff.diff(oldStr, newStr, callback); }, + diffLines: function(oldStr, newStr, callback) { return LineDiff.diff(oldStr, newStr, callback); }, + diffTrimmedLines: function(oldStr, newStr, callback) { return TrimmedLineDiff.diff(oldStr, newStr, callback); }, + + diffSentences: function(oldStr, newStr, callback) { return SentenceDiff.diff(oldStr, newStr, callback); }, + + diffCss: function(oldStr, newStr, callback) { return CssDiff.diff(oldStr, newStr, callback); }, + diffJson: function(oldObj, newObj, callback) { + return JsonDiff.diff( + typeof oldObj === 'string' ? oldObj : JSON.stringify(canonicalize(oldObj), undefined, ' '), + typeof newObj === 'string' ? newObj : JSON.stringify(canonicalize(newObj), undefined, ' '), + callback + ); + }, + + createTwoFilesPatch: function(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader) { + var ret = []; - if (oldFileName == newFileName) { - ret.push('Index: ' + oldFileName); - } - ret.push('==================================================================='); - ret.push('--- ' + oldFileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader)); - ret.push('+++ ' + newFileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader)); + if (oldFileName == newFileName) { + ret.push('Index: ' + oldFileName); + } + ret.push('==================================================================='); + ret.push('--- ' + oldFileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader)); + ret.push('+++ ' + newFileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader)); - var diff = LineDiff.diff(oldStr, newStr); - if (!diff[diff.length - 1].value) { - diff.pop(); // Remove trailing newline add - } - diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier + var diff = LineDiff.diff(oldStr, newStr); + if (!diff[diff.length - 1].value) { + diff.pop(); // Remove trailing newline add + } + diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier - // Formats a given set of lines for printing as context lines in a patch - function contextLines(lines) { - return map(lines, function(entry) { return ' ' + entry; }); - } + // Formats a given set of lines for printing as context lines in a patch + function contextLines(lines) { + return map(lines, function(entry) { return ' ' + entry; }); + } - // Outputs the no newline at end of file warning if needed - function eofNL(curRange, i, current) { - var last = diff[diff.length - 2], - isLast = i === diff.length - 2, - isLastOfType = i === diff.length - 3 && (current.added !== last.added || current.removed !== last.removed); + // Outputs the no newline at end of file warning if needed + function eofNL(curRange, i, current) { + var last = diff[diff.length - 2], + isLast = i === diff.length - 2, + isLastOfType = i === diff.length - 3 && (current.added !== last.added || current.removed !== last.removed); - // Figure out if this is the last line for the given file and missing NL - if (!(/\n$/.test(current.value)) && (isLast || isLastOfType)) { - curRange.push('\\ No newline at end of file'); - } + // Figure out if this is the last line for the given file and missing NL + if (!(/\n$/.test(current.value)) && (isLast || isLastOfType)) { + curRange.push('\\ No newline at end of file'); } + } - var oldRangeStart = 0, newRangeStart = 0, curRange = [], - oldLine = 1, newLine = 1; - for (var i = 0; i < diff.length; i++) { - var current = diff[i], - lines = current.lines || current.value.replace(/\n$/, '').split('\n'); - current.lines = lines; - - if (current.added || current.removed) { - if (!oldRangeStart) { - var prev = diff[i - 1]; - oldRangeStart = oldLine; - newRangeStart = newLine; - - if (prev) { - curRange = contextLines(prev.lines.slice(-4)); - oldRangeStart -= curRange.length; - newRangeStart -= curRange.length; - } + var oldRangeStart = 0, newRangeStart = 0, curRange = [], + oldLine = 1, newLine = 1; + for (var i = 0; i < diff.length; i++) { + var current = diff[i], + lines = current.lines || current.value.replace(/\n$/, '').split('\n'); + current.lines = lines; + + if (current.added || current.removed) { + if (!oldRangeStart) { + var prev = diff[i - 1]; + oldRangeStart = oldLine; + newRangeStart = newLine; + + if (prev) { + curRange = contextLines(prev.lines.slice(-4)); + oldRangeStart -= curRange.length; + newRangeStart -= curRange.length; } - curRange.push.apply(curRange, map(lines, function(entry) { - return (current.added ? '+' : '-') + entry; - })); - eofNL(curRange, i, current); + } + curRange.push.apply(curRange, map(lines, function(entry) { + return (current.added ? '+' : '-') + entry; + })); + eofNL(curRange, i, current); - if (current.added) { - newLine += lines.length; - } else { - oldLine += lines.length; - } + if (current.added) { + newLine += lines.length; } else { - if (oldRangeStart) { - // Close out any changes that have been output (or join overlapping) - if (lines.length <= 8 && i < diff.length - 2) { - // Overlapping - curRange.push.apply(curRange, contextLines(lines)); - } else { - // end the range and output - var contextSize = Math.min(lines.length, 4); - ret.push( - '@@ -' + oldRangeStart + ',' + (oldLine - oldRangeStart + contextSize) - + ' +' + newRangeStart + ',' + (newLine - newRangeStart + contextSize) - + ' @@'); - ret.push.apply(ret, curRange); - ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); - if (lines.length <= 4) { - eofNL(ret, i, current); - } - - oldRangeStart = 0; - newRangeStart = 0; - curRange = []; + oldLine += lines.length; + } + } else { + if (oldRangeStart) { + // Close out any changes that have been output (or join overlapping) + if (lines.length <= 8 && i < diff.length - 2) { + // Overlapping + curRange.push.apply(curRange, contextLines(lines)); + } else { + // end the range and output + var contextSize = Math.min(lines.length, 4); + ret.push( + '@@ -' + oldRangeStart + ',' + (oldLine - oldRangeStart + contextSize) + + ' +' + newRangeStart + ',' + (newLine - newRangeStart + contextSize) + + ' @@'); + ret.push.apply(ret, curRange); + ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); + if (lines.length <= 4) { + eofNL(ret, i, current); } + + oldRangeStart = 0; + newRangeStart = 0; + curRange = []; } - oldLine += lines.length; - newLine += lines.length; } + oldLine += lines.length; + newLine += lines.length; } + } - return ret.join('\n') + '\n'; - }, + return ret.join('\n') + '\n'; + }, - createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { - return JsDiff.createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader); - }, + createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { + return JsDiff.createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader); + }, - applyPatch: function(oldStr, uniDiff) { - var diffstr = uniDiff.split('\n'); - var diff = []; - var i = 0, - remEOFNL = false, - addEOFNL = false; + applyPatch: function(oldStr, uniDiff) { + var diffstr = uniDiff.split('\n'); + var diff = []; + var i = 0, + remEOFNL = false, + addEOFNL = false; - // Skip to the first change chunk - while (i < diffstr.length && !(/^@@/.test(diffstr[i]))) { - i++; - } + // Skip to the first change chunk + while (i < diffstr.length && !(/^@@/.test(diffstr[i]))) { + i++; + } - for (; i < diffstr.length; i++) { - if (diffstr[i][0] === '@') { - var chnukHeader = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/); - diff.unshift({ - start: chnukHeader[3], - oldlength: chnukHeader[2], - oldlines: [], - newlength: chnukHeader[4], - newlines: [] - }); - } else if (diffstr[i][0] === '+') { - diff[0].newlines.push(diffstr[i].substr(1)); - } else if (diffstr[i][0] === '-') { - diff[0].oldlines.push(diffstr[i].substr(1)); - } else if (diffstr[i][0] === ' ') { - diff[0].newlines.push(diffstr[i].substr(1)); - diff[0].oldlines.push(diffstr[i].substr(1)); - } else if (diffstr[i][0] === '\\') { - if (diffstr[i - 1][0] === '+') { - remEOFNL = true; - } else if (diffstr[i - 1][0] === '-') { - addEOFNL = true; - } + for (; i < diffstr.length; i++) { + if (diffstr[i][0] === '@') { + var chnukHeader = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/); + diff.unshift({ + start: chnukHeader[3], + oldlength: chnukHeader[2], + oldlines: [], + newlength: chnukHeader[4], + newlines: [] + }); + } else if (diffstr[i][0] === '+') { + diff[0].newlines.push(diffstr[i].substr(1)); + } else if (diffstr[i][0] === '-') { + diff[0].oldlines.push(diffstr[i].substr(1)); + } else if (diffstr[i][0] === ' ') { + diff[0].newlines.push(diffstr[i].substr(1)); + diff[0].oldlines.push(diffstr[i].substr(1)); + } else if (diffstr[i][0] === '\\') { + if (diffstr[i - 1][0] === '+') { + remEOFNL = true; + } else if (diffstr[i - 1][0] === '-') { + addEOFNL = true; } } + } - var str = oldStr.split('\n'); - for (i = diff.length - 1; i >= 0; i--) { - var d = diff[i]; - for (var j = 0; j < d.oldlength; j++) { - if (str[d.start - 1 + j] !== d.oldlines[j]) { - return false; - } + var str = oldStr.split('\n'); + for (i = diff.length - 1; i >= 0; i--) { + var d = diff[i]; + for (var j = 0; j < d.oldlength; j++) { + if (str[d.start - 1 + j] !== d.oldlines[j]) { + return false; } - Array.prototype.splice.apply(str, [d.start - 1, +d.oldlength].concat(d.newlines)); } + Array.prototype.splice.apply(str, [d.start - 1, +d.oldlength].concat(d.newlines)); + } - if (remEOFNL) { - while (!str[str.length - 1]) { - str.pop(); - } - } else if (addEOFNL) { - str.push(''); + if (remEOFNL) { + while (!str[str.length - 1]) { + str.pop(); } - return str.join('\n'); - }, - - convertChangesToXML: function(changes) { - var ret = []; - for (var i = 0; i < changes.length; i++) { - var change = changes[i]; - if (change.added) { - ret.push(''); - } else if (change.removed) { - ret.push(''); - } - - ret.push(escapeHTML(change.value)); + } else if (addEOFNL) { + str.push(''); + } + return str.join('\n'); + }, - if (change.added) { - ret.push(''); - } else if (change.removed) { - ret.push(''); - } + convertChangesToXML: function(changes) { + var ret = []; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); } - return ret.join(''); - }, - - // See: http://code.google.com/p/google-diff-match-patch/wiki/API - convertChangesToDMP: function(changes) { - var ret = [], - change, - operation; - for (var i = 0; i < changes.length; i++) { - change = changes[i]; - if (change.added) { - operation = 1; - } else if (change.removed) { - operation = -1; - } else { - operation = 0; - } - ret.push([operation, change.value]); + ret.push(escapeHTML(change.value)); + + if (change.added) { + ret.push(''); + } else if (change.removed) { + ret.push(''); + } + } + return ret.join(''); + }, + + // See: http://code.google.com/p/google-diff-match-patch/wiki/API + convertChangesToDMP: function(changes) { + var ret = [], + change, + operation; + for (var i = 0; i < changes.length; i++) { + change = changes[i]; + if (change.added) { + operation = 1; + } else if (change.removed) { + operation = -1; + } else { + operation = 0; } - return ret; - }, - canonicalize: canonicalize - }; - })(); + ret.push([operation, change.value]); + } + return ret; + }, + + canonicalize: canonicalize + }; /*istanbul ignore next */ /*global module */ From 7c0a20e5de291034282b5ee4f29bb8480c4dba40 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Mon, 4 May 2015 18:15:00 -0500 Subject: [PATCH 006/436] Style and error handler updates --- test/diffTest.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/test/diffTest.js b/test/diffTest.js index ba02eacb..28b74fb9 100644 --- a/test/diffTest.js +++ b/test/diffTest.js @@ -1,4 +1,5 @@ -var diff = require('../diff'); +var diff = require('../diff'), + should = require('should'); describe('#diffWords', function() { it('should diff whitespace', function() { @@ -61,6 +62,7 @@ describe('#diffWords', function() { describe('#diffWords - async', function() { it('should diff whitespace', function(done) { diff.diffWords('New Value', 'New ValueMoreData', function(err, diffResult) { + should(err).not.exist; diff.convertChangesToXML(diffResult).should.equal('New ValueMoreDataValue'); done(); }); @@ -68,6 +70,7 @@ describe('#diffWords - async', function() { it('should diff multiple whitespace values', function(done) { diff.diffWords('New Value ', 'New ValueMoreData ', function(err, diffResult) { + should(err).not.exist; diff.convertChangesToXML(diffResult).should.equal('New ValueMoreDataValue '); done(); }); @@ -76,6 +79,7 @@ describe('#diffWords - async', function() { // Diff on word boundary it('should diff on word boundaries', function(done) { diff.diffWords('New :Value:Test', 'New ValueMoreData ', function(err, diffResult) { + should(err).not.exist; diff.convertChangesToXML(diffResult).should.equal('New ValueMoreData :Value:Test'); done(); }); @@ -84,18 +88,21 @@ describe('#diffWords - async', function() { // Diff without changes it('should handle identity', function(done) { diff.diffWords('New Value', 'New Value', function(err, diffResult) { + should(err).not.exist; diff.convertChangesToXML(diffResult).should.equal('New Value'); done(); }); }); it('should handle empty', function(done) { diff.diffWords('', '', function(err, diffResult) { + should(err).not.exist; diff.convertChangesToXML(diffResult).should.equal(''); done(); }); }); it('should diff has identical content', function(done) { diff.diffWords('New Value', 'New Value', function(err, diffResult) { + should(err).not.exist; diff.convertChangesToXML(diffResult).should.equal('New Value'); done(); }); @@ -198,7 +205,7 @@ describe('#diffLines', function() { 'line\r\nnew value\r\nline'); diff.convertChangesToXML(diffResult).should.equal('line\r\nnew value\r\nold value \r\nline'); }); - + it('should handle empty lines', function() { var diffResult = diff.diffLines( 'line\n\nold value \n\nline', @@ -298,12 +305,12 @@ describe('#diffJson', function() { it('should throw an error if one of the objects being diffed has a circular reference', function() { var circular = {foo: 123}; circular.bar = circular; - (function () { + (function() { diff.diffJson( circular, {foo: 123, bar: {}} ); - }).should.throw('Converting circular structure to JSON'); + }.should['throw']('Converting circular structure to JSON')); }); }); @@ -312,6 +319,6 @@ describe('convertToDMP', function() { var diffResult = diff.diffWords('New Value ', 'New ValueMoreData '); diff.convertChangesToDMP(diffResult).should.eql( - [[0,'New '],[1,'ValueMoreData'],[-1,'Value'],[0,' ']]); + [[0, 'New '], [1, 'ValueMoreData'], [-1, 'Value'], [0, ' ']]); }); }); From 5e27060d2198198c4fad10e37cd7b70520a8813a Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 6 May 2015 02:28:05 -0500 Subject: [PATCH 007/436] Remove dead code This code had no input on the behavior of this edge case. --- diff.js | 3 --- test/createPatch.js | 10 ++++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/diff.js b/diff.js index 2864489f..908a804b 100644 --- a/diff.js +++ b/diff.js @@ -379,9 +379,6 @@ ret.push('+++ ' + newFileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader)); var diff = LineDiff.diff(oldStr, newStr); - if (!diff[diff.length - 1].value) { - diff.pop(); // Remove trailing newline add - } diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier // Formats a given set of lines for printing as context lines in a patch diff --git a/test/createPatch.js b/test/createPatch.js index 067f6e18..16569307 100644 --- a/test/createPatch.js +++ b/test/createPatch.js @@ -485,4 +485,14 @@ describe('#createPatch', function() { var diffResult = diff.createPatch('testFileName', oldFile, oldFile, 'Old Header', 'New Header'); diffResult.should.equal(expectedResult); }); + + it('should safely handle empty inputs', function() { + var expectedResult = + 'Index: testFileName\n' + + '===================================================================\n' + + '--- testFileName\n' + + '+++ testFileName\n'; + var diffResult = diff.createPatch('testFileName', '', ''); + diffResult.should.equal(expectedResult); + }); }); From 9f6279d5a0a11a233a5f71491df821c8193b72ba Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 6 May 2015 02:28:12 -0500 Subject: [PATCH 008/436] Add additional test coverage --- diff.js | 50 ++++++++++++++++++++++++--------------------- test/applyPatch.js | 28 +++++++++++++++++++------ test/createPatch.js | 19 +++++++++++++++++ test/diffTest.js | 7 +++++++ 4 files changed, 75 insertions(+), 29 deletions(-) diff --git a/diff.js b/diff.js index 908a804b..79d99679 100644 --- a/diff.js +++ b/diff.js @@ -318,7 +318,7 @@ // Merge lines that may contain windows new lines if (line === '\n' && lastLineLastChar === '\r') { retLines[retLines.length - 1] = retLines[retLines.length - 1].slice(0, -1) + '\r\n'; - } else if (line) { + } else { if (this.ignoreTrim) { line = line.trim(); // add a newline unless this is the last line. @@ -464,34 +464,35 @@ }, applyPatch: function(oldStr, uniDiff) { - var diffstr = uniDiff.split('\n'); - var diff = []; - var i = 0, + var diffstr = uniDiff.split('\n'), + hunks = [], + i = 0, remEOFNL = false, addEOFNL = false; - // Skip to the first change chunk + // Skip to the first change hunk while (i < diffstr.length && !(/^@@/.test(diffstr[i]))) { i++; } + // Parse the unified diff for (; i < diffstr.length; i++) { if (diffstr[i][0] === '@') { var chnukHeader = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/); - diff.unshift({ + hunks.unshift({ start: chnukHeader[3], - oldlength: chnukHeader[2], - oldlines: [], + oldlength: +chnukHeader[2], + removed: [], newlength: chnukHeader[4], - newlines: [] + added: [] }); } else if (diffstr[i][0] === '+') { - diff[0].newlines.push(diffstr[i].substr(1)); + hunks[0].added.push(diffstr[i].substr(1)); } else if (diffstr[i][0] === '-') { - diff[0].oldlines.push(diffstr[i].substr(1)); + hunks[0].removed.push(diffstr[i].substr(1)); } else if (diffstr[i][0] === ' ') { - diff[0].newlines.push(diffstr[i].substr(1)); - diff[0].oldlines.push(diffstr[i].substr(1)); + hunks[0].added.push(diffstr[i].substr(1)); + hunks[0].removed.push(diffstr[i].substr(1)); } else if (diffstr[i][0] === '\\') { if (diffstr[i - 1][0] === '+') { remEOFNL = true; @@ -501,25 +502,28 @@ } } - var str = oldStr.split('\n'); - for (i = diff.length - 1; i >= 0; i--) { - var d = diff[i]; - for (var j = 0; j < d.oldlength; j++) { - if (str[d.start - 1 + j] !== d.oldlines[j]) { + // Apply the diff to the input + var lines = oldStr.split('\n'); + for (i = hunks.length - 1; i >= 0; i--) { + var hunk = hunks[i]; + // Sanity check the input string. Bail if we don't match. + for (var j = 0; j < hunk.oldlength; j++) { + if (lines[hunk.start - 1 + j] !== hunk.removed[j]) { return false; } } - Array.prototype.splice.apply(str, [d.start - 1, +d.oldlength].concat(d.newlines)); + Array.prototype.splice.apply(lines, [hunk.start - 1, hunk.oldlength].concat(hunk.added)); } + // Handle EOFNL insertion/removal if (remEOFNL) { - while (!str[str.length - 1]) { - str.pop(); + while (!lines[lines.length - 1]) { + lines.pop(); } } else if (addEOFNL) { - str.push(''); + lines.push(''); } - return str.join('\n'); + return lines.join('\n'); }, convertChangesToXML: function(changes) { diff --git a/test/applyPatch.js b/test/applyPatch.js index f7cae2a0..304ded65 100644 --- a/test/applyPatch.js +++ b/test/applyPatch.js @@ -3,9 +3,9 @@ var diff = require('../diff'); describe('#applyPatch', function() { it('should apply patches that change the last line', function() { diff.applyPatch( - 'line2\n'+ - 'line3\n'+ - 'line5\n', + 'line2\n' + + 'line3\n' + + 'line5\n', 'Index: test\n' + '===================================================================\n' @@ -272,9 +272,9 @@ describe('#applyPatch', function() { it('should apply patches that lack an index header', function() { diff.applyPatch( - 'line2\n'+ - 'line3\n'+ - 'line5\n', + 'line2\n' + + 'line3\n' + + 'line5\n', '--- test\theader1\n' + '+++ test\theader2\n' @@ -289,4 +289,20 @@ describe('#applyPatch', function() { + 'line4\n' + 'line5\n'); }); + + it('should fail on mismatch', function() { + diff.applyPatch( + 'line2\n' + + 'line2\n' + + 'line5\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,3 +1,4 @@\n' + + ' line2\n' + + ' line3\n' + + '+line4\n' + + ' line5\n') + .should.equal(false); + }); }); diff --git a/test/createPatch.js b/test/createPatch.js index 16569307..439e9de0 100644 --- a/test/createPatch.js +++ b/test/createPatch.js @@ -486,6 +486,16 @@ describe('#createPatch', function() { diffResult.should.equal(expectedResult); }); + it('should omit headers if undefined', function() { + var expectedResult = + 'Index: testFileName\n' + + '===================================================================\n' + + '--- testFileName\n' + + '+++ testFileName\n'; + var diffResult = diff.createPatch('testFileName', oldFile, oldFile); + diffResult.should.equal(expectedResult); + }); + it('should safely handle empty inputs', function() { var expectedResult = 'Index: testFileName\n' @@ -495,4 +505,13 @@ describe('#createPatch', function() { var diffResult = diff.createPatch('testFileName', '', ''); diffResult.should.equal(expectedResult); }); + + it('should omit index with multiple file names', function() { + var expectedResult = + '===================================================================\n' + + '--- foo\n' + + '+++ bar\n'; + var diffResult = diff.createTwoFilesPatch('foo', 'bar', '', ''); + diffResult.should.equal(expectedResult); + }); }); diff --git a/test/diffTest.js b/test/diffTest.js index 28b74fb9..42903348 100644 --- a/test/diffTest.js +++ b/test/diffTest.js @@ -212,6 +212,13 @@ describe('#diffLines', function() { 'line\n\nnew value\n\nline'); diff.convertChangesToXML(diffResult).should.equal('line\n\nnew value\nold value \n\nline'); }); + + it('should handle empty input', function() { + var diffResult = diff.diffLines( + 'line\n\nold value \n\nline', + ''); + diff.convertChangesToXML(diffResult).should.equal('line\n\nold value \n\nline'); + }); }); // Trimmed Line Diff From 7c9541f2976f845597109e57a3f84ca19fdfc4d1 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 6 May 2015 02:47:50 -0500 Subject: [PATCH 009/436] Provide better comments in patch generator --- diff.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/diff.js b/diff.js index 79d99679..46cd77a2 100644 --- a/diff.js +++ b/diff.js @@ -406,6 +406,7 @@ current.lines = lines; if (current.added || current.removed) { + // If we have previous context, start with that if (!oldRangeStart) { var prev = diff[i - 1]; oldRangeStart = oldLine; @@ -417,17 +418,21 @@ newRangeStart -= curRange.length; } } + + // Output our changes curRange.push.apply(curRange, map(lines, function(entry) { return (current.added ? '+' : '-') + entry; })); eofNL(curRange, i, current); + // Track the updated file position if (current.added) { newLine += lines.length; } else { oldLine += lines.length; } } else { + // Identical context lines. Track line changes if (oldRangeStart) { // Close out any changes that have been output (or join overlapping) if (lines.length <= 8 && i < diff.length - 2) { From b86c6e7b583c933a2fbc35a772fb282aba84d930 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 6 May 2015 03:21:20 -0500 Subject: [PATCH 010/436] Output removals prior to additions Fixes #14 --- diff.js | 15 ++++++++++++--- test/createPatch.js | 24 +++++++++++------------ test/diffTest.js | 46 ++++++++++++++++++++++----------------------- 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/diff.js b/diff.js index 46cd77a2..482a0a71 100644 --- a/diff.js +++ b/diff.js @@ -128,6 +128,15 @@ } else { component.value = oldString.slice(oldPos, oldPos + component.count).join(''); oldPos += component.count; + + // Reverse add and remove so removes are output first to match common convention + // The diffing algorithm is tied to add then remove output and this is the simplest + // route to get the desired output with minimal overhead. + if (componentPos && components[componentPos - 1].added) { + var tmp = components[componentPos - 1]; + components[componentPos - 1] = components[componentPos]; + components[componentPos] = tmp; + } } } @@ -188,8 +197,8 @@ bestPath[diagonalPath - 1] = undefined; } - var canAdd = addPath && addPath.newPos + 1 < newLen; - var canRemove = removePath && 0 <= oldPos && oldPos < oldLen; + var canAdd = addPath && addPath.newPos + 1 < newLen, + canRemove = removePath && 0 <= oldPos && oldPos < oldLen; if (!canAdd && !canRemove) { // If this path is a terminal then prune bestPath[diagonalPath] = undefined; @@ -390,7 +399,7 @@ function eofNL(curRange, i, current) { var last = diff[diff.length - 2], isLast = i === diff.length - 2, - isLastOfType = i === diff.length - 3 && (current.added !== last.added || current.removed !== last.removed); + isLastOfType = i === diff.length - 3 && current.added !== last.added; // Figure out if this is the last line for the given file and missing NL if (!(/\n$/.test(current.value)) && (isLast || isLastOfType)) { diff --git a/test/createPatch.js b/test/createPatch.js index 439e9de0..62665acf 100644 --- a/test/createPatch.js +++ b/test/createPatch.js @@ -39,8 +39,8 @@ describe('#createPatch', function() { + ' line1\n' + ' line2\n' + ' line3\n' - + '+line44\n' - + '-line4\n'); + + '-line4\n' + + '+line44\n'); diff.createPatch('test', 'line1\nline2\nline3\nline4\n', 'line1\nline2\nline3\nline44\nline5\n', 'header1', 'header2').should.equal( 'Index: test\n' @@ -51,9 +51,9 @@ describe('#createPatch', function() { + ' line1\n' + ' line2\n' + ' line3\n' + + '-line4\n' + '+line44\n' - + '+line5\n' - + '-line4\n'); + + '+line5\n'); }); it('should output no newline at end of file message', function() { @@ -66,9 +66,9 @@ describe('#createPatch', function() { + ' line1\n' + ' line2\n' + ' line3\n' + + '-line4\n' + '+line4\n' - + '\\ No newline at end of file\n' - + '-line4\n'); + + '\\ No newline at end of file\n'); diff.createPatch('test', 'line1\nline2\nline3\nline4', 'line1\nline2\nline3\nline4\n', 'header1', 'header2').should.equal( 'Index: test\n' @@ -79,9 +79,9 @@ describe('#createPatch', function() { + ' line1\n' + ' line2\n' + ' line3\n' - + '+line4\n' + '-line4\n' - + '\\ No newline at end of file\n'); + + '\\ No newline at end of file\n' + + '+line4\n'); diff.createPatch('test', 'line11\nline2\nline3\nline4', 'line1\nline2\nline3\nline4', 'header1', 'header2').should.equal( 'Index: test\n' @@ -89,8 +89,8 @@ describe('#createPatch', function() { + '--- test\theader1\n' + '+++ test\theader2\n' + '@@ -1,4 +1,4 @@\n' - + '+line1\n' + '-line11\n' + + '+line1\n' + ' line2\n' + ' line3\n' + ' line4\n' @@ -102,8 +102,8 @@ describe('#createPatch', function() { + '--- test\theader1\n' + '+++ test\theader2\n' + '@@ -1,5 +1,5 @@\n' - + '+line1\n' + '-line11\n' + + '+line1\n' + ' line2\n' + ' line3\n' + ' line4\n' @@ -429,9 +429,9 @@ describe('#createPatch', function() { + '--- testFileName\tOld Header\n' + '+++ testFileName\tNew Header\n' + '@@ -1,5 +1,6 @@\n' + + '-value\n' + '+new value\n' + '+new value 2\n' - + '-value\n' + ' context\n' + ' context\n' + ' context\n' @@ -465,9 +465,9 @@ describe('#createPatch', function() { + ' context\n' + ' context\n' + ' context\n' + + '-value\n' + '+new value\n' + '+new value 2\n' - + '-value\n' + ' context\n' + ' context\n' + '\\ No newline at end of file\n'; diff --git a/test/diffTest.js b/test/diffTest.js index 42903348..fe034d0e 100644 --- a/test/diffTest.js +++ b/test/diffTest.js @@ -4,24 +4,24 @@ var diff = require('../diff'), describe('#diffWords', function() { it('should diff whitespace', function() { var diffResult = diff.diffWords('New Value', 'New ValueMoreData'); - diff.convertChangesToXML(diffResult).should.equal('New ValueMoreDataValue'); + diff.convertChangesToXML(diffResult).should.equal('New ValueValueMoreData'); }); it('should diff multiple whitespace values', function() { var diffResult = diff.diffWords('New Value ', 'New ValueMoreData '); - diff.convertChangesToXML(diffResult).should.equal('New ValueMoreDataValue '); + diff.convertChangesToXML(diffResult).should.equal('New ValueValueMoreData '); }); // Diff on word boundary it('should diff on word boundaries', function() { var diffResult = diff.diffWords('New :Value:Test', 'New ValueMoreData '); - diff.convertChangesToXML(diffResult).should.equal('New ValueMoreData :Value:Test'); + diff.convertChangesToXML(diffResult).should.equal('New :Value:TestValueMoreData '); diffResult = diff.diffWords('New Value:Test', 'New Value:MoreData '); - diff.convertChangesToXML(diffResult).should.equal('New Value:MoreData Test'); + diff.convertChangesToXML(diffResult).should.equal('New Value:TestMoreData '); diffResult = diff.diffWords('New Value-Test', 'New Value:MoreData '); - diff.convertChangesToXML(diffResult).should.equal('New Value:MoreData -Test'); + diff.convertChangesToXML(diffResult).should.equal('New Value-Test:MoreData '); diffResult = diff.diffWords('New Value', 'New Value:MoreData '); diff.convertChangesToXML(diffResult).should.equal('New Value:MoreData '); @@ -55,7 +55,7 @@ describe('#diffWords', function() { // With without anchor (the Heckel algorithm error case) it('should diff when there is no anchor value', function() { var diffResult = diff.diffWords('New Value New Value', 'Value Value New New'); - diff.convertChangesToXML(diffResult).should.equal('ValueNew Value New NewValue'); + diff.convertChangesToXML(diffResult).should.equal('NewValue Value New ValueNew'); }); }); @@ -63,7 +63,7 @@ describe('#diffWords - async', function() { it('should diff whitespace', function(done) { diff.diffWords('New Value', 'New ValueMoreData', function(err, diffResult) { should(err).not.exist; - diff.convertChangesToXML(diffResult).should.equal('New ValueMoreDataValue'); + diff.convertChangesToXML(diffResult).should.equal('New ValueValueMoreData'); done(); }); }); @@ -71,7 +71,7 @@ describe('#diffWords - async', function() { it('should diff multiple whitespace values', function(done) { diff.diffWords('New Value ', 'New ValueMoreData ', function(err, diffResult) { should(err).not.exist; - diff.convertChangesToXML(diffResult).should.equal('New ValueMoreDataValue '); + diff.convertChangesToXML(diffResult).should.equal('New ValueValueMoreData '); done(); }); }); @@ -80,7 +80,7 @@ describe('#diffWords - async', function() { it('should diff on word boundaries', function(done) { diff.diffWords('New :Value:Test', 'New ValueMoreData ', function(err, diffResult) { should(err).not.exist; - diff.convertChangesToXML(diffResult).should.equal('New ValueMoreData :Value:Test'); + diff.convertChangesToXML(diffResult).should.equal('New :Value:TestValueMoreData '); done(); }); }); @@ -126,7 +126,7 @@ describe('#diffWords - async', function() { // With without anchor (the Heckel algorithm error case) it('should diff when there is no anchor value', function(done) { diff.diffWords('New Value New Value', 'Value Value New New', function(err, diffResult) { - diff.convertChangesToXML(diffResult).should.equal('ValueNew Value New NewValue'); + diff.convertChangesToXML(diffResult).should.equal('NewValue Value New ValueNew'); done(); }); }); @@ -135,7 +135,7 @@ describe('#diffWords - async', function() { describe('#diffWordsWithSpace', function() { it('should diff whitespace', function() { var diffResult = diff.diffWordsWithSpace('New Value', 'New ValueMoreData'); - diff.convertChangesToXML(diffResult).should.equal('New ValueMoreData Value'); + diff.convertChangesToXML(diffResult).should.equal('New Value ValueMoreData'); }); it('should diff multiple whitespace values', function() { @@ -155,12 +155,12 @@ describe('#diffChars', function() { describe('#diffSentences', function() { it('Should diff Sentences', function() { var diffResult = diff.diffSentences('New Value.', 'New ValueMoreData.'); - diff.convertChangesToXML(diffResult).should.equal('New ValueMoreData.New Value.'); + diff.convertChangesToXML(diffResult).should.equal('New Value.New ValueMoreData.'); }); it('should diff only the last sentence', function() { var diffResult = diff.diffSentences('Here im. Rock you like old man.', 'Here im. Rock you like hurricane.'); - diff.convertChangesToXML(diffResult).should.equal('Here im. Rock you like hurricane.Rock you like old man.'); + diff.convertChangesToXML(diffResult).should.equal('Here im. Rock you like old man.Rock you like hurricane.'); }); }); @@ -171,9 +171,9 @@ describe('#diffCss', function() { '.test,#value .test{margin-left:50px;margin-right:-40px}', '.test2, #value2 .test {\nmargin-top:50px;\nmargin-right:-400px;\n}'); diff.convertChangesToXML(diffResult).should.equal( - '.test2.test,#value #value2 .test {\n' - + 'margin-topmargin-left:50px;\n' - + 'margin-right:-400px;\n-40px}'); + '.test.test2,#value #value2 .test {' + + 'margin-left\nmargin-top:50px;\n' + + 'margin-right:-40px-400px;\n}'); }); }); @@ -183,7 +183,7 @@ describe('#diffLines', function() { var diffResult = diff.diffLines( 'line\nold value\nline', 'line\nnew value\nline'); - diff.convertChangesToXML(diffResult).should.equal('line\nnew value\nold value\nline'); + diff.convertChangesToXML(diffResult).should.equal('line\nold value\nnew value\nline'); }); it('should the same lines in diff', function() { var diffResult = diff.diffLines( @@ -196,21 +196,21 @@ describe('#diffLines', function() { var diffResult = diff.diffLines( 'line\nvalue \nline', 'line\nvalue\nline'); - diff.convertChangesToXML(diffResult).should.equal('line\nvalue\nvalue \nline'); + diff.convertChangesToXML(diffResult).should.equal('line\nvalue \nvalue\nline'); }); it('should handle windows line endings', function() { var diffResult = diff.diffLines( 'line\r\nold value \r\nline', 'line\r\nnew value\r\nline'); - diff.convertChangesToXML(diffResult).should.equal('line\r\nnew value\r\nold value \r\nline'); + diff.convertChangesToXML(diffResult).should.equal('line\r\nold value \r\nnew value\r\nline'); }); it('should handle empty lines', function() { var diffResult = diff.diffLines( 'line\n\nold value \n\nline', 'line\n\nnew value\n\nline'); - diff.convertChangesToXML(diffResult).should.equal('line\n\nnew value\nold value \n\nline'); + diff.convertChangesToXML(diffResult).should.equal('line\n\nold value \nnew value\n\nline'); }); it('should handle empty input', function() { @@ -227,7 +227,7 @@ describe('#TrimmedLineDiff', function() { var diffResult = diff.diffTrimmedLines( 'line\nold value\nline', 'line\nnew value\nline'); - diff.convertChangesToXML(diffResult).should.equal('line\nnew value\nold value\nline'); + diff.convertChangesToXML(diffResult).should.equal('line\nold value\nnew value\nline'); }); it('should the same lines in diff', function() { var diffResult = diff.diffTrimmedLines( @@ -247,7 +247,7 @@ describe('#TrimmedLineDiff', function() { var diffResult = diff.diffTrimmedLines( 'line\r\nold value \r\nline', 'line\r\nnew value\r\nline'); - diff.convertChangesToXML(diffResult).should.equal('line\r\nnew value\r\nold value\r\nline'); + diff.convertChangesToXML(diffResult).should.equal('line\r\nold value\r\nnew value\r\nline'); }); }); @@ -326,6 +326,6 @@ describe('convertToDMP', function() { var diffResult = diff.diffWords('New Value ', 'New ValueMoreData '); diff.convertChangesToDMP(diffResult).should.eql( - [[0, 'New '], [1, 'ValueMoreData'], [-1, 'Value'], [0, ' ']]); + [[0, 'New '], [-1, 'Value'], [1, 'ValueMoreData'], [0, ' ']]); }); }); From 98a5bb2503cbafb2431ba46e9ef6b7d4bc7adfae Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 6 May 2015 09:20:53 -0500 Subject: [PATCH 011/436] Only use ASCII new lines for patch tokenization Fixes #57 --- diff.js | 26 ++++++++++++++++++++++++-- test/applyPatch.js | 8 ++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/diff.js b/diff.js index 482a0a71..421854a1 100644 --- a/diff.js +++ b/diff.js @@ -322,7 +322,7 @@ for (var i = 0; i < lines.length; i++) { var line = lines[i], lastLine = lines[i - 1], - lastLineLastChar = lastLine ? lastLine[lastLine.length - 1] : ''; + lastLineLastChar = lastLine && lastLine[lastLine.length - 1]; // Merge lines that may contain windows new lines if (line === '\n' && lastLineLastChar === '\r') { @@ -342,6 +342,28 @@ return retLines; }; + var PatchDiff = new Diff(); + PatchDiff.tokenize = function(value) { + var ret = [], + linesAndNewlines = value.split(/(\n|\r\n)/); + + // Ignore the final empty token that occurs if the string ends with a new line + if (!linesAndNewlines[linesAndNewlines.length - 1]) { + linesAndNewlines.pop(); + } + + // Merge the content and line separators into single tokens + for (var i = 0; i < linesAndNewlines.length; i++) { + var line = linesAndNewlines[i]; + + if (i % 2) { + ret[ret.length - 1] += line; + } else { + ret.push(line); + } + } + return ret; + }; var SentenceDiff = new Diff(); SentenceDiff.tokenize = function(value) { @@ -387,7 +409,7 @@ ret.push('--- ' + oldFileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader)); ret.push('+++ ' + newFileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader)); - var diff = LineDiff.diff(oldStr, newStr); + var diff = PatchDiff.diff(oldStr, newStr); diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier // Formats a given set of lines for printing as context lines in a patch diff --git a/test/applyPatch.js b/test/applyPatch.js index 304ded65..0ca02601 100644 --- a/test/applyPatch.js +++ b/test/applyPatch.js @@ -305,4 +305,12 @@ describe('#applyPatch', function() { + ' line5\n') .should.equal(false); }); + + it('should work with unicode newline characters', function() { + var oldtext = 'AAAAAAAAAAAAAAAA\n\n'; + var newtext = 'AAAAAAAAAAAAAAAA\nBBBBBB' + String.fromCharCode(0x2028) + '\nCCCCCCCCCCCCCCCCCC\n\n'; + + var diffed = diff.createPatch('test', oldtext, newtext); + diff.applyPatch(oldtext, diffed).should.equal(newtext); + }); }); From f8e79fd6d9006c3cefb26ed41e0e416738bba57c Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 6 May 2015 09:21:02 -0500 Subject: [PATCH 012/436] Remove unused var --- test/canonicalize.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/canonicalize.js b/test/canonicalize.js index 7df0600c..568de919 100644 --- a/test/canonicalize.js +++ b/test/canonicalize.js @@ -1,5 +1,3 @@ -const VERBOSE = false; - var diff = require('../diff'); function getKeys(obj) { From 9582b04f891fb8642931771f2939a3bf4acb9a68 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 6 May 2015 09:21:14 -0500 Subject: [PATCH 013/436] Add coverage checking at 100% --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d748af96..903a59c2 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ }, "main": "./diff", "scripts": { - "test": "istanbul cover node_modules/.bin/_mocha test/*.js" + "test": "istanbul cover node_modules/.bin/_mocha test/*.js && istanbul check-coverage --statements 100 --functions 100 --branches 100 --lines 100 coverage/coverage.json" }, "dependencies": {}, "devDependencies": { From 9e81c0fe861ba850dade04404cb0fd55772b0f14 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 6 May 2015 09:31:18 -0500 Subject: [PATCH 014/436] Update release notes --- release-notes.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/release-notes.md b/release-notes.md index 87c7b600..58ef9887 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,7 +2,14 @@ ## Development -[Commits](https://github.com/kpdecker/jsdiff/compare/v1.3.2...master) +[Commits](https://github.com/kpdecker/jsdiff/compare/v1.4.0...master) + +## v1.4.0 - May 6th, 2015 +- [#57](https://github.com/kpdecker/jsdiff/issues/57) - createPatch -> applyPatch failed. ([@mog422](https://api.github.com/users/mog422)) +- [#56](https://github.com/kpdecker/jsdiff/pull/56) - Two files patch ([@rgeissert](https://api.github.com/users/rgeissert)) +- [#14](https://github.com/kpdecker/jsdiff/issues/14) - Flip added and removed order? ([@jakesandlund](https://api.github.com/users/jakesandlund)) + +[Commits](https://github.com/kpdecker/jsdiff/compare/v1.3.2...v1.4.0) ## v1.3.2 - March 30th, 2015 - [#53](https://github.com/kpdecker/jsdiff/pull/53) - Updated README.MD with Bower installation instructions ([@ofbriggs](https://api.github.com/users/ofbriggs)) From 27a750e9116e6ade6303bc24a9be72f6845e00ed Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 6 May 2015 09:31:20 -0500 Subject: [PATCH 015/436] v1.4.0 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index aa6862d1..c755b0ac 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jsdiff", - "version": "1.3.2", + "version": "1.4.0", "main": [ "diff.js" ], diff --git a/package.json b/package.json index 903a59c2..2bd81f89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff", - "version": "1.3.2", + "version": "1.4.0", "description": "A javascript text diff implementation.", "keywords": [ "diff", From f5d3bd15620fa96001b225752f6a5bf75f05a5bb Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Fri, 10 Jul 2015 20:07:25 +0800 Subject: [PATCH 016/436] Use svg instead of png to get better image quality --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b867e19a..7ff7fffc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # jsdiff -[![Build Status](https://secure.travis-ci.org/kpdecker/jsdiff.png)](http://travis-ci.org/kpdecker/jsdiff) +[![Build Status](https://secure.travis-ci.org/kpdecker/jsdiff.svg)](http://travis-ci.org/kpdecker/jsdiff) A javascript text differencing implementation. From b257c312567da10726cf92ec3510b206ca831a07 Mon Sep 17 00:00:00 2001 From: Quest Date: Wed, 22 Jul 2015 18:19:12 +0200 Subject: [PATCH 017/436] Allow retreiving the patch data as a list of hunk objects --- diff.js | 101 ++++++++++++++++++++++++---------------- test/createPatch.js | 76 +++++++++++++++++++++++++++++- test/structuredPatch.js | 26 +++++++++++ 3 files changed, 162 insertions(+), 41 deletions(-) create mode 100644 test/structuredPatch.js diff --git a/diff.js b/diff.js index 421854a1..1c9e5c5d 100644 --- a/diff.js +++ b/diff.js @@ -398,39 +398,22 @@ callback ); }, - - createTwoFilesPatch: function(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader) { - var ret = []; - - if (oldFileName == newFileName) { - ret.push('Index: ' + oldFileName); + + structuredPatch: function(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { + if(!options) { + options = { context: 4 }; } - ret.push('==================================================================='); - ret.push('--- ' + oldFileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader)); - ret.push('+++ ' + newFileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader)); - + var diff = PatchDiff.diff(oldStr, newStr); diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier - - // Formats a given set of lines for printing as context lines in a patch + function contextLines(lines) { return map(lines, function(entry) { return ' ' + entry; }); } - // Outputs the no newline at end of file warning if needed - function eofNL(curRange, i, current) { - var last = diff[diff.length - 2], - isLast = i === diff.length - 2, - isLastOfType = i === diff.length - 3 && current.added !== last.added; - - // Figure out if this is the last line for the given file and missing NL - if (!(/\n$/.test(current.value)) && (isLast || isLastOfType)) { - curRange.push('\\ No newline at end of file'); - } - } - + var hunks = []; var oldRangeStart = 0, newRangeStart = 0, curRange = [], - oldLine = 1, newLine = 1; + oldLine = 1, newLine = 1; for (var i = 0; i < diff.length; i++) { var current = diff[i], lines = current.lines || current.value.replace(/\n$/, '').split('\n'); @@ -444,7 +427,7 @@ newRangeStart = newLine; if (prev) { - curRange = contextLines(prev.lines.slice(-4)); + curRange = options.context > 0 ? contextLines(prev.lines.slice(-options.context)) : []; oldRangeStart -= curRange.length; newRangeStart -= curRange.length; } @@ -454,7 +437,6 @@ curRange.push.apply(curRange, map(lines, function(entry) { return (current.added ? '+' : '-') + entry; })); - eofNL(curRange, i, current); // Track the updated file position if (current.added) { @@ -466,21 +448,34 @@ // Identical context lines. Track line changes if (oldRangeStart) { // Close out any changes that have been output (or join overlapping) - if (lines.length <= 8 && i < diff.length - 2) { + if (lines.length <= options.context * 2 && i < diff.length - 2) { // Overlapping curRange.push.apply(curRange, contextLines(lines)); } else { // end the range and output - var contextSize = Math.min(lines.length, 4); - ret.push( - '@@ -' + oldRangeStart + ',' + (oldLine - oldRangeStart + contextSize) - + ' +' + newRangeStart + ',' + (newLine - newRangeStart + contextSize) - + ' @@'); - ret.push.apply(ret, curRange); - ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); - if (lines.length <= 4) { - eofNL(ret, i, current); + var contextSize = Math.min(lines.length, options.context); + var hunklines = []; + hunklines.push.apply(hunklines, curRange); + hunklines.push.apply(hunklines, contextLines(lines.slice(0, contextSize))); + + var hunk = { + oldStart: oldRangeStart, + oldLines: (oldLine - oldRangeStart + contextSize), + newStart: newRangeStart, + newLines: (newLine - newRangeStart + contextSize), + lines: hunklines + } + if(i >= diff.length - 2 && lines.length <= options.context) { + // EOF is inside this hunk + var oldEOFNewline = /\n$/.test(oldStr); + var newEOFNewline = /\n$/.test(newStr); + if(lines.length == 0 && !oldEOFNewline) { + hunklines.splice(hunk.oldLines, 0, '\\ No newline at end of file') + } else if (!oldEOFNewline || !newEOFNewline) { + hunklines.push('\\ No newline at end of file') + } } + hunks.push(hunk); oldRangeStart = 0; newRangeStart = 0; @@ -491,12 +486,40 @@ newLine += lines.length; } } + + return { + oldFileName: oldFileName, newFileName: newFileName, + oldHeader: oldHeader, newHeader: newHeader, + hunks: hunks + }; + }, + createTwoFilesPatch: function(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { + var diff = JsDiff.structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options); + + var ret = []; + if (oldFileName == newFileName) { + ret.push('Index: ' + oldFileName); + } + ret.push('==================================================================='); + ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader)); + ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader)); + + for(var i = 0; i < diff.hunks.length; i++) { + var hunk = diff.hunks[i]; + ret.push( + '@@ -' + hunk.oldStart + ',' + hunk.oldLines + + ' +' + hunk.newStart + ',' + hunk.newLines + + ' @@' + ); + ret.push.apply(ret, hunk.lines); + } + return ret.join('\n') + '\n'; }, - createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { - return JsDiff.createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader); + createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader, options) { + return JsDiff.createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options); }, applyPatch: function(oldStr, uniDiff) { diff --git a/test/createPatch.js b/test/createPatch.js index 62665acf..fea4aa83 100644 --- a/test/createPatch.js +++ b/test/createPatch.js @@ -56,7 +56,7 @@ describe('#createPatch', function() { + '+line5\n'); }); - it('should output no newline at end of file message', function() { + it('should output "no newline" at end of file message on new missing nl', function() { diff.createPatch('test', 'line1\nline2\nline3\nline4\n', 'line1\nline2\nline3\nline4', 'header1', 'header2').should.equal( 'Index: test\n' + '===================================================================\n' @@ -69,7 +69,9 @@ describe('#createPatch', function() { + '-line4\n' + '+line4\n' + '\\ No newline at end of file\n'); + }); + it('should output "no newline" at end of file message on old missing nl', function() { diff.createPatch('test', 'line1\nline2\nline3\nline4', 'line1\nline2\nline3\nline4\n', 'header1', 'header2').should.equal( 'Index: test\n' + '===================================================================\n' @@ -82,7 +84,9 @@ describe('#createPatch', function() { + '-line4\n' + '\\ No newline at end of file\n' + '+line4\n'); + }); + it('should output "no newline" at end of file message on context missing nl', function() { diff.createPatch('test', 'line11\nline2\nline3\nline4', 'line1\nline2\nline3\nline4', 'header1', 'header2').should.equal( 'Index: test\n' + '===================================================================\n' @@ -95,7 +99,9 @@ describe('#createPatch', function() { + ' line3\n' + ' line4\n' + '\\ No newline at end of file\n'); + }); + it('should not output no newline at end of file message when eof outside hunk', function() { diff.createPatch('test', 'line11\nline2\nline3\nline4\nline4\nline4\nline4', 'line1\nline2\nline3\nline4\nline4\nline4\nline4', 'header1', 'header2').should.equal( 'Index: test\n' + '===================================================================\n' @@ -422,7 +428,7 @@ describe('#createPatch', function() { + 'context\n' + 'context'; - it('should generate a patch', function() { + it('should generate a patch with default context size', function() { var expectedResult = 'Index: testFileName\n' + '===================================================================\n' @@ -475,6 +481,72 @@ describe('#createPatch', function() { var diffResult = diff.createPatch('testFileName', oldFile, newFile, 'Old Header', 'New Header'); diffResult.should.equal(expectedResult); }); + + it('should generatea a patch with context size 0', function() { + var expectedResult = + 'Index: testFileName\n' + + '===================================================================\n' + + '--- testFileName\tOld Header\n' + + '+++ testFileName\tNew Header\n' + + '@@ -1,1 +1,2 @@\n' + + '-value\n' + + '+new value\n' + + '+new value 2\n' + + '@@ -11,1 +12,0 @@\n' + + '-remove value\n' + + '@@ -21,1 +21,0 @@\n' + + '-remove value\n' + + '@@ -30,0 +29,1 @@\n' + + '+add value\n' + + '@@ -34,1 +34,2 @@\n' + + '-value\n' + + '+new value\n' + + '+new value 2\n'; + var diffResult = diff.createPatch('testFileName', oldFile, newFile, 'Old Header', 'New Header', { context: 0 }); + diffResult.should.equal(expectedResult); + }); + + it('should generate a patch with context size 2', function() { + var expectedResult = + 'Index: testFileName\n' + + '===================================================================\n' + + '--- testFileName\tOld Header\n' + + '+++ testFileName\tNew Header\n' + + '@@ -1,3 +1,4 @@\n' + + '-value\n' + + '+new value\n' + + '+new value 2\n' + + ' context\n' + + ' context\n' + + '@@ -9,5 +10,4 @@\n' + + ' context\n' + + ' context\n' + + '-remove value\n' + + ' context\n' + + ' context\n' + + '@@ -19,5 +19,4 @@\n' + + ' context\n' + + ' context\n' + + '-remove value\n' + + ' context\n' + + ' context\n' + + '@@ -28,9 +27,11 @@\n' + + ' context\n' + + ' context\n' + + '+add value\n' + + ' context\n' + + ' context\n' + + ' context\n' + + ' context\n' + + '-value\n' + + '+new value\n' + + '+new value 2\n' + + ' context\n' + + ' context\n' + + '\\ No newline at end of file\n'; + var diffResult = diff.createPatch('testFileName', oldFile, newFile, 'Old Header', 'New Header', { context: 2 }); + diffResult.should.equal(expectedResult); + }); it('should output headers only for identical files', function() { var expectedResult = diff --git a/test/structuredPatch.js b/test/structuredPatch.js new file mode 100644 index 00000000..08201b81 --- /dev/null +++ b/test/structuredPatch.js @@ -0,0 +1,26 @@ +const VERBOSE = false; + +var diff = require('../diff'); + +function log() { + VERBOSE && console.log.apply(console, arguments); +} + +describe('#structuredPatch', function() { + it('should handle files with the last line changed', function() { + var res = diff.structuredPatch( + 'oldfile', 'newfile', + 'line2\nline3\nline5\n', 'line2\nline3\nline4\nline5\n', + 'header1', 'header2' + ); + res.should.eql({ + oldFileName: 'oldfile', newFileName: 'newfile', + oldHeader: 'header1', newHeader: 'header2', + hunks: [{ + oldStart: 1, oldLines: 3, newStart: 1, newLines: 4, + lines: [' line2', ' line3', '+line4', ' line5'], + oldEOFNewline: true, newEOFNewline: true, + }] + }); + }); +}); From bc11bccc0fcebefc2655081928407fb548804d3b Mon Sep 17 00:00:00 2001 From: Quest Date: Wed, 22 Jul 2015 22:46:55 +0200 Subject: [PATCH 018/436] Test on structured patch method updated --- test/structuredPatch.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/structuredPatch.js b/test/structuredPatch.js index 08201b81..d88077c5 100644 --- a/test/structuredPatch.js +++ b/test/structuredPatch.js @@ -10,16 +10,15 @@ describe('#structuredPatch', function() { it('should handle files with the last line changed', function() { var res = diff.structuredPatch( 'oldfile', 'newfile', - 'line2\nline3\nline5\n', 'line2\nline3\nline4\nline5\n', + 'line2\nline3\nline4\n', 'line2\nline3\nline5', 'header1', 'header2' ); res.should.eql({ oldFileName: 'oldfile', newFileName: 'newfile', oldHeader: 'header1', newHeader: 'header2', hunks: [{ - oldStart: 1, oldLines: 3, newStart: 1, newLines: 4, - lines: [' line2', ' line3', '+line4', ' line5'], - oldEOFNewline: true, newEOFNewline: true, + oldStart: 1, oldLines: 3, newStart: 1, newLines: 3, + lines: [' line2', ' line3', '-line4', '+line5', '\\ No newline at end of file'], }] }); }); From 8209a6bf8becad36a010a1575b6c0e4df9f27636 Mon Sep 17 00:00:00 2001 From: Quest Date: Fri, 31 Jul 2015 01:32:20 +0200 Subject: [PATCH 019/436] As per @kpdecker comments to PR #62 --- diff.js | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/diff.js b/diff.js index 1c9e5c5d..66f4a02d 100644 --- a/diff.js +++ b/diff.js @@ -400,7 +400,7 @@ }, structuredPatch: function(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { - if(!options) { + if (!options) { options = { context: 4 }; } @@ -413,7 +413,7 @@ var hunks = []; var oldRangeStart = 0, newRangeStart = 0, curRange = [], - oldLine = 1, newLine = 1; + oldLine = 1, newLine = 1; for (var i = 0; i < diff.length; i++) { var current = diff[i], lines = current.lines || current.value.replace(/\n$/, '').split('\n'); @@ -454,25 +454,24 @@ } else { // end the range and output var contextSize = Math.min(lines.length, options.context); - var hunklines = []; - hunklines.push.apply(hunklines, curRange); - hunklines.push.apply(hunklines, contextLines(lines.slice(0, contextSize))); + curRange.push.apply(curRange, contextLines(lines.slice(0, contextSize))); var hunk = { oldStart: oldRangeStart, oldLines: (oldLine - oldRangeStart + contextSize), newStart: newRangeStart, newLines: (newLine - newRangeStart + contextSize), - lines: hunklines + lines: curRange } - if(i >= diff.length - 2 && lines.length <= options.context) { + if (i >= diff.length - 2 && lines.length <= options.context) { // EOF is inside this hunk var oldEOFNewline = /\n$/.test(oldStr); var newEOFNewline = /\n$/.test(newStr); - if(lines.length == 0 && !oldEOFNewline) { - hunklines.splice(hunk.oldLines, 0, '\\ No newline at end of file') + if (lines.length == 0 && !oldEOFNewline) { + // special case: old has no eol and no trailing context; no-nl can end up before adds + curRange.splice(hunk.oldLines, 0, '\\ No newline at end of file') } else if (!oldEOFNewline || !newEOFNewline) { - hunklines.push('\\ No newline at end of file') + curRange.push('\\ No newline at end of file') } } hunks.push(hunk); @@ -505,7 +504,7 @@ ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader)); ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader)); - for(var i = 0; i < diff.hunks.length; i++) { + for (var i = 0; i < diff.hunks.length; i++) { var hunk = diff.hunks[i]; ret.push( '@@ -' + hunk.oldStart + ',' + hunk.oldLines From cf440256a4548722319838be7b7bee44434e77aa Mon Sep 17 00:00:00 2001 From: Quest Date: Fri, 31 Jul 2015 16:25:00 +0200 Subject: [PATCH 020/436] Documentationn for structuredPatch --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 7ff7fffc..6f415f8c 100644 --- a/README.md +++ b/README.md @@ -62,11 +62,29 @@ or * `newStr` : New string value * `oldHeader` : Additional information to include in the old file header * `newHeader` : Additional information to include in thew new file header + * `options` : An object with options. Currently, only `context` is supported and describes how many lines of context should be included. * `JsDiff.createPatch(fileName, oldStr, newStr, oldHeader, newHeader)` - creates a unified diff patch. Just like JsDiff.createTwoFilesPatch, but with oldFileName being equal to newFileName. + + * `JsDiff.structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options)` - returns an object with an array of hunk objects. + + This method is similar to createTwoFilesPatch, but returns a data structure + suitable for further processing. Parameters are the same as createTwoFilesPatch. The data structure returned may look like this: + + ```js + { + oldFileName: 'oldfile', newFileName: 'newfile', + oldHeader: 'header1', newHeader: 'header2', + hunks: [{ + oldStart: 1, oldLines: 3, newStart: 1, newLines: 3, + lines: [' line2', ' line3', '-line4', '+line5', '\\ No newline at end of file'], + }] + } + ``` + * `JsDiff.applyPatch(oldStr, diffStr)` - applies a unified diff patch. Return a string containing new version of provided data. From b49299afd6f59e4b07369d07dd53b7f60a7f3c8f Mon Sep 17 00:00:00 2001 From: Quest Date: Fri, 31 Jul 2015 16:26:54 +0200 Subject: [PATCH 021/436] Documentationn for structuredPatch --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 6f415f8c..8308de6a 100644 --- a/README.md +++ b/README.md @@ -69,20 +69,20 @@ or Just like JsDiff.createTwoFilesPatch, but with oldFileName being equal to newFileName. - * `JsDiff.structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options)` - returns an object with an array of hunk objects. - - This method is similar to createTwoFilesPatch, but returns a data structure - suitable for further processing. Parameters are the same as createTwoFilesPatch. The data structure returned may look like this: - - ```js - { - oldFileName: 'oldfile', newFileName: 'newfile', - oldHeader: 'header1', newHeader: 'header2', - hunks: [{ - oldStart: 1, oldLines: 3, newStart: 1, newLines: 3, - lines: [' line2', ' line3', '-line4', '+line5', '\\ No newline at end of file'], - }] - } +* `JsDiff.structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options)` - returns an object with an array of hunk objects. + + This method is similar to createTwoFilesPatch, but returns a data structure + suitable for further processing. Parameters are the same as createTwoFilesPatch. The data structure returned may look like this: + +```js +{ + oldFileName: 'oldfile', newFileName: 'newfile', + oldHeader: 'header1', newHeader: 'header2', + hunks: [{ + oldStart: 1, oldLines: 3, newStart: 1, newLines: 3, + lines: [' line2', ' line3', '-line4', '+line5', '\\ No newline at end of file'], + }] +} ``` * `JsDiff.applyPatch(oldStr, diffStr)` - applies a unified diff patch. From 30189fbe094fd0eca9e89539cc816bf9e74fa3f0 Mon Sep 17 00:00:00 2001 From: Quest Date: Fri, 31 Jul 2015 16:30:03 +0200 Subject: [PATCH 022/436] Documentationn for structuredPatch --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8308de6a..65ea13f2 100644 --- a/README.md +++ b/README.md @@ -74,15 +74,15 @@ or This method is similar to createTwoFilesPatch, but returns a data structure suitable for further processing. Parameters are the same as createTwoFilesPatch. The data structure returned may look like this: -```js -{ - oldFileName: 'oldfile', newFileName: 'newfile', - oldHeader: 'header1', newHeader: 'header2', - hunks: [{ - oldStart: 1, oldLines: 3, newStart: 1, newLines: 3, - lines: [' line2', ' line3', '-line4', '+line5', '\\ No newline at end of file'], - }] -} + ```js + { + oldFileName: 'oldfile', newFileName: 'newfile', + oldHeader: 'header1', newHeader: 'header2', + hunks: [{ + oldStart: 1, oldLines: 3, newStart: 1, newLines: 3, + lines: [' line2', ' line3', '-line4', '+line5', '\\ No newline at end of file'], + }] + } ``` * `JsDiff.applyPatch(oldStr, diffStr)` - applies a unified diff patch. From 091aeef6fc5fc8ac1247758ffb608dde4090f6a1 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 4 Aug 2015 17:05:30 -0500 Subject: [PATCH 023/436] Add eslint style checking to build --- .eslintrc | 178 ++++++++++++++++++++++++++++++++++++++++ diff.js | 38 ++++----- package.json | 3 +- test/.eslintrc | 14 ++++ test/createPatch.js | 10 +-- test/structuredPatch.js | 16 ++-- 6 files changed, 223 insertions(+), 36 deletions(-) create mode 100644 .eslintrc create mode 100644 test/.eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..15ecb91b --- /dev/null +++ b/.eslintrc @@ -0,0 +1,178 @@ +{ + "env": { + "browser": true + }, + "rules": { + // Possible Errors // + //-----------------// + + "comma-dangle": [2, "never"], + "no-cond-assign": [2, "except-parens"], + + // Allow for debugging + "no-console": 1, + + "no-constant-condition": 2, + "no-control-regex": 2, + + // Allow for debugging + "no-debugger": 1, + + "no-dupe-args": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-empty": 2, + "no-empty-character-class": 2, + "no-ex-assign": 2, + "no-extra-boolean-cast": 2, + "no-extra-parens": [2, "functions"], + "no-extra-semi": 2, + "no-func-assign": 2, + + // Stylistic... might consider disallowing in the future + "no-inner-declarations": 0, + + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-negated-in-lhs": 2, + "no-obj-calls": 2, + "no-regex-spaces": 2, + "no-sparse-arrays": 0, + + // Optimizer and coverage will handle/highlight this and can be useful for debugging + "no-unreachable": 1, + + "use-isnan": 2, + "valid-jsdoc": 0, + "valid-typeof": 2, + + + // Best Practices // + //----------------// + "block-scoped-var": 0, + "complexity": 0, + "consistent-return": 0, + "curly": 2, + "default-case": 1, + "dot-notation": [2, {"allowKeywords": false}], + "eqeqeq": 0, + "guard-for-in": 1, + "no-alert": 2, + "no-caller": 2, + "no-div-regex": 1, + "no-else-return": 0, + "no-empty-label": 2, + "no-eq-null": 0, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-fallthrough": 2, + "no-floating-decimal": 2, + "no-implied-eval": 2, + "no-iterator": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-loop-func": 0, + "no-multi-spaces": 2, + "no-multi-str": 1, + "no-native-reassign": 2, + "no-new": 2, + "no-new-func": 2, + "no-new-wrappers": 2, + "no-octal": 2, + "no-octal-escape": 2, + "no-param-reassign": 0, + "no-process-env": 2, + "no-proto": 2, + "no-redeclare": 2, + "no-return-assign": 2, + "no-script-url": 2, + "no-self-compare": 2, + "no-sequences": 2, + "no-throw-literal": 2, + "no-unused-expressions": 2, + "no-void": 0, + "no-warning-comments": 1, + "no-with": 2, + "radix": 2, + "vars-on-top": 0, + "wrap-iife": 2, + "yoda": 0, + + + // Strict // + //--------// + "strict": 0, + + + // Variables // + //-----------// + "no-catch-shadow": 2, + "no-delete-var": 2, + "no-label-var": 2, + "no-shadow": 0, + "no-shadow-restricted-names": 0, + "no-undef": 2, + "no-undef-init": 2, + "no-undefined": 0, + "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], + "no-use-before-define": [2, "nofunc"], + + + // Node.js // + //---------// + // Others left to environment defaults + "no-mixed-requires": 0, + + + // Stylistic // + //-----------// + "indent": 0, + "brace-style": [2, "1tbs", {"allowSingleLine": true}], + "camelcase": 2, + "comma-spacing": [2, {"before": false, "after": true}], + "comma-style": [2, "last"], + "consistent-this": [1, "self"], + "eol-last": 2, + "func-names": 0, + "func-style": [2, "declaration"], + "key-spacing": [2, { + "beforeColon": false, + "afterColon": true + }], + "max-nested-callbacks": 0, + "new-cap": 2, + "new-parens": 2, + "newline-after-var": 0, + "no-array-constructor": 2, + "no-continue": 0, + "no-inline-comments": 0, + "no-lonely-if": 2, + "no-mixed-spaces-and-tabs": 2, + "no-multiple-empty-lines": 0, + "no-nested-ternary": 1, + "no-new-object": 2, + "no-spaced-func": 2, + "no-ternary": 0, + "no-trailing-spaces": 2, + "no-underscore-dangle": 0, + "one-var": 0, + "operator-assignment": 0, + "padded-blocks": 0, + "quote-props": [2, "as-needed", {"keywords": true}], + "quotes": [2, "single", "avoid-escape"], + "semi": 2, + "semi-spacing": [2, {"before": false, "after": true}], + "sort-vars": 0, + "space-after-keywords": [2, "always"], + "space-before-blocks": [2, "always"], + "space-before-function-paren": [2, {"anonymous": "never", "named": "never"}], + "space-in-brackets": 0, + "space-in-parens": [2, "never"], + "space-infix-ops": 2, + "space-return-throw-case": 2, + "space-unary-ops": 2, + "spaced-comment": [2, "always"], + "wrap-regex": 1 + } +} \ No newline at end of file diff --git a/diff.js b/diff.js index 66f4a02d..dae487df 100644 --- a/diff.js +++ b/diff.js @@ -17,7 +17,7 @@ (function(global, undefined) { var objectPrototypeToString = Object.prototype.toString; - /*istanbul ignore next*/ + /* istanbul ignore next*/ function map(arr, mapper, that) { if (Array.prototype.map) { return Array.prototype.map.call(arr, mapper, that); @@ -238,7 +238,7 @@ (function exec() { setTimeout(function() { // This should not happen, but we want to be safe. - /*istanbul ignore next */ + /* istanbul ignore next */ if (editLength > maxEditLength) { return callback(); } @@ -398,15 +398,15 @@ callback ); }, - + structuredPatch: function(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { if (!options) { options = { context: 4 }; } - + var diff = PatchDiff.diff(oldStr, newStr); diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier - + function contextLines(lines) { return map(lines, function(entry) { return ' ' + entry; }); } @@ -456,22 +456,22 @@ var contextSize = Math.min(lines.length, options.context); curRange.push.apply(curRange, contextLines(lines.slice(0, contextSize))); - var hunk = { - oldStart: oldRangeStart, - oldLines: (oldLine - oldRangeStart + contextSize), - newStart: newRangeStart, + var hunk = { + oldStart: oldRangeStart, + oldLines: (oldLine - oldRangeStart + contextSize), + newStart: newRangeStart, newLines: (newLine - newRangeStart + contextSize), lines: curRange - } + }; if (i >= diff.length - 2 && lines.length <= options.context) { // EOF is inside this hunk - var oldEOFNewline = /\n$/.test(oldStr); - var newEOFNewline = /\n$/.test(newStr); + var oldEOFNewline = (/\n$/.test(oldStr)); + var newEOFNewline = (/\n$/.test(newStr)); if (lines.length == 0 && !oldEOFNewline) { // special case: old has no eol and no trailing context; no-nl can end up before adds - curRange.splice(hunk.oldLines, 0, '\\ No newline at end of file') + curRange.splice(hunk.oldLines, 0, '\\ No newline at end of file'); } else if (!oldEOFNewline || !newEOFNewline) { - curRange.push('\\ No newline at end of file') + curRange.push('\\ No newline at end of file'); } } hunks.push(hunk); @@ -485,7 +485,7 @@ newLine += lines.length; } } - + return { oldFileName: oldFileName, newFileName: newFileName, oldHeader: oldHeader, newHeader: newHeader, @@ -513,7 +513,7 @@ ); ret.push.apply(ret, hunk.lines); } - + return ret.join('\n') + '\n'; }, @@ -628,12 +628,12 @@ canonicalize: canonicalize }; - /*istanbul ignore next */ - /*global module */ + /* istanbul ignore next */ + /* global module */ if (typeof module !== 'undefined' && module.exports) { module.exports = JsDiff; } else if (typeof define === 'function' && define.amd) { - /*global define */ + /* global define */ define([], function() { return JsDiff; }); } else if (typeof global.JsDiff === 'undefined') { global.JsDiff = JsDiff; diff --git a/package.json b/package.json index 2bd81f89..70764eed 100644 --- a/package.json +++ b/package.json @@ -28,11 +28,12 @@ }, "main": "./diff", "scripts": { - "test": "istanbul cover node_modules/.bin/_mocha test/*.js && istanbul check-coverage --statements 100 --functions 100 --branches 100 --lines 100 coverage/coverage.json" + "test": "eslint *.js test/ && istanbul cover node_modules/.bin/_mocha test/*.js && istanbul check-coverage --statements 100 --functions 100 --branches 100 --lines 100 coverage/coverage.json" }, "dependencies": {}, "devDependencies": { "colors": "^1.1.0", + "eslint": "^1.0.0", "istanbul": "^0.3.2", "mocha": "^2.2.4", "should": "^6.0.1" diff --git a/test/.eslintrc b/test/.eslintrc new file mode 100644 index 00000000..bc43bc2e --- /dev/null +++ b/test/.eslintrc @@ -0,0 +1,14 @@ +{ + "env": { + "node": true, + "mocha": true + }, + "rules": { + // Disabling for tests, for now. + "no-unused-expressions": 0, + "no-path-concat": 0, + + "no-var": 0, + "no-console": 0 + } +} \ No newline at end of file diff --git a/test/createPatch.js b/test/createPatch.js index fea4aa83..365e7c2c 100644 --- a/test/createPatch.js +++ b/test/createPatch.js @@ -1,4 +1,4 @@ -const VERBOSE = false; +var VERBOSE = false; var diff = require('../diff'); @@ -314,11 +314,11 @@ describe('#createPatch', function() { var largeNewValue = largeTest, len = largeTest.length, count = nextRandom() % 20, - removeBound = len-(count*100), + removeBound = len - (count * 100), logData = []; for (; count > 0; count--) { var removePos = nextRandom() % removeBound, - removeLength = 1+nextRandom()%100; + removeLength = 1 + nextRandom() % 100; logData.push('(' + removePos + ', ' + removeLength + ')'); largeNewValue = largeNewValue.substring(0, removePos) + largeNewValue.substring(removePos + removeLength); @@ -481,7 +481,7 @@ describe('#createPatch', function() { var diffResult = diff.createPatch('testFileName', oldFile, newFile, 'Old Header', 'New Header'); diffResult.should.equal(expectedResult); }); - + it('should generatea a patch with context size 0', function() { var expectedResult = 'Index: testFileName\n' @@ -505,7 +505,7 @@ describe('#createPatch', function() { var diffResult = diff.createPatch('testFileName', oldFile, newFile, 'Old Header', 'New Header', { context: 0 }); diffResult.should.equal(expectedResult); }); - + it('should generate a patch with context size 2', function() { var expectedResult = 'Index: testFileName\n' diff --git a/test/structuredPatch.js b/test/structuredPatch.js index d88077c5..6d3d4ef9 100644 --- a/test/structuredPatch.js +++ b/test/structuredPatch.js @@ -1,24 +1,18 @@ -const VERBOSE = false; - var diff = require('../diff'); -function log() { - VERBOSE && console.log.apply(console, arguments); -} - describe('#structuredPatch', function() { it('should handle files with the last line changed', function() { var res = diff.structuredPatch( - 'oldfile', 'newfile', - 'line2\nline3\nline4\n', 'line2\nline3\nline5', + 'oldfile', 'newfile', + 'line2\nline3\nline4\n', 'line2\nline3\nline5', 'header1', 'header2' ); res.should.eql({ - oldFileName: 'oldfile', newFileName: 'newfile', + oldFileName: 'oldfile', newFileName: 'newfile', oldHeader: 'header1', newHeader: 'header2', - hunks: [{ + hunks: [{ oldStart: 1, oldLines: 3, newStart: 1, newLines: 3, - lines: [' line2', ' line3', '-line4', '+line5', '\\ No newline at end of file'], + lines: [' line2', ' line3', '-line4', '+line5', '\\ No newline at end of file'] }] }); }); From f01f57a1c65183e62fe6cecd6fd7f55951feac8c Mon Sep 17 00:00:00 2001 From: kpdecker Date: Tue, 4 Aug 2015 17:09:43 -0500 Subject: [PATCH 024/436] Run removeEmpty on all tokenize calls This is the most likely behavior that users will want. Should it not be desired for whatever reason, subclasses may override the removeEmpty field. Fixes #64 --- diff.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/diff.js b/diff.js index dae487df..ba1b063b 100644 --- a/diff.js +++ b/diff.js @@ -33,15 +33,6 @@ function clonePath(path) { return { newPos: path.newPos, components: path.components.slice(0) }; } - function removeEmpty(array) { - var ret = []; - for (var i = 0; i < array.length; i++) { - if (array[i]) { - ret.push(array[i]); - } - } - return ret; - } function escapeHTML(s) { var n = s; n = n.replace(/&/g, '&'); @@ -170,8 +161,8 @@ return done([{ value: newString, added: true }]); } - newString = this.tokenize(newString); - oldString = this.tokenize(oldString); + newString = this.removeEmpty(this.tokenize(newString)); + oldString = this.removeEmpty(this.tokenize(oldString)); var newLen = newString.length, oldLen = oldString.length; var editLength = 1; @@ -293,6 +284,15 @@ var reWhitespace = /\S/; return left === right || (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)); }, + removeEmpty: function(array) { + var ret = []; + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + return ret; + }, tokenize: function(value) { return value.split(''); } @@ -303,12 +303,12 @@ var WordDiff = new Diff(true); var WordWithSpaceDiff = new Diff(); WordDiff.tokenize = WordWithSpaceDiff.tokenize = function(value) { - return removeEmpty(value.split(/(\s+|\b)/)); + return value.split(/(\s+|\b)/); }; var CssDiff = new Diff(true); CssDiff.tokenize = function(value) { - return removeEmpty(value.split(/([{}:;,]|\s+)/)); + return value.split(/([{}:;,]|\s+)/); }; var LineDiff = new Diff(); @@ -367,7 +367,7 @@ var SentenceDiff = new Diff(); SentenceDiff.tokenize = function(value) { - return removeEmpty(value.split(/(\S.+?[.!?])(?=\s+|$)/)); + return value.split(/(\S.+?[.!?])(?=\s+|$)/); }; var JsonDiff = new Diff(); From bcb87ec2f8383013bf530e653b264c23c518bd10 Mon Sep 17 00:00:00 2001 From: kpdecker Date: Wed, 5 Aug 2015 12:58:52 -0500 Subject: [PATCH 025/436] Convert to webpack and babel built library --- .gitignore | 1 + Gruntfile.js | 90 +++++++++++++++++++++++++++++++++++++++ examples/node_example.js | 2 +- examples/web_example.html | 2 +- package.json | 19 +++++++-- diff.js => src/diff.js | 0 test/applyPatch.js | 2 +- test/canonicalize.js | 2 +- test/createPatch.js | 2 +- test/diffTest.js | 2 +- test/structuredPatch.js | 2 +- 11 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 Gruntfile.js rename diff.js => src/diff.js (100%) diff --git a/.gitignore b/.gitignore index df9af16b..b034aafe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ coverage node_modules npm-debug.log +dist \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 00000000..814174c4 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,90 @@ +/* esline-env node */ +/* eslint-disable no-process-env, camelcase */ +module.exports = function(grunt) { + + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + clean: ['dist'], + + eslint: { + options: { + }, + files: [ + 'src/**/*.js', + 'test/**/*.js' + ] + }, + + clean: ['dist'], + + webpack: { + options: { + context: __dirname, + module: { + loaders: [ + // the optional 'runtime' transformer tells babel to require the runtime instead of inlining it. + { test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader?optional=runtime&loose=es6.modules&auxiliaryCommentBefore=istanbul%20ignore%20next' } + ] + }, + output: { + path: 'dist/', + library: 'JsDiff', + libraryTarget: 'umd' + } + }, + dist: { + entry: './src/diff.js', + output: { + filename: 'diff.js' + } + } + }, + + mocha_istanbul: { + coverage: { + src: 'test' + } + }, + istanbul_check_coverage: { + 'default': { + options: { + coverageFolder: 'coverage*', // will check both coverage folders and merge the coverage results + check: { + statements: 100, + functions: 100, + branches: 100, + lines: 100 + } + } + } + }, + + watch: { + scripts: { + options: { + atBegin: true + }, + + files: ['src/**/*.js', 'test/**/*.js'], + tasks: ['build', 'test'] + } + } + }); + + // Build a new version of the library + this.registerTask('build', 'Builds a distributable version of the current project', ['eslint', 'webpack']); + this.registerTask('test', ['mocha_istanbul:coverage', 'istanbul_check_coverage']); + + // Load tasks from npm + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-eslint'); + grunt.loadNpmTasks('grunt-mocha-istanbul'); + grunt.loadNpmTasks('grunt-webpack'); + + grunt.registerTask('travis', 'default'); + + grunt.registerTask('dev', ['clean', 'watch']); + grunt.registerTask('default', ['clean', 'build', 'test']); +}; diff --git a/examples/node_example.js b/examples/node_example.js index 0c62ca1b..8e696caf 100644 --- a/examples/node_example.js +++ b/examples/node_example.js @@ -1,5 +1,5 @@ require('colors') -var jsdiff = require('../diff'); +var jsdiff = require('../'); var one = 'beep boop'; var other = 'beep boob blah'; diff --git a/examples/web_example.html b/examples/web_example.html index fc58df7e..7f2cb636 100644 --- a/examples/web_example.html +++ b/examples/web_example.html @@ -1,5 +1,5 @@

-
+
 
\ No newline at end of file
+
+

From d20f367576161b1a43e9c0d6d31542cdb3731fc8 Mon Sep 17 00:00:00 2001
From: Charlie Ozinga 
Date: Tue, 31 May 2016 12:25:47 -0600
Subject: [PATCH 141/436] Add a fix for applying 0-length destination patches

---
 src/patch/apply.js  |  1 +
 test/patch/apply.js | 17 +++++++++++++++++
 2 files changed, 18 insertions(+)

diff --git a/src/patch/apply.js b/src/patch/apply.js
index fda26ece..864d52da 100644
--- a/src/patch/apply.js
+++ b/src/patch/apply.js
@@ -81,6 +81,7 @@ export function applyPatch(source, uniDiff, options = {}) {
   for (let i = 0; i < hunks.length; i++) {
     let hunk = hunks[i],
         toPos = hunk.offset + hunk.newStart - 1;
+    if (hunk.newLines == 0) { toPos++; }
 
     for (let j = 0; j < hunk.lines.length; j++) {
       let line = hunk.lines[j],
diff --git a/test/patch/apply.js b/test/patch/apply.js
index dbf24277..3e278f61 100644
--- a/test/patch/apply.js
+++ b/test/patch/apply.js
@@ -416,6 +416,23 @@ describe('patch/apply', function() {
           + 'line5\n');
     });
 
+    it('should erase a file', function() {
+      expect(applyPatch(
+          'line1\n'
+          + 'line2\n'
+          + 'line3\n'
+          + 'line4\n',
+
+          '--- test\theader1\n'
+          + '+++ test\theader2\n'
+          + '@@ -1,4 +0,0 @@\n'
+          + '-line1\n'
+          + '-line2\n'
+          + '-line3\n'
+          + '-line4\n'))
+        .to.equal('');
+    });
+
     it('should allow custom line comparison', function() {
       expect(applyPatch(
           'line2\n'

From 06eaeee2711fcbda31e6c249a14ad75d7ba1cd5f Mon Sep 17 00:00:00 2001
From: kpdecker 
Date: Tue, 31 May 2016 15:06:24 -0500
Subject: [PATCH 142/436] Update release notes

---
 release-notes.md | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/release-notes.md b/release-notes.md
index 3172175c..9dfa3c0b 100644
--- a/release-notes.md
+++ b/release-notes.md
@@ -2,7 +2,14 @@
 
 ## Development
 
-[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.2...master)
+[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.3...master)
+
+## v2.2.3 - May 31st, 2016
+- [#118](https://github.com/kpdecker/jsdiff/pull/118) - Add a fix for applying 0-length destination patches ([@chaaz](https://api.github.com/users/chaaz))
+- [#115](https://github.com/kpdecker/jsdiff/pull/115) - Fixed grammar in README ([@krizalys](https://api.github.com/users/krizalys))
+- [#113](https://github.com/kpdecker/jsdiff/pull/113) - fix typo ([@vmazare](https://api.github.com/users/vmazare))
+
+[Commits](https://github.com/kpdecker/jsdiff/compare/v2.2.2...v2.2.3)
 
 ## v2.2.2 - March 13th, 2016
 - [#102](https://github.com/kpdecker/jsdiff/issues/102) - diffJson with dates, returns empty curly braces  ([@dr-dimitru](https://api.github.com/users/dr-dimitru))

From ec007c364e88c37ccc6b3f94a4cfe163d8389b85 Mon Sep 17 00:00:00 2001
From: kpdecker 
Date: Tue, 31 May 2016 15:06:36 -0500
Subject: [PATCH 143/436] v2.2.3

---
 components/bower.json     | 2 +-
 components/component.json | 2 +-
 package.json              | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/components/bower.json b/components/bower.json
index efdfba2c..7a8404b3 100644
--- a/components/bower.json
+++ b/components/bower.json
@@ -1,6 +1,6 @@
 {
   "name": "jsdiff",
-  "version": "2.2.2",
+  "version": "2.2.3",
   "main": [
     "diff.js"
   ],
diff --git a/components/component.json b/components/component.json
index ce9dd4e0..e90ac11d 100644
--- a/components/component.json
+++ b/components/component.json
@@ -6,7 +6,7 @@
     "diff",
     "text"
   ],
-  "version": "2.2.2",
+  "version": "2.2.3",
   "scripts": [ "diff.js" ],
   "main":  "diff.js",
   "license": "BSD"
diff --git a/package.json b/package.json
index 3194fcd0..f0466532 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "diff",
-  "version": "2.2.2",
+  "version": "2.2.3",
   "description": "A javascript text diff implementation.",
   "keywords": [
     "diff",

From 6c829046e2a41ab363a3178d2c425dcc1a6840c2 Mon Sep 17 00:00:00 2001
From: wifiextender 
Date: Thu, 2 Jun 2016 02:06:52 +0200
Subject: [PATCH 144/436] Do single reflow

---
 README.md                 | 19 ++++++++++++-------
 examples/web_example.html | 18 ++++++++++++------
 2 files changed, 24 insertions(+), 13 deletions(-)

diff --git a/README.md b/README.md
index 39842cc2..dab7189d 100644
--- a/README.md
+++ b/README.md
@@ -156,23 +156,28 @@ Basic example in a web page
 

 
 
 ```
 
diff --git a/examples/web_example.html b/examples/web_example.html
index 7a5f52e9..c4c1ed42 100644
--- a/examples/web_example.html
+++ b/examples/web_example.html
@@ -1,22 +1,28 @@