Skip to content

Commit

Permalink
Merge pull request #62 from bittrance/patch-refactor
Browse files Browse the repository at this point in the history
Allow access to structured object representation of patch data
  • Loading branch information
kpdecker committed Aug 4, 2015
2 parents 6eeea0f + 30189fb commit 9047d42
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 40 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
98 changes: 60 additions & 38 deletions diff.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,37 +398,20 @@
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;
for (var i = 0; i < diff.length; i++) {
Expand All @@ -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;
}
Expand All @@ -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) {
Expand All @@ -466,21 +448,33 @@
// 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);
curRange.push.apply(curRange, contextLines(lines.slice(0, contextSize)));

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);
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) {
curRange.push('\\ No newline at end of file')
}
}
hunks.push(hunk);

oldRangeStart = 0;
newRangeStart = 0;
Expand All @@ -491,12 +485,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) {
Expand Down
76 changes: 74 additions & 2 deletions test/createPatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
Expand All @@ -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'
Expand All @@ -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'
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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 =
Expand Down
25 changes: 25 additions & 0 deletions test/structuredPatch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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',
'header1', 'header2'
);
res.should.eql({
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'],
}]
});
});
});

0 comments on commit 9047d42

Please sign in to comment.