diff --git a/package.json b/package.json index 97e8e35a..77ca7091 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,9 @@ }, "main": "./lib", "scripts": { - "test": "grunt" + "prepublish": "grunt build", + "test": "grunt test" }, - "dependencies": {}, "devDependencies": { "async": "^1.4.2", "babel": "^5.8.23", @@ -60,6 +60,5 @@ "semver": "^5.0.3", "webpack": "^1.12.2", "webpack-dev-server": "^1.12.0" - }, - "optionalDependencies": {} + } } diff --git a/src/patch/apply.js b/src/patch/apply.js index c86cae26..d4ce8b77 100644 --- a/src/patch/apply.js +++ b/src/patch/apply.js @@ -20,19 +20,21 @@ export function applyPatch(source, uniDiff, options = {}) { compareLine = options.compareLine || ((lineNumber, line, operation, patchContent) => line === patchContent), errorCount = 0, fuzzFactor = options.fuzzFactor || 0, + minLine = 0, + offset = 0, removeEOFNL, addEOFNL; - for (let i = 0; i < hunks.length; i++) { - let hunk = hunks[i], - toPos = hunk.newStart - 1; - - // Sanity check the input string. Bail if we don't match. + /** + * Checks if the hunk exactly fits on the provided location + */ + 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); + if (operation === ' ' || operation === '-') { // Context sanity check if (!compareLine(toPos + 1, lines[toPos], operation, content)) { @@ -42,7 +44,90 @@ export function applyPatch(source, uniDiff, options = {}) { return false; } } + toPos++; } + } + + return true; + } + + function distanceIterator(toPos, minLine, maxLine) { + let wantForward = true, + backwardExhausted = false, + forwardExhausted = false, + localOffset = 1; + + return function iterator() { + if (wantForward && !forwardExhausted) { + if (backwardExhausted) { + localOffset++; + } else { + wantForward = false; + } + + // Check if trying to fit beyond text length, and if not, check it fits + // after offset location (or desired location on first iteration) + if (toPos + localOffset <= maxLine) { + return localOffset; + } + + forwardExhausted = true; + } + + if (!backwardExhausted) { + if (!forwardExhausted) { + wantForward = true; + } + + // Check if trying to fit before text beginning, and if not, check it fits + // before offset location + if (minLine <= toPos - localOffset) { + return -localOffset++; + } + + backwardExhausted = true; + return iterator(); + } + + // We tried to fit hunk before text beginning and beyond text lenght, then + // hunk can't fit on the text. Return undefined + }; + } + + // Search best fit offsets for each hunk based on the previous ones + for (let i = 0; i < hunks.length; i++) { + let hunk = hunks[i], + maxLine = lines.length - hunk.oldLines, + localOffset = 0, + toPos = offset + hunk.oldStart - 1; + + let iterator = distanceIterator(toPos, minLine, maxLine); + + for (; localOffset !== undefined; localOffset = iterator()) { + if (hunkFits(hunk, toPos + localOffset)) { + hunk.offset = offset += localOffset; + break; + } + } + + if (localOffset === undefined) { + return false; + } + + // Set lower text limit to end of the current hunk, so next ones don't try + // to fit over already patched text + minLine = hunk.offset + hunk.oldStart + hunk.oldLines; + } + + // Apply patch hunks + for (let i = 0; i < hunks.length; i++) { + let hunk = hunks[i], + toPos = hunk.offset + hunk.newStart - 1; + + for (let j = 0; j < hunk.lines.length; j++) { + let line = hunk.lines[j], + operation = line[0], + content = line.substr(1); if (operation === ' ') { toPos++; @@ -84,7 +169,7 @@ export function applyPatches(uniDiff, options) { function processIndex() { let index = uniDiff[currentIndex++]; if (!index) { - options.complete(); + return options.complete(); } options.loadFile(index, function(err, data) { diff --git a/src/patch/parse.js b/src/patch/parse.js index e00619a6..4d0466db 100644 --- a/src/patch/parse.js +++ b/src/patch/parse.js @@ -9,13 +9,13 @@ export function parsePatch(uniDiff, options = {}) { // Ignore any leading junk while (i < diffstr.length) { - if ((/^Index:/.test(diffstr[i])) || (/^@@/.test(diffstr[i]))) { + if (/^(Index:|diff -r|@@)/.test(diffstr[i])) { break; } i++; } - let header = (/^Index: (.*)/.exec(diffstr[i])); + let header = (/^(?:Index:|diff(?: -r \w+)+) (.*)/.exec(diffstr[i])); if (header) { index.index = header[1]; i++; @@ -35,7 +35,7 @@ export function parsePatch(uniDiff, options = {}) { index.hunks = []; while (i < diffstr.length) { - if (/^Index:/.test(diffstr[i])) { + if (/^(Index:|diff -r)/.test(diffstr[i])) { break; } else if (/^@@/.test(diffstr[i])) { index.hunks.push(parseHunk()); diff --git a/test/patch/apply.js b/test/patch/apply.js index d15d8d0f..23675315 100644 --- a/test/patch/apply.js +++ b/test/patch/apply.js @@ -374,6 +374,48 @@ describe('patch/apply', function() { + 'line5\n'); }); + it('should succeed when hunk needs a negative offset', function() { + expect(applyPatch( + 'line1\n' + + 'line3\n' + + 'line4\n' + + 'line5\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -3,2 +3,3 @@\n' + + ' line1\n' + + '+line2\n' + + ' line3\n')) + .to.equal( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line4\n' + + 'line5\n'); + }); + + it('should succeed when hunk needs a positive offset', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line5\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,2 +1,3 @@\n' + + ' line3\n' + + '+line4\n' + + ' line5\n')) + .to.equal( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line4\n' + + 'line5\n'); + }); + it('should allow custom line comparison', function() { expect(applyPatch( 'line2\n'