Skip to content

Commit

Permalink
Merge pull request #18 from cujojs/add-rebase
Browse files Browse the repository at this point in the history
Add rebase and unit test
  • Loading branch information
briancavalier committed May 20, 2014
2 parents ee80128 + c85b6f0 commit d716542
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 15 deletions.
58 changes: 44 additions & 14 deletions lib/commute.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,54 @@
var patches = require('./patches');

module.exports = function commute(left, right) {
return right.reduce(function(pair, p2) {
return left.reduceRight(function(pair, p1) {
return commuteOne(pair, p1, p2);
}, pair);
}, { left: [], right: [] })
};

function commuteOne (pair, p1, p2) {
var patch = patches[p2.op];
var commuted;
module.exports = commute;

commute.rtl = commuteRtL;

/**
* Given adjacent patch pair p1,p2, commute them to create a new
* adjacent pair p2',p1'
* @param {array} p1 JSON Patch
* @param {array} p2 JSON Patch
* @returns {array<array>} pair [commutedRight, commutedLeft]
*/
function commute(p1, p2) {
return runCommute(keepBoth, [[], []], p1, p2);
}
/**
* Commute left and right and return only the newly commuted left, throwing
* away the newly commuted right.
* @param p1
* @param p2
* @returns {*}
*/
function commuteRtL(p1, p2) {
return runCommute(keepLeft, [], p1, p2);
}

function runCommute(f, accum, p1, p2) {
return p2.reduce(function(accum, p2) {
return p1.reduceRight(function(accum, p1) {
return commuteOne(f, accum, p1, p2);
}, accum);
}, accum)
}

function commuteOne (f, accum, p1, p2) {
var patch = patches[p2.op];
if (patch === void 0 || typeof patch.commute !== 'function') {
throw new TypeError('patches cannot be commuted');
}

commuted = patch.commute(p1, p2);
pair.left.push(commuted[0]);
pair.right.push(commuted[1]);
return f(accum, patch.commute(p1, p2));
}

function keepBoth(pair, commuted) {
pair[0].push(commuted[0]);
pair[1].push(commuted[1]);
return pair;
}

function keepLeft(left, commuted) {
left.push(commuted[0]);
return left;
}
16 changes: 16 additions & 0 deletions lib/rebase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
var commuteRtL = require('./commute').rtl;
var inverse = require('./inverse');

/**
* Given a patch history (array of patches) and a single patch, rooted
* at the same starting document context d1, rebase patch onto history
* so that d1 + history -> d2, d2 + patch -> d3
* @param {array<array>} history array of JSON Patch
* @param {array} patch JSON Patch
* @returns {array} rebased patch which can be applied after history
*/
module.exports = function rebase(history, patch) {
return history.reduce(function(commuted, patchFromHistory) {
return commuteRtL(inverse(patchFromHistory), commuted);
}, patch);
};
2 changes: 1 addition & 1 deletion test/jsonPatch-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ buster.testCase('jsonPatch', {
refute.defined(result.x);
},

'=>should not allow moving to ancestor path': function() {
'should not allow moving to ancestor path': function() {
var from = '/a/b/c';
var to = '/a/b';
assert.exception(function() {
Expand Down
26 changes: 26 additions & 0 deletions test/rebase-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
var buster = require('buster');
var assert = buster.referee.assert;

var rebase = require('../lib/rebase');
var jiff = require('../jiff');
var deepEquals = require('../lib/deepEquals');

buster.testCase('rebase', {
'should allow parallel patches': function() {
var d1 = [1,2,3,4,5];
var d2a = [1,2,4,5];
var d2b = [1,2,3,6,4,5];
var d3 = [1,2,6,4,5];

// Two parallel patches created from d1
var d1pd2a = jiff.diff(d1, d2a);
var d1pd2b = jiff.diff(d1, d2b);

// Rebase d1pd2b onto d1pd2a
var d2apd2b = rebase([d1pd2a], d1pd2b);

var d3a = jiff.patch(d2apd2b, jiff.patch(d1pd2a, d1));
assert(deepEquals(d3, d3a));
}

});

0 comments on commit d716542

Please sign in to comment.