diff --git a/src/patch/create.js b/src/patch/create.js index a6825f03..a48e1615 100644 --- a/src/patch/create.js +++ b/src/patch/create.js @@ -72,8 +72,9 @@ export function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHea let oldEOFNewline = ((/\n$/).test(oldStr)); let newEOFNewline = ((/\n$/).test(newStr)); let noNlBeforeAdds = lines.length == 0 && curRange.length > hunk.oldLines; - if (!oldEOFNewline && noNlBeforeAdds) { + if (!oldEOFNewline && noNlBeforeAdds && oldStr.length > 0) { // special case: old has no eol and no trailing context; no-nl can end up before adds + // however, if the old file is empty, do not output the no-nl line curRange.splice(hunk.oldLines, 0, '\\ No newline at end of file'); } if ((!oldEOFNewline && !noNlBeforeAdds) || !newEOFNewline) { @@ -110,6 +111,15 @@ export function formatPatch(diff) { for (let i = 0; i < diff.hunks.length; i++) { const hunk = diff.hunks[i]; + // Unified Diff Format quirk: If the chunk size is 0, + // the first number is one lower than one would expect. + // https://www.artima.com/weblogs/viewpost.jsp?thread=164293 + if (hunk.oldLines === 0) { + hunk.oldStart -= 1; + } + if (hunk.newLines === 0) { + hunk.newStart -= 1; + } ret.push( '@@ -' + hunk.oldStart + ',' + hunk.oldLines + ' +' + hunk.newStart + ',' + hunk.newLines diff --git a/src/patch/parse.js b/src/patch/parse.js index 6769609c..0a8807bb 100755 --- a/src/patch/parse.js +++ b/src/patch/parse.js @@ -77,13 +77,23 @@ export function parsePatch(uniDiff, options = {}) { let hunk = { oldStart: +chunkHeader[1], - oldLines: +chunkHeader[2] || 1, + oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2], newStart: +chunkHeader[3], - newLines: +chunkHeader[4] || 1, + newLines: typeof chunkHeader[4] === 'undefined' ? 1 : +chunkHeader[4], lines: [], linedelimiters: [] }; + // Unified Diff Format quirk: If the chunk size is 0, + // the first number is one lower than one would expect. + // https://www.artima.com/weblogs/viewpost.jsp?thread=164293 + if (hunk.oldLines === 0) { + hunk.oldStart += 1; + } + if (hunk.newLines === 0) { + hunk.newStart += 1; + } + let addCount = 0, removeCount = 0; for (; i < diffstr.length; i++) { diff --git a/test/patch/apply.js b/test/patch/apply.js index 125199a8..65187155 100755 --- a/test/patch/apply.js +++ b/test/patch/apply.js @@ -355,6 +355,160 @@ describe('patch/apply', function() { + 'line5\n'); }); + it('should apply single line patches with zero context and zero removed', function() { + expect(applyPatch( + 'line2\n' + + 'line3\n' + + 'line5\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -2,0 +3 @@\n' + + '+line4\n')) + .to.equal( + 'line2\n' + + 'line3\n' + + 'line4\n' + + 'line5\n'); + }); + + it('should apply multiline patches with zero context and zero removed', function() { + expect(applyPatch( + 'line2\n' + + 'line3\n' + + 'line7\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -2,0 +3,3 @@\n' + + '+line4\n' + + '+line5\n' + + '+line6\n')) + .to.equal( + 'line2\n' + + 'line3\n' + + 'line4\n' + + 'line5\n' + + 'line6\n' + + 'line7\n'); + }); + + it('should apply single line patches with zero context and zero removed at start of file', function() { + expect(applyPatch( + 'line2\n' + + 'line3\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -0,0 +1 @@\n' + + '+line1\n')) + .to.equal( + 'line1\n' + + 'line2\n' + + 'line3\n'); + }); + + it('should apply multi line patches with zero context and zero removed at start of file', function() { + expect(applyPatch( + 'line3\n' + + 'line4\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -0,0 +1,2 @@\n' + + '+line1\n' + + '+line2\n')) + .to.equal( + 'line1\n' + + 'line2\n' + + 'line3\n' + + 'line4\n'); + }); + + it('should apply multi line patches with zero context and zero removed at end of file', function() { + expect(applyPatch( + 'line1\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,0 +2 @@\n' + + '+line2\n')) + .to.equal( + 'line1\n' + + 'line2\n'); + }); + + it('should apply multi line patches with zero context and zero removed at end of file', function() { + expect(applyPatch( + 'line1\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,0 +2,2 @@\n' + + '+line2\n' + + '+line3\n')) + .to.equal( + 'line1\n' + + 'line2\n' + + 'line3\n'); + }); + + it('should apply single line patches with zero context and zero added at beginning of file', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1 +0,0 @@\n' + + '-line1\n')) + .to.equal( + 'line2\n'); + }); + + it('should apply multi line patches with zero context and zero added at beginning of file', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line3\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -1,2 +0,0 @@\n' + + '-line1\n' + + '-line2\n')) + .to.equal( + 'line3\n'); + }); + + it('should apply single line patches with zero context and zero added at end of file', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -2 +1,0 @@\n' + + '-line2\n')) + .to.equal( + 'line1\n'); + }); + + it('should apply multi line patches with zero context and zero added at end of file', function() { + expect(applyPatch( + 'line1\n' + + 'line2\n' + + 'line3\n', + + '--- test\theader1\n' + + '+++ test\theader2\n' + + '@@ -2,2 +1,0 @@\n' + + '-line2\n' + + '-line3\n')) + .to.equal( + 'line1\n'); + }); + it('should fail on mismatch', function() { expect(applyPatch( 'line2\n' diff --git a/test/patch/create.js b/test/patch/create.js index e9b570c5..1315df56 100644 --- a/test/patch/create.js +++ b/test/patch/create.js @@ -120,14 +120,13 @@ describe('patch/create', function() { + '\\ No newline at end of file\n'); }); - it('should output only one "no newline" at end of file message on empty file', function() { + it('should output no "no newline" at end of file message on empty file', function() { expect(createPatch('test', '', 'line1\nline2\nline3\nline4', 'header1', 'header2')).to.equal( 'Index: test\n' + '===================================================================\n' + '--- test\theader1\n' + '+++ test\theader2\n' - + '@@ -1,0 +1,4 @@\n' - + '\\ No newline at end of file\n' + + '@@ -0,0 +1,4 @@\n' + '+line1\n' + '+line2\n' + '+line3\n' @@ -139,7 +138,7 @@ describe('patch/create', function() { + '===================================================================\n' + '--- test\theader1\n' + '+++ test\theader2\n' - + '@@ -1,4 +1,0 @@\n' + + '@@ -1,4 +0,0 @@\n' + '-line1\n' + '-line2\n' + '-line3\n' @@ -528,7 +527,7 @@ describe('patch/create', function() { expect(diffResult).to.equal(expectedResult); }); - it('should generatea a patch with context size 0', function() { + it('should generate a patch with context size 0', function() { const expectedResult = 'Index: testFileName\n' + '===================================================================\n' @@ -538,11 +537,11 @@ describe('patch/create', function() { + '-value\n' + '+new value\n' + '+new value 2\n' - + '@@ -11,1 +12,0 @@\n' + + '@@ -11,1 +11,0 @@\n' + '-remove value\n' - + '@@ -21,1 +21,0 @@\n' + + '@@ -21,1 +20,0 @@\n' + '-remove value\n' - + '@@ -30,0 +29,1 @@\n' + + '@@ -29,0 +29,1 @@\n' + '+add value\n' + '@@ -34,1 +34,2 @@\n' + '-value\n'