Skip to content
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

Allow patchs in diff format #83

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from 12 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
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -60,6 +60,5 @@
"semver": "^5.0.3",
"webpack": "^1.12.2",
"webpack-dev-server": "^1.12.0"
Copy link
Owner

Choose a reason for hiding this comment

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

As long as the tests pass I'm fine taking version changes as is, but generally it's better to keep the changes in a PR down to just those needed to implement your feature. Makes it easier to review and less likely to be rejected for reasons that are tangential to your feature change.

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 agree. When I have it finished I can be able to split the commits in several feature branches so we can analize them separately.

Copy link
Owner

Choose a reason for hiding this comment

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

I've cherry-picked these into master if you want to rebase to help simplify the PR.

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've rebased it.

},
"optionalDependencies": {}
}
Copy link
Owner

Choose a reason for hiding this comment

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

Please keep the changes focused on this PR.

}
97 changes: 91 additions & 6 deletions src/patch/apply.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -42,7 +44,90 @@ export function applyPatch(source, uniDiff, options = {}) {
return false;
}
}
toPos++;
}
}

return true;
}

function distanceIterator(toPos, minLine, maxLine) {
Copy link
Owner

Choose a reason for hiding this comment

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

This should probably be a separate module to help make things clearer.

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()) {
Copy link
Owner

Choose a reason for hiding this comment

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

You need !== here or != null otherwise zero will fail. We should have a test case for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You need !== here or != null otherwise zero will fail.

[piranna@Mabuk:~/Proyectos/NodeOS/node_modules]
 (vagga) > node
> 0 != undefined
true
> 

I think it's not needed... :-) But I could agree on the !== undefined, in any case is the only thing it will return... I left it this way to be more generic.

We should have a test case for that.

A test case for what, exactly?

Copy link
Owner

Choose a reason for hiding this comment

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

Perhaps it's been too long that I've been using != null. Generally prefer != null for "anything other than null and undefined" and "!== undefined" for "just looking for undefined". (I actually thought that was in the linter rules, but I guess not).

Little bit of a nit picky change but would be nice to have so it doesn't cause confusion down the road.

Test case: To make sure that it can patch at an offset of zero.

if (hunkFits(hunk, toPos + localOffset)) {
hunk.offset = offset += localOffset;
break;
}
}

if (localOffset == undefined) {
return false;
Copy link
Owner

Choose a reason for hiding this comment

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

What behavior does this change that is necessary to do two iterations?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is WiP to allow to calculate the location offset where the hunks best fit. This is to fix issue #84

}

// 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++;
Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions src/patch/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand All @@ -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());
Expand Down
42 changes: 42 additions & 0 deletions test/patch/apply.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down