Skip to content

Commit

Permalink
wip(merge): findPathToLeaf, removeLeafFromTree
Browse files Browse the repository at this point in the history
  • Loading branch information
jankaszel authored and AlbaHerrerias committed Dec 7, 2022
1 parent 5fd1522 commit 972ae33
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 84 deletions.
43 changes: 43 additions & 0 deletions packages/node_modules/pouchdb-merge/src/findPathToLeaf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
function findPathToLeaf(revs, targetRev) {
// `revs` has the same structure as what `revs_tree` has on e.g. the IndexedDB representation
// of the rev tree datastructure.
let path = [];
const toVisit = revs.slice();

let node;
while ((node = toVisit.pop())) {
const { pos, ids: tree } = node;
const rev = `${pos}-${tree[0]}`;
const branches = tree[2];

// just assuming we're already working on the path up towards our desired leaf.
path.push(rev);

// we've reached the leaf of our dreams, so return the computed path.
if (rev === targetRev) {
//…unleeeeess
if (branches.length !== 0) {
throw new Error('The requested revision is not a leaf');
}
return path.reverse();
}

// this is based on the assumption that after we have a leaf (`branches.length == 0`), we handle the next
// branch. this is true for all branches other than the path leading to the winning rev (which is 6-a in
// the example below. i've added a reset condition for branching nodes (`branches.length > 1`) as well.
if (branches.length === 0 || branches.length > 1) {
path = [];
}

// as a next step, we push the branches of this node to `toVisit` for visiting it during the next iteration
for (let i = 0, len = branches.length; i < len; i++) {
toVisit.push({ pos: pos + 1, ids: branches[i] });
}
}
if (path.length === 0) {
throw new Error('The requested revision does not exist');
}
return path.reverse();
}

export default findPathToLeaf;
6 changes: 5 additions & 1 deletion packages/node_modules/pouchdb-merge/src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import collectConflicts from './collectConflicts';
import collectLeaves from './collectLeaves';
import compactTree from './compactTree';
import findPathToLeaf from './findPathToLeaf';
import merge from './merge';
import removeLeafFromTree from './removeLeafFromTree';
import revExists from './revExists';
import rootToLeaf from './rootToLeaf';
import traverseRevTree from './traverseRevTree';
Expand All @@ -14,12 +16,14 @@ export {
collectConflicts,
collectLeaves,
compactTree,
findPathToLeaf,
isDeleted,
isLocalId,
merge,
removeLeafFromTree,
revExists,
rootToLeaf,
traverseRevTree,
winningRev,
latest
};
};
32 changes: 32 additions & 0 deletions packages/node_modules/pouchdb-merge/src/removeLeafFromTree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { clone } from 'pouchdb-utils';

function removeLeafFromRevTree(tree, leafRev) {
const _tree = clone(tree);
const toVisit = _tree.slice();

let previousNode;
let node;
while ((node = toVisit.pop())) {
const pos = node.pos;
const [id, opts, branches] = node.ids;
const isLeaf = branches.length === 0;
const hash = `${pos}-${id}`;

if (isLeaf && hash === leafRev && opts.status === "available") {
previousNode.ids[2] = previousNode.ids[2].filter(function (branchNode) {
return branchNode[0] !== id;
});
return _tree;
}
previousNode = node;

if (branches.length) {
for (let i = 0, len = branches.length; i < len; i++) {
toVisit.push({pos: pos + 1, ids: branches[i]});
}
}
}
return _tree;
}

export default removeLeafFromRevTree;
216 changes: 133 additions & 83 deletions tests/unit/test.purge.js
Original file line number Diff line number Diff line change
@@ -1,64 +1,23 @@
'use strict';

var should = require('chai').should();

function findPathToLeaf(revs, targetRev) {
// `revs` has the same structure as what `revs_tree` has on e.g. the IndexedDB representation
// of the rev tree datastructure.
let path = [];
const toVisit = revs.slice();

let node;
while ((node = toVisit.pop())) {
const { pos, ids: tree } = node;
const rev = `${pos}-${tree[0]}`;
const branches = tree[2];

// just assuming we're already working on the path up towards our desired leaf.
path.push(rev);

// we've reached the leaf of our dreams, so return the computed path.
if (rev === targetRev) {
//…unleeeeess
if (branches.length !== 0) {
throw new Error('The requested revision is not a leaf');
}
return path.reverse();
}

// this is based on the assumption that after we have a leaf (`branches.length == 0`), we handle the next
// branch. this is true for all branches other than the path leading to the winning rev (which is 6-a in
// the example below. i've added a reset condition for branching nodes (`branches.length > 1`) as well.
if (branches.length === 0 || branches.length > 1) {
path = [];
}

// as a next step, we push the branches of this node to `toVisit` for visiting it during the next iteration
for (let i = 0, len = branches.length; i < len; i++) {
toVisit.push({ pos: pos + 1, ids: branches[i] });
}
}
if (path.length === 0) {
throw new Error('The requested revision does not exist');
}
return path.reverse();
}
const should = require('chai').should();
const { findPathToLeaf, removeLeafFromTree } = require('../../packages/node_modules/pouchdb-merge');

/*
1-a - 2-a -- 3-a - 4-a - 5-a - 6-a
\ \
\ 5-c - 6-c
3-b - 4-b
1-a = 1-9692d401ed2d3434827608278bdc36e3
2-a = 2-37aa033df08c21b4f56f1da2081e9e00
3-a = 3-df226cb9a2e5bdd3e6be009fd51f47c1
4-a = 4-6e94d345514a08620c3176eea080d3ec
5-a = 5-df4a81cd21c75c71974d96e88a68fc2f
6-a = 6-3f7f6c55c27bf54c009b661607a9fe05 leaf
5-c = 5-0b84bfea5508e2020feb07384714a987
6-c = 6-2d0ab4f4089a57c95d52bfd2d66b823d leaf
3-b = 3-43f6d5557d6de39488c64bb2c684ae7c
4-b = 4-a3b44168027079c2692a7d8eb35e9643 leaf
1-a - 2-a -- 3-a - 4-a - 5-a - 6-a
\ \
\ 5-c - 6-c
3-b - 4-b
1-a = 1-9692d401ed2d3434827608278bdc36e3
2-a = 2-37aa033df08c21b4f56f1da2081e9e00
3-a = 3-df226cb9a2e5bdd3e6be009fd51f47c1
4-a = 4-6e94d345514a08620c3176eea080d3ec
5-a = 5-df4a81cd21c75c71974d96e88a68fc2f
6-a = 6-3f7f6c55c27bf54c009b661607a9fe05 leaf
5-c = 5-0b84bfea5508e2020feb07384714a987
6-c = 6-2d0ab4f4089a57c95d52bfd2d66b823d leaf
3-b = 3-43f6d5557d6de39488c64bb2c684ae7c
4-b = 4-a3b44168027079c2692a7d8eb35e9643 leaf
*/
const shortConflictedTree = [
{
Expand Down Expand Up @@ -143,22 +102,98 @@ const shortConflictedTree = [
}
];

// the same as the above shortConflictedTree, but without the 6-a leaf
const shortConflictedTreeWithout6a = [
{
"pos": 1,
"ids": [
"9692d401ed2d3434827608278bdc36e3",
{
"status": "available"
},
[
[
"37aa033df08c21b4f56f1da2081e9e00",
{
"status": "available"
},
[
[
"43f6d5557d6de39488c64bb2c684ae7c",
{
"status": "missing"
},
[
[
"a3b44168027079c2692a7d8eb35e9643",
{
"status": "available"
},
[]
]
]
],
[
"df226cb9a2e5bdd3e6be009fd51f47c1",
{
"status": "available"
},
[
[
"6e94d345514a08620c3176eea080d3ec",
{
"status": "available"
},
[
[
"0b84bfea5508e2020feb07384714a987",
{
"status": "missing"
},
[
[
"2d0ab4f4089a57c95d52bfd2d66b823d",
{
"status": "available"
},
[]
]
]
],
[
"df4a81cd21c75c71974d96e88a68fc2f",
{
"status": "available"
},
[]
]
]
]
]
]
]
]
]
]
}
];

/*
With revs_limit: 4, the shortConflictedTree from above becomes:
3-a - 4-a - 5-a - 6-a
\
5-c - 6-c
1-a - 2-a - 3-b - 4-b
1-a = 1-9692d401ed2d3434827608278bdc36e3
2-a = 2-37aa033df08c21b4f56f1da2081e9e00
3-a = 3-df226cb9a2e5bdd3e6be009fd51f47c1
4-a = 4-6e94d345514a08620c3176eea080d3ec
5-a = 5-df4a81cd21c75c71974d96e88a68fc2f
6-a = 6-3f7f6c55c27bf54c009b661607a9fe05 leaf
5-c = 5-0b84bfea5508e2020feb07384714a987
6-c = 6-2d0ab4f4089a57c95d52bfd2d66b823d leaf
3-b = 3-43f6d5557d6de39488c64bb2c684ae7c
4-b = 4-a3b44168027079c2692a7d8eb35e9643 leaf
With revs_limit: 4, the shortConflictedTree from above becomes:
3-a - 4-a - 5-a - 6-a
\
5-c - 6-c
1-a - 2-a - 3-b - 4-b
1-a = 1-9692d401ed2d3434827608278bdc36e3
2-a = 2-37aa033df08c21b4f56f1da2081e9e00
3-a = 3-df226cb9a2e5bdd3e6be009fd51f47c1
4-a = 4-6e94d345514a08620c3176eea080d3ec
5-a = 5-df4a81cd21c75c71974d96e88a68fc2f
6-a = 6-3f7f6c55c27bf54c009b661607a9fe05 leaf
5-c = 5-0b84bfea5508e2020feb07384714a987
6-c = 6-2d0ab4f4089a57c95d52bfd2d66b823d leaf
3-b = 3-43f6d5557d6de39488c64bb2c684ae7c
4-b = 4-a3b44168027079c2692a7d8eb35e9643 leaf
*/
const shortConflictedTreeWithTwoRoots = [
{
Expand Down Expand Up @@ -247,17 +282,17 @@ const shortConflictedTreeWithTwoRoots = [
];

/*
With revs_limit: 4, just the main branch
(a doc without conflicts):
1-a - 2-a | 3-a - 4-a - 5-a - 6-a
truncated ^ here, 1-a - 2-a are missing. This
is functionally the same as traversing a
single-branch tree that hasn’t had a revs_limit
applied, so no need to test that separately.
3-a = 3-df226cb9a2e5bdd3e6be009fd51f47c1
4-a = 4-6e94d345514a08620c3176eea080d3ec
5-a = 5-df4a81cd21c75c71974d96e88a68fc2f
6-a = 6-3f7f6c55c27bf54c009b661607a9fe05 leaf
With revs_limit: 4, just the main branch
(a doc without conflicts):
1-a - 2-a | 3-a - 4-a - 5-a - 6-a
truncated ^ here, 1-a - 2-a are missing. This
is functionally the same as traversing a
single-branch tree that hasn’t had a revs_limit
applied, so no need to test that separately.
3-a = 3-df226cb9a2e5bdd3e6be009fd51f47c1
4-a = 4-6e94d345514a08620c3176eea080d3ec
5-a = 5-df4a81cd21c75c71974d96e88a68fc2f
6-a = 6-3f7f6c55c27bf54c009b661607a9fe05 leaf
*/
const revsLimitedSingleBranchTree = [
{
Expand Down Expand Up @@ -367,3 +402,18 @@ describe.only('the findPathToLeaf util', function () {
}
});
});

describe.only('the removeLeafFromTree util', function () {
it('removes a leaf from the tree', function () {
const tree = removeLeafFromTree(shortConflictedTree, "6-3f7f6c55c27bf54c009b661607a9fe05");
tree.should.be.deep.equal(shortConflictedTreeWithout6a);
});
it('does not remove anything from the tree if the rev is not a leaf', function () {
const tree = removeLeafFromTree(shortConflictedTree, "5-df4a81cd21c75c71974d96e88a68fc2f");
tree.should.be.deep.equal(shortConflictedTree);
});
it('does not remove anything from the tree if the rev doesn\'t exist', function () {
const tree = removeLeafFromTree(shortConflictedTree, "foobar");
tree.should.be.deep.equal(shortConflictedTree);
});
});

0 comments on commit 972ae33

Please sign in to comment.