Skip to content

Allow access to structured object representation of patch data #62

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 4, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had trouble adapting its logic to the hunk'd format. Since trying to add no-nl each time we have an add/delete entry seemed inefficient, I opted for a rewrite.


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'],
}]
});
});
});