diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..7fa83f32 --- /dev/null +++ b/.babelrc @@ -0,0 +1,6 @@ +{ + "sourceMaps": "inline", + "presets": ["es3", "es2015-mod"], + "auxiliaryCommentBefore": "istanbul ignore start", + "auxiliaryCommentAfter": "istanbul ignore end" +} diff --git a/.npmignore b/.npmignore index 0840f4bc..b5b2b76a 100644 --- a/.npmignore +++ b/.npmignore @@ -13,3 +13,4 @@ Gruntfile.js index.html karma.conf.js style.css +.babelrc diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 989a6c00..96f4530a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,10 +15,10 @@ Generally we like to see pull requests that ``` npm install -grunt +npm test ```` -The `grunt dev` implements watching for tests within Node and `karma start` may be used for manual testing in browsers. +The `npm test -- dev` implements watching for tests within Node and `karma start` may be used for manual testing in browsers. If you notice any problems, please report them to the GitHub issue tracker at [http://github.com/kpdecker/jsdiff/issues](http://github.com/kpdecker/jsdiff/issues). diff --git a/README.md b/README.md index ec2363fe..5747fe36 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,9 @@ bower install jsdiff --save * `JsDiff.diffArrays(oldArr, newArr[, options])` - diffs two arrays, comparing each item for strict equality (===). + Options + * `comparator`: `function(left, right)` for custom equality checks + Returns a list of change objects (See below). * `JsDiff.createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader)` - creates a unified diff patch. @@ -104,7 +107,7 @@ bower install jsdiff --save The optional `options` object may have the following keys: - `fuzzFactor`: Number of lines that are allowed to differ before rejecting a patch. Defaults to 0. - - `compareLine(lineNumber, line, operation, patchContent)`: Callback used to compare to given lines to determine if they should be considered equal when patching. Defaults to strict equality but may be overriden to provide fuzzier comparison. Should return false if the lines should be rejected. + - `compareLine(lineNumber, line, operation, patchContent)`: Callback used to compare to given lines to determine if they should be considered equal when patching. Defaults to strict equality but may be overridden to provide fuzzier comparison. Should return false if the lines should be rejected. * `JsDiff.applyPatches(patch, options)` - applies one or more patches. @@ -138,7 +141,7 @@ Note that some cases may omit a particular flag field. Comparison on the flag fi Basic example in Node ```js -require('colors') +require('colors'); var jsdiff = require('diff'); var one = 'beep boop'; @@ -154,7 +157,7 @@ diff.forEach(function(part){ process.stderr.write(part.value[color]); }); -console.log() +console.log(); ``` Running the above program should yield diff --git a/components/bower.json b/components/bower.json index fd486986..2b0707d6 100644 --- a/components/bower.json +++ b/components/bower.json @@ -1,6 +1,6 @@ { "name": "jsdiff", - "version": "3.3.1", + "version": "3.5.0", "main": [ "diff.js" ], diff --git a/components/component.json b/components/component.json index 8e262b88..790543cf 100644 --- a/components/component.json +++ b/components/component.json @@ -6,7 +6,7 @@ "diff", "text" ], - "version": "3.3.1", + "version": "3.5.0", "scripts": [ "diff.js" ], "main": "diff.js", "license": "BSD" diff --git a/package.json b/package.json index 36308d96..d2376bfb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diff", - "version": "3.3.1", + "version": "3.5.0", "description": "A javascript text diff implementation.", "keywords": [ "diff", @@ -63,14 +63,5 @@ "webpack": "^1.12.2", "webpack-dev-server": "^1.12.0" }, - "optionalDependencies": {}, - "babel": { - "sourceMaps": "inline", - "presets": [ - "es3", - "es2015-mod" - ], - "auxiliaryCommentBefore": "istanbul ignore start", - "auxiliaryCommentAfter": "istanbul ignore end" - } + "optionalDependencies": {} } diff --git a/release-notes.md b/release-notes.md index 43b9d2b3..0116a2b7 100644 --- a/release-notes.md +++ b/release-notes.md @@ -2,7 +2,36 @@ ## Development -[Commits](https://github.com/kpdecker/jsdiff/compare/v3.3.1...master) +[Commits](https://github.com/kpdecker/jsdiff/compare/v3.5.0...master) + +## v3.5.0 - March 4th, 2018 +- Omit redundant slice in join method of diffArrays - 1023590 +- Support patches with empty lines - fb0f208 +- Accept a custom JSON replacer function for JSON diffing - 69c7f0a +- Optimize parch header parser - 2aec429 +- Fix typos - e89c832 + +[Commits](https://github.com/kpdecker/jsdiff/compare/v3.5.0...v3.5.0) + +## v3.5.0 - March 4th, 2018 +- Omit redundant slice in join method of diffArrays - 1023590 +- Support patches with empty lines - fb0f208 +- Accept a custom JSON replacer function for JSON diffing - 69c7f0a +- Optimize parch header parser - 2aec429 +- Fix typos - e89c832 + +[Commits](https://github.com/kpdecker/jsdiff/compare/v3.4.0...v3.5.0) + +## v3.4.0 - October 7th, 2017 +- [#183](https://github.com/kpdecker/jsdiff/issues/183) - Feature request: ability to specify a custom equality checker for `diffArrays` +- [#173](https://github.com/kpdecker/jsdiff/issues/173) - Bug: diffArrays gives wrong result on array of booleans +- [#158](https://github.com/kpdecker/jsdiff/issues/158) - diffArrays will not compare the empty string in array? +- comparator for custom equality checks - 30e141e +- count oldLines and newLines when there are conflicts - 53bf384 +- Fix: diffArrays can compare falsey items - 9e24284 +- Docs: Replace grunt with npm test - 00e2f94 + +[Commits](https://github.com/kpdecker/jsdiff/compare/v3.3.1...v3.4.0) ## v3.3.1 - September 3rd, 2017 - [#141](https://github.com/kpdecker/jsdiff/issues/141) - Cannot apply patch because my file delimiter is "/r/n" instead of "/n" @@ -108,7 +137,7 @@ Compatibility notes: - [#69](https://github.com/kpdecker/jsdiff/issues/69) - Missing count ([@wfalkwallace](https://api.github.com/users/wfalkwallace)) - [#68](https://github.com/kpdecker/jsdiff/issues/68) - diffLines seems broken ([@wfalkwallace](https://api.github.com/users/wfalkwallace)) - [#60](https://github.com/kpdecker/jsdiff/issues/60) - Support multiple diff hunks ([@piranna](https://api.github.com/users/piranna)) -- [#54](https://github.com/kpdecker/jsdiff/issues/54) - Feature Reuqest: 3-way merge ([@mog422](https://api.github.com/users/mog422)) +- [#54](https://github.com/kpdecker/jsdiff/issues/54) - Feature Request: 3-way merge ([@mog422](https://api.github.com/users/mog422)) - [#42](https://github.com/kpdecker/jsdiff/issues/42) - Fuzz factor for applyPatch ([@stuartpb](https://api.github.com/users/stuartpb)) - Move whitespace ignore out of equals method - 542063c - Include source maps in babel output - 7f7ab21 @@ -141,7 +170,7 @@ Compatibility notes: - [#29](https://github.com/kpdecker/jsdiff/issues/29) - word tokenizer works only for 7 bit ascii ([@plasmagunman](https://api.github.com/users/plasmagunman)) Compatibility notes: -- `this.removeEmpty` is now called automatically for all instances. If this is not desired, this may be overriden on a per instance basis. +- `this.removeEmpty` is now called automatically for all instances. If this is not desired, this may be overridden on a per instance basis. - The library has been refactored to use some ES6 features. The external APIs should remain the same, but bower projects that directly referenced the repository will now have to point to the [components/jsdiff](https://github.com/components/jsdiff) repository. [Commits](https://github.com/kpdecker/jsdiff/compare/v1.4.0...v2.0.0) diff --git a/src/diff/array.js b/src/diff/array.js index 4c2a3292..99de08fc 100644 --- a/src/diff/array.js +++ b/src/diff/array.js @@ -1,8 +1,11 @@ import Diff from './base'; export const arrayDiff = new Diff(); -arrayDiff.tokenize = arrayDiff.join = function(value) { +arrayDiff.tokenize = function(value) { return value.slice(); }; +arrayDiff.join = arrayDiff.removeEmpty = function(value) { + return value; +}; export function diffArrays(oldArr, newArr, callback) { return arrayDiff.diff(oldArr, newArr, callback); } diff --git a/src/diff/base.js b/src/diff/base.js index 4a514b4d..fd346ffe 100644 --- a/src/diff/base.js +++ b/src/diff/base.js @@ -144,8 +144,12 @@ Diff.prototype = { }, equals(left, right) { - return left === right - || (this.options.ignoreCase && left.toLowerCase() === right.toLowerCase()); + if (this.options.comparator) { + return this.options.comparator(left, right); + } else { + return left === right + || (this.options.ignoreCase && left.toLowerCase() === right.toLowerCase()); + } }, removeEmpty(array) { let ret = []; @@ -208,10 +212,12 @@ function buildValues(diff, components, newString, oldString, useLongestToken) { } } - // Special case handle for when one terminal is ignored. For this case we merge the - // terminal into the prior string and drop the change. + // Special case handle for when one terminal is ignored (i.e. whitespace). + // For this case we merge the terminal into the prior string and drop the change. + // This is only available for string mode. let lastComponent = components[componentLen - 1]; if (componentLen > 1 + && typeof lastComponent.value === 'string' && (lastComponent.added || lastComponent.removed) && diff.equals('', lastComponent.value)) { components[componentLen - 2].value += lastComponent.value; diff --git a/src/diff/json.js b/src/diff/json.js index 126351b7..6c06c9ab 100644 --- a/src/diff/json.js +++ b/src/diff/json.js @@ -11,15 +11,9 @@ jsonDiff.useLongestToken = true; jsonDiff.tokenize = lineDiff.tokenize; jsonDiff.castInput = function(value) { - const {undefinedReplacement} = this.options; + const {undefinedReplacement, stringifyReplacer = (k, v) => typeof v === 'undefined' ? undefinedReplacement : v} = this.options; - return typeof value === 'string' ? value : JSON.stringify(canonicalize(value), function(k, v) { - if (typeof v === 'undefined') { - return undefinedReplacement; - } - - return v; - }, ' '); + return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' '); }; jsonDiff.equals = function(left, right) { return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')); @@ -28,11 +22,15 @@ jsonDiff.equals = function(left, right) { export function diffJson(oldObj, newObj, options) { return jsonDiff.diff(oldObj, newObj, options); } // 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. -export function canonicalize(obj, stack, replacementStack) { +// object that is already on the "stack" of items being processed. Accepts an optional replacer +export function canonicalize(obj, stack, replacementStack, replacer, key) { stack = stack || []; replacementStack = replacementStack || []; + if (replacer) { + obj = replacer(key, obj); + } + let i; for (i = 0; i < stack.length; i += 1) { @@ -48,7 +46,7 @@ export function canonicalize(obj, stack, replacementStack) { canonicalizedObj = new Array(obj.length); replacementStack.push(canonicalizedObj); for (i = 0; i < obj.length; i += 1) { - canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack); + canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key); } stack.pop(); replacementStack.pop(); @@ -74,7 +72,7 @@ export function canonicalize(obj, stack, replacementStack) { sortedKeys.sort(); for (i = 0; i < sortedKeys.length; i += 1) { key = sortedKeys[i]; - canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack); + canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack, replacer, key); } stack.pop(); replacementStack.pop(); diff --git a/src/patch/apply.js b/src/patch/apply.js index e830fc11..7bbdb6d0 100644 --- a/src/patch/apply.js +++ b/src/patch/apply.js @@ -34,8 +34,8 @@ export function applyPatch(source, uniDiff, options = {}) { function hunkFits(hunk, toPos) { for (let j = 0; j < hunk.lines.length; j++) { let line = hunk.lines[j], - operation = line[0], - content = line.substr(1); + operation = (line.length > 0 ? line[0] : ' '), + content = (line.length > 0 ? line.substr(1) : line); if (operation === ' ' || operation === '-') { // Context sanity check @@ -91,8 +91,8 @@ export function applyPatch(source, uniDiff, options = {}) { for (let j = 0; j < hunk.lines.length; j++) { let line = hunk.lines[j], - operation = line[0], - content = line.substr(1), + operation = (line.length > 0 ? line[0] : ' '), + content = (line.length > 0 ? line.substr(1) : line), delimiter = hunk.linedelimiters[j]; if (operation === ' ') { diff --git a/src/patch/merge.js b/src/patch/merge.js index d3b55bd2..1b1fa92e 100644 --- a/src/patch/merge.js +++ b/src/patch/merge.js @@ -4,27 +4,17 @@ import {parsePatch} from './parse'; import {arrayEqual, arrayStartsWith} from '../util/array'; export function calcLineCount(hunk) { - let conflicted = false; + const {oldLines, newLines} = calcOldNewLineCount(hunk.lines); - hunk.oldLines = 0; - hunk.newLines = 0; - - hunk.lines.forEach(function(line) { - if (typeof line !== 'string') { - conflicted = true; - return; - } - - if (line[0] === '+' || line[0] === ' ') { - hunk.newLines++; - } - if (line[0] === '-' || line[0] === ' ') { - hunk.oldLines++; - } - }); - - if (conflicted) { + if (oldLines !== undefined) { + hunk.oldLines = oldLines; + } else { delete hunk.oldLines; + } + + if (newLines !== undefined) { + hunk.newLines = newLines; + } else { delete hunk.newLines; } } @@ -347,3 +337,40 @@ function skipRemoveSuperset(state, removeChanges, delta) { state.index += delta; return true; } + +function calcOldNewLineCount(lines) { + let oldLines = 0; + let newLines = 0; + + lines.forEach(function(line) { + if (typeof line !== 'string') { + let myCount = calcOldNewLineCount(line.mine); + let theirCount = calcOldNewLineCount(line.theirs); + + if (oldLines !== undefined) { + if (myCount.oldLines === theirCount.oldLines) { + oldLines += myCount.oldLines; + } else { + oldLines = undefined; + } + } + + if (newLines !== undefined) { + if (myCount.newLines === theirCount.newLines) { + newLines += myCount.newLines; + } else { + newLines = undefined; + } + } + } else { + if (newLines !== undefined && (line[0] === '+' || line[0] === ' ')) { + newLines++; + } + if (oldLines !== undefined && (line[0] === '-' || line[0] === ' ')) { + oldLines++; + } + } + }); + + return {oldLines, newLines}; +} diff --git a/src/patch/parse.js b/src/patch/parse.js index 310e3a94..d1af8d3d 100755 --- a/src/patch/parse.js +++ b/src/patch/parse.js @@ -53,16 +53,16 @@ export function parsePatch(uniDiff, options = {}) { // Parses the --- and +++ headers, if none are found, no lines // are consumed. function parseFileHeader(index) { - const headerPattern = /^(---|\+\+\+)\s+([\S ]*)(?:\t(.*?)\s*)?$/; - const fileHeader = headerPattern.exec(diffstr[i]); + const fileHeader = (/^(---|\+\+\+)\s+(.*)$/).exec(diffstr[i]); if (fileHeader) { let keyPrefix = fileHeader[1] === '---' ? 'old' : 'new'; - let fileName = fileHeader[2].replace(/\\\\/g, '\\'); + const data = fileHeader[2].split('\t', 2); + let fileName = data[0].replace(/\\\\/g, '\\'); if (/^".*"$/.test(fileName)) { fileName = fileName.substr(1, fileName.length - 2); } index[keyPrefix + 'FileName'] = fileName; - index[keyPrefix + 'Header'] = fileHeader[3]; + index[keyPrefix + 'Header'] = (data[1] || '').trim(); i++; } @@ -95,7 +95,7 @@ export function parsePatch(uniDiff, options = {}) { && diffstr[i + 2].indexOf('@@') === 0) { break; } - let operation = diffstr[i][0]; + let operation = (diffstr[i].length == 0 && i != (diffstr.length - 1)) ? ' ' : diffstr[i][0]; if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') { hunk.lines.push(diffstr[i]); diff --git a/test/diff/array.js b/test/diff/array.js index 1dde77bc..33dcf61a 100644 --- a/test/diff/array.js +++ b/test/diff/array.js @@ -15,5 +15,65 @@ describe('diff/array', function() { {count: 1, value: [c], removed: true, added: undefined} ]); }); + it('should diff falsey values', function() { + const a = false; + const b = 0; + const c = ''; + // Example sequences from Myers 1986 + const arrayA = [c, b, a, b, a, c]; + const arrayB = [a, b, c, a, b, b, a]; + const diffResult = diffArrays(arrayA, arrayB); + expect(diffResult).to.deep.equals([ + {count: 2, value: [a, b], removed: undefined, added: true}, + {count: 1, value: [c]}, + {count: 1, value: [b], removed: true, added: undefined}, + {count: 2, value: [a, b]}, + {count: 1, value: [b], removed: undefined, added: true}, + {count: 1, value: [a]}, + {count: 1, value: [c], removed: true, added: undefined} + ]); + }); + describe('anti-aliasing', function() { + // Test apparent contract that no chunk value is ever an input argument. + const value = [0, 1, 2]; + const expected = [ + {count: value.length, value: value} + ]; + + const input = value.slice(); + const diffResult = diffArrays(input, input); + it('returns correct deep result for identical inputs', function() { + expect(diffResult).to.deep.equals(expected); + }); + it('does not return the input array', function() { + expect(diffResult[0].value).to.not.equal(input); + }); + + const input1 = value.slice(); + const input2 = value.slice(); + const diffResult2 = diffArrays(input1, input2); + it('returns correct deep result for equivalent inputs', function() { + expect(diffResult2).to.deep.equals(expected); + }); + it('does not return the first input array', function() { + expect(diffResult2[0].value).to.not.equal(input1); + }); + it('does not return the second input array', function() { + expect(diffResult2[0].value).to.not.equal(input2); + }); + }); + it('Should diff arrays with comparator', function() { + const a = {a: 0}, b = {a: 1}, c = {a: 2}, d = {a: 3}; + function comparator(left, right) { + return left.a === right.a; + } + const diffResult = diffArrays([a, b, c], [a, b, d], { comparator: comparator }); + console.log(diffResult); + expect(diffResult).to.deep.equals([ + {count: 2, value: [a, b]}, + {count: 1, value: [c], removed: true, added: undefined}, + {count: 1, value: [d], removed: undefined, added: true} + ]); + }); }); }); diff --git a/test/diff/json.js b/test/diff/json.js index 977504f1..1754218a 100644 --- a/test/diff/json.js +++ b/test/diff/json.js @@ -15,6 +15,7 @@ describe('diff/json', function() { { count: 1, value: '}' } ]); }); + it('should accept objects with different order', function() { expect(diffJson( {a: 123, b: 456, c: 789}, @@ -136,6 +137,51 @@ describe('diff/json', function() { expect(getKeys(canonicalObj)).to.eql(['a', 'b']); expect(getKeys(canonicalObj.a)).to.eql(['a', 'b']); }); + + it('should accept a custom JSON.stringify() replacer function', function() { + expect(diffJson( + {a: 123}, + {a: /foo/} + )).to.eql([ + { count: 1, value: '{\n' }, + { count: 1, value: ' \"a\": 123\n', added: undefined, removed: true }, + { count: 1, value: ' \"a\": {}\n', added: true, removed: undefined }, + { count: 1, value: '}' } + ]); + + expect(diffJson( + {a: 123}, + {a: /foo/gi}, + {stringifyReplacer: (k, v) => v instanceof RegExp ? v.toString() : v} + )).to.eql([ + { count: 1, value: '{\n' }, + { count: 1, value: ' \"a\": 123\n', added: undefined, removed: true }, + { count: 1, value: ' \"a\": "/foo/gi"\n', added: true, removed: undefined }, + { count: 1, value: '}' } + ]); + + expect(diffJson( + {a: 123}, + {a: new Error('ohaider')}, + {stringifyReplacer: (k, v) => v instanceof Error ? `${v.name}: ${v.message}` : v} + )).to.eql([ + { count: 1, value: '{\n' }, + { count: 1, value: ' \"a\": 123\n', added: undefined, removed: true }, + { count: 1, value: ' \"a\": "Error: ohaider"\n', added: true, removed: undefined }, + { count: 1, value: '}' } + ]); + + expect(diffJson( + {a: 123}, + {a: [new Error('ohaider')]}, + {stringifyReplacer: (k, v) => v instanceof Error ? `${v.name}: ${v.message}` : v} + )).to.eql([ + { count: 1, value: '{\n' }, + { count: 1, value: ' \"a\": 123\n', added: undefined, removed: true }, + { count: 3, value: ' \"a\": [\n "Error: ohaider"\n ]\n', added: true, removed: undefined }, + { count: 1, value: '}' } + ]); + }); }); }); diff --git a/test/patch/apply.js b/test/patch/apply.js index 67965662..125199a8 100755 --- a/test/patch/apply.js +++ b/test/patch/apply.js @@ -180,6 +180,23 @@ describe('patch/apply', function() { + ' line4\n' + ' line4\n')) .to.equal('line1\nline2\nline3\nline4\nline4\nline4\nline4'); + + // Test empty lines in patches + expect(applyPatch( + 'line11\nline2\n\nline4', + + 'Index: test\n' + + '===================================================================\n' + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,4 +1,4 @@\n' + + '+line1\n' + + '-line11\n' + + ' line2\n' + + '\n' + + ' line4\n' + + '\\ No newline at end of file\n')) + .to.equal('line1\nline2\n\nline4'); }); it('should apply patches', function() { diff --git a/test/patch/merge.js b/test/patch/merge.js index d487cadb..f2bc5b3a 100644 --- a/test/patch/merge.js +++ b/test/patch/merge.js @@ -354,7 +354,9 @@ describe('patch/merge', function() { { conflict: true, oldStart: 1, + oldLines: 6, newStart: 1, + newLines: 3, lines: [ ' line2', ' line3', @@ -407,6 +409,7 @@ describe('patch/merge', function() { conflict: true, oldStart: 1, newStart: 1, + newLines: 4, lines: [ ' line2', ' line3', @@ -454,6 +457,7 @@ describe('patch/merge', function() { { conflict: true, oldStart: 1, + oldLines: 3, newStart: 1, lines: [ ' line2', @@ -499,6 +503,7 @@ describe('patch/merge', function() { { conflict: true, oldStart: 1, + oldLines: 3, newStart: 1, lines: [ ' line2', @@ -538,6 +543,7 @@ describe('patch/merge', function() { { conflict: true, oldStart: 1, + oldLines: 2, newStart: 1, lines: [ ' line2', @@ -829,6 +835,7 @@ describe('patch/merge', function() { { conflict: true, oldStart: 1, + oldLines: 4, newStart: 1, lines: [ '-line2', @@ -936,6 +943,7 @@ describe('patch/merge', function() { { conflict: true, oldStart: 1, + oldLines: 6, newStart: 1, lines: [ '-line2', @@ -1003,7 +1011,150 @@ describe('patch/merge', function() { swapConflicts(expected); expect(merge(theirs, mine)).to.eql(expected); }); - it('should conflict context mismatch', function() { + + it('should handle multiple conflicts in one hunk', function() { + const mine = + '@@ -1,10 +1,10 @@\n' + + ' line1\n' + + '-line2\n' + + '+line2-1\n' + + ' line3\n' + + ' line4\n' + + ' line5\n' + + '-line6\n' + + '+line6-1\n' + + ' line7\n'; + const theirs = + '@@ -1,10 +1,10 @@\n' + + ' line1\n' + + '-line2\n' + + '+line2-2\n' + + ' line3\n' + + ' line4\n' + + ' line5\n' + + '-line6\n' + + '+line6-2\n' + + ' line7\n'; + const expected = { + hunks: [ + { + conflict: true, + oldStart: 1, + oldLines: 7, + newStart: 1, + newLines: 7, + lines: [ + ' line1', + { + conflict: true, + mine: [ + '-line2', + '+line2-1' + ], + theirs: [ + '-line2', + '+line2-2' + ] + }, + ' line3', + ' line4', + ' line5', + { + conflict: true, + mine: [ + '-line6', + '+line6-1' + ], + theirs: [ + '-line6', + '+line6-2' + ] + }, + ' line7' + ] + } + ] + }; + + expect(merge(mine, theirs)).to.eql(expected); + + swapConflicts(expected); + expect(merge(theirs, mine)).to.eql(expected); + }); + + it('should remove oldLines if base differs', function() { + const mine = + '@@ -1,10 +1,10 @@\n' + + ' line1\n' + + '-line2\n' + + '-line2-0\n' + + '+line2-1\n' + + ' line3\n' + + ' line4\n' + + ' line5\n' + + '-line6\n' + + '+line6-1\n' + + ' line7\n'; + const theirs = + '@@ -1,10 +1,10 @@\n' + + ' line1\n' + + '-line2\n' + + '+line2-2\n' + + '+line2-3\n' + + ' line3\n' + + ' line4\n' + + ' line5\n' + + '-line6\n' + + '+line6-2\n' + + ' line7\n'; + const expected = { + hunks: [ + { + conflict: true, + oldStart: 1, + newStart: 1, + lines: [ + ' line1', + { + conflict: true, + mine: [ + '-line2', + '-line2-0', + '+line2-1' + ], + theirs: [ + '-line2', + '+line2-2', + '+line2-3' + ] + }, + ' line3', + ' line4', + ' line5', + { + conflict: true, + mine: [ + '-line6', + '+line6-1' + ], + theirs: [ + '-line6', + '+line6-2' + ] + }, + ' line7' + ] + } + ] + }; + + expect(merge(mine, theirs)).to.eql(expected); + + swapConflicts(expected); + expect(merge(theirs, mine)).to.eql(expected); + }); + + it('should handle multiple conflict sections', function() { const mine = '@@ -1,3 +1,4 @@\n' + ' line2\n' @@ -1017,7 +1168,9 @@ describe('patch/merge', function() { { conflict: true, oldStart: 1, + oldLines: 2, newStart: 1, + newLines: 2, lines: [ { conflict: true,