|
|
var _ = require('underscore'); |
|
|
|
|
|
|
|
|
var TreeCompare = {}; |
|
|
|
|
|
TreeCompare.dispatchFromLevel = function(levelBlob, treeToCompare) { |
|
|
var goalTreeString = levelBlob.goalTreeString; |
|
|
if (typeof treeToCompare !== 'string') { |
|
|
console.warn('NEED to pass in string!! gah'); |
|
|
} |
|
|
return TreeCompare.dispatch(levelBlob, goalTreeString, treeToCompare); |
|
|
}; |
|
|
|
|
|
TreeCompare.onlyMainCompared = function(levelBlob) { |
|
|
var getAroundLintTrue = true; |
|
|
switch (getAroundLintTrue) { |
|
|
case !!levelBlob.compareOnlyMain: |
|
|
case !!levelBlob.compareOnlyMainHashAgnostic: |
|
|
case !!levelBlob.compareOnlyMainHashAgnosticWithAsserts: |
|
|
return true; |
|
|
default: |
|
|
return false; |
|
|
} |
|
|
}; |
|
|
|
|
|
TreeCompare.dispatch = function(levelBlob, goalTreeString, treeToCompare) { |
|
|
var goalTree = this.convertTreeSafe(goalTreeString); |
|
|
treeToCompare = this.convertTreeSafe(treeToCompare); |
|
|
if (typeof goalTree.originTree !== typeof treeToCompare.originTree) { |
|
|
|
|
|
return false; |
|
|
} |
|
|
var shallowResult = this.dispatchShallow( |
|
|
levelBlob, goalTree, treeToCompare |
|
|
); |
|
|
if (!shallowResult || !goalTree.originTree) { |
|
|
|
|
|
return shallowResult; |
|
|
} |
|
|
|
|
|
var originBlob = (levelBlob.originCompare) ? |
|
|
levelBlob.originCompare : levelBlob; |
|
|
|
|
|
return shallowResult && this.dispatchShallow( |
|
|
originBlob, goalTree.originTree, treeToCompare.originTree |
|
|
); |
|
|
}; |
|
|
|
|
|
TreeCompare.dispatchShallow = function(levelBlob, goalTreeString, treeToCompare) { |
|
|
var getAroundLintTrue = true; |
|
|
|
|
|
switch (getAroundLintTrue) { |
|
|
case !!levelBlob.compareOnlyMain: |
|
|
return TreeCompare.compareBranchWithinTrees( |
|
|
treeToCompare, goalTreeString, 'main' |
|
|
); |
|
|
case !!levelBlob.compareAllBranchesAndEnforceBranchCleanup: |
|
|
return TreeCompare.compareAllBranchesAndEnforceBranchCleanup( |
|
|
treeToCompare, goalTreeString |
|
|
); |
|
|
case !!levelBlob.compareOnlyBranches: |
|
|
return TreeCompare.compareAllBranchesWithinTrees( |
|
|
treeToCompare, goalTreeString |
|
|
); |
|
|
case !!levelBlob.compareAllBranchesHashAgnostic: |
|
|
return TreeCompare.compareAllBranchesWithinTreesHashAgnostic( |
|
|
treeToCompare, goalTreeString |
|
|
); |
|
|
case !!levelBlob.compareOnlyMainHashAgnostic: |
|
|
return TreeCompare.compareBranchesWithinTreesHashAgnostic( |
|
|
treeToCompare, goalTreeString, ['main'] |
|
|
); |
|
|
case !!levelBlob.compareOnlyMainHashAgnosticWithAsserts: |
|
|
return TreeCompare.compareBranchesWithinTreesHashAgnostic( |
|
|
treeToCompare, goalTreeString, ['main'] |
|
|
) && TreeCompare.evalAsserts(treeToCompare, levelBlob.goalAsserts); |
|
|
default: |
|
|
return TreeCompare.compareAllBranchesWithinTreesAndHEAD( |
|
|
treeToCompare, goalTreeString |
|
|
); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
TreeCompare.compareAllBranchesWithinTreesAndHEAD = function(treeToCompare, goalTree) { |
|
|
treeToCompare = this.convertTreeSafe(treeToCompare); |
|
|
goalTree = this.convertTreeSafe(goalTree); |
|
|
|
|
|
|
|
|
return treeToCompare.HEAD.target === goalTree.HEAD.target && |
|
|
this.compareAllBranchesWithinTrees(treeToCompare, goalTree) && |
|
|
this.compareAllTagsWithinTrees(treeToCompare, goalTree); |
|
|
}; |
|
|
|
|
|
TreeCompare.compareAllBranchesAndEnforceBranchCleanup = function(treeToCompare, goalTree) { |
|
|
treeToCompare = this.convertTreeSafe(treeToCompare); |
|
|
goalTree = this.convertTreeSafe(goalTree); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var allBranches = Object.assign( |
|
|
{}, |
|
|
treeToCompare.branches, |
|
|
goalTree.branches |
|
|
); |
|
|
return Object.keys(allBranches).every(function(branch) { |
|
|
return this.compareBranchWithinTrees(treeToCompare, goalTree, branch); |
|
|
}.bind(this)); |
|
|
}; |
|
|
|
|
|
|
|
|
TreeCompare.compareAllBranchesWithinTrees = function(treeToCompare, goalTree) { |
|
|
treeToCompare = this.convertTreeSafe(treeToCompare); |
|
|
goalTree = this.convertTreeSafe(goalTree); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return Object.keys(goalTree.branches).every(function(branch) { |
|
|
return this.compareBranchWithinTrees(treeToCompare, goalTree, branch); |
|
|
}.bind(this)); |
|
|
}; |
|
|
|
|
|
TreeCompare.compareAllTagsWithinTrees = function(treeToCompare, goalTree) { |
|
|
treeToCompare = this.convertTreeSafe(treeToCompare); |
|
|
goalTree = this.convertTreeSafe(goalTree); |
|
|
this.reduceTreeFields([treeToCompare, goalTree]); |
|
|
|
|
|
return _.isEqual(treeToCompare.tags, goalTree.tags); |
|
|
}; |
|
|
|
|
|
TreeCompare.compareBranchesWithinTrees = function(treeToCompare, goalTree, branches) { |
|
|
var result = true; |
|
|
branches.forEach(function(branchName) { |
|
|
result = result && this.compareBranchWithinTrees(treeToCompare, goalTree, branchName); |
|
|
}, this); |
|
|
|
|
|
return result; |
|
|
}; |
|
|
|
|
|
TreeCompare.compareBranchWithinTrees = function(treeToCompare, goalTree, branchName) { |
|
|
treeToCompare = this.convertTreeSafe(treeToCompare); |
|
|
goalTree = this.convertTreeSafe(goalTree); |
|
|
this.reduceTreeFields([treeToCompare, goalTree]); |
|
|
|
|
|
var recurseCompare = this.getRecurseCompare(treeToCompare, goalTree); |
|
|
var branchA = treeToCompare.branches[branchName]; |
|
|
var branchB = goalTree.branches[branchName]; |
|
|
|
|
|
return _.isEqual(branchA, branchB) && |
|
|
recurseCompare(treeToCompare.commits[branchA.target], goalTree.commits[branchB.target]); |
|
|
}; |
|
|
|
|
|
TreeCompare.compareAllBranchesWithinTreesHashAgnostic = function(treeToCompare, goalTree) { |
|
|
treeToCompare = this.convertTreeSafe(treeToCompare); |
|
|
goalTree = this.convertTreeSafe(goalTree); |
|
|
this.reduceTreeFields([treeToCompare, goalTree]); |
|
|
|
|
|
var allBranches = Object.assign( |
|
|
{}, |
|
|
treeToCompare.branches, |
|
|
goalTree.branches |
|
|
); |
|
|
var branchNames = Object.keys(allBranches || {}); |
|
|
|
|
|
return this.compareBranchesWithinTreesHashAgnostic(treeToCompare, goalTree, branchNames); |
|
|
}; |
|
|
|
|
|
TreeCompare.compareBranchesWithinTreesHashAgnostic = function(treeToCompare, goalTree, branches) { |
|
|
|
|
|
|
|
|
treeToCompare = this.convertTreeSafe(treeToCompare); |
|
|
goalTree = this.convertTreeSafe(goalTree); |
|
|
this.reduceTreeFields([treeToCompare, goalTree]); |
|
|
|
|
|
|
|
|
var compareBranchObjs = function(branchA, branchB) { |
|
|
if (!branchA || !branchB) { |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
branchA = Object.assign({}, branchA); |
|
|
branchB = Object.assign({}, branchB); |
|
|
branchA.target = this.getBaseRef(branchA.target); |
|
|
branchB.target = this.getBaseRef(branchB.target); |
|
|
|
|
|
return _.isEqual(branchA, branchB); |
|
|
}.bind(this); |
|
|
|
|
|
var recurseCompare = this.getRecurseCompareHashAgnostic(treeToCompare, goalTree); |
|
|
|
|
|
var result = true; |
|
|
branches.forEach(function(branchName) { |
|
|
var branchA = treeToCompare.branches[branchName]; |
|
|
var branchB = goalTree.branches[branchName]; |
|
|
|
|
|
result = result && compareBranchObjs(branchA, branchB) && |
|
|
recurseCompare(treeToCompare.commits[branchA.target], goalTree.commits[branchB.target]); |
|
|
}, this); |
|
|
return result; |
|
|
}; |
|
|
|
|
|
TreeCompare.evalAsserts = function(tree, assertsPerBranch) { |
|
|
var result = true; |
|
|
Object.keys(assertsPerBranch).forEach(function(branchName) { |
|
|
var asserts = assertsPerBranch[branchName]; |
|
|
result = result && this.evalAssertsOnBranch(tree, branchName, asserts); |
|
|
}, this); |
|
|
return result; |
|
|
}; |
|
|
|
|
|
TreeCompare.evalAssertsOnBranch = function(tree, branchName, asserts) { |
|
|
tree = this.convertTreeSafe(tree); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!tree.branches[branchName]) { |
|
|
return false; |
|
|
} |
|
|
|
|
|
var branch = tree.branches[branchName]; |
|
|
var queue = [branch.target]; |
|
|
var data = {}; |
|
|
while (queue.length) { |
|
|
var commitRef = queue.pop(); |
|
|
data[this.getBaseRef(commitRef)] = this.getNumHashes(commitRef); |
|
|
queue = queue.concat(tree.commits[commitRef].parents); |
|
|
} |
|
|
|
|
|
var result = true; |
|
|
asserts.forEach(function(assert) { |
|
|
try { |
|
|
result = result && assert(data); |
|
|
} catch (err) { |
|
|
console.warn('error during assert', err); |
|
|
console.log(err); |
|
|
result = false; |
|
|
} |
|
|
}); |
|
|
|
|
|
return result; |
|
|
}; |
|
|
|
|
|
TreeCompare.getNumHashes = function(ref) { |
|
|
var regexMap = [ |
|
|
[/^C(\d+)([']{0,3})$/, function(bits) { |
|
|
if (!bits[2]) { |
|
|
return 0; |
|
|
} |
|
|
return bits[2].length; |
|
|
}], |
|
|
[/^C(\d+)['][\^](\d+)$/, function(bits) { |
|
|
return Number(bits[2]); |
|
|
}] |
|
|
]; |
|
|
|
|
|
for (var i = 0; i < regexMap.length; i++) { |
|
|
var regex = regexMap[i][0]; |
|
|
var func = regexMap[i][1]; |
|
|
var results = regex.exec(ref); |
|
|
if (results) { |
|
|
return func(results); |
|
|
} |
|
|
} |
|
|
throw new Error('couldn\'t parse ref ' + ref); |
|
|
}; |
|
|
|
|
|
TreeCompare.getBaseRef = function(ref) { |
|
|
var idRegex = /^C(\d+)/; |
|
|
var bits = idRegex.exec(ref); |
|
|
if (!bits) { throw new Error('no regex matchy for ' + ref); } |
|
|
|
|
|
|
|
|
return 'C' + bits[1]; |
|
|
}; |
|
|
|
|
|
TreeCompare.getRecurseCompareHashAgnostic = function(treeToCompare, goalTree) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var getStrippedCommitCopy = function(commit) { |
|
|
if (!commit) { return {}; } |
|
|
return Object.assign( |
|
|
{}, |
|
|
commit, |
|
|
{ |
|
|
id: this.getBaseRef(commit.id), |
|
|
parents: null |
|
|
} |
|
|
); |
|
|
}.bind(this); |
|
|
|
|
|
var isEqual = function(commitA, commitB) { |
|
|
return _.isEqual( |
|
|
getStrippedCommitCopy(commitA), |
|
|
getStrippedCommitCopy(commitB) |
|
|
); |
|
|
}; |
|
|
return this.getRecurseCompare(treeToCompare, goalTree, {isEqual: isEqual}); |
|
|
}; |
|
|
|
|
|
TreeCompare.getRecurseCompare = function(treeToCompare, goalTree, options) { |
|
|
options = options || {}; |
|
|
|
|
|
|
|
|
var recurseCompare = function(commitA, commitB) { |
|
|
|
|
|
var result = options.isEqual ? |
|
|
options.isEqual(commitA, commitB) : _.isEqual(commitA, commitB); |
|
|
if (!result) { |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var maxNumParents = Math.max(commitA.parents.length, commitB.parents.length); |
|
|
for (var index = 0; index < maxNumParents; index++) { |
|
|
var pAid = commitA.parents[index]; |
|
|
var pBid = commitB.parents[index]; |
|
|
|
|
|
|
|
|
|
|
|
var childA = treeToCompare.commits[pAid]; |
|
|
var childB = goalTree.commits[pBid]; |
|
|
|
|
|
result = result && recurseCompare(childA, childB); |
|
|
} |
|
|
|
|
|
return result; |
|
|
}; |
|
|
return recurseCompare; |
|
|
}; |
|
|
|
|
|
TreeCompare.lowercaseTree = function(tree) { |
|
|
if (tree.HEAD) { |
|
|
tree.HEAD.target = tree.HEAD.target.toLocaleLowerCase(); |
|
|
} |
|
|
|
|
|
var branches = tree.branches || {}; |
|
|
tree.branches = {}; |
|
|
Object.keys(branches).forEach(function(name) { |
|
|
var obj = branches[name]; |
|
|
obj.id = obj.id.toLocaleLowerCase(); |
|
|
tree.branches[name.toLocaleLowerCase()] = obj; |
|
|
}); |
|
|
return tree; |
|
|
}; |
|
|
|
|
|
TreeCompare.convertTreeSafe = function(tree) { |
|
|
if (typeof tree !== 'string') { |
|
|
return tree; |
|
|
} |
|
|
tree = JSON.parse(unescape(tree)); |
|
|
|
|
|
|
|
|
|
|
|
this.lowercaseTree(tree); |
|
|
if (tree.originTree) { |
|
|
tree.originTree = this.lowercaseTree(tree.originTree); |
|
|
} |
|
|
return tree; |
|
|
}; |
|
|
|
|
|
TreeCompare.reduceTreeFields = function(trees) { |
|
|
var commitSaveFields = [ |
|
|
'parents', |
|
|
'id', |
|
|
'rootCommit' |
|
|
]; |
|
|
var branchSaveFields = [ |
|
|
'target', |
|
|
'id', |
|
|
'remoteTrackingBranchID' |
|
|
]; |
|
|
var tagSaveFields = [ |
|
|
'target', |
|
|
'id' |
|
|
]; |
|
|
|
|
|
var commitSortFields = ['children', 'parents']; |
|
|
|
|
|
var defaults = { |
|
|
remoteTrackingBranchID: null |
|
|
}; |
|
|
|
|
|
var treeDefaults = { |
|
|
tags: {} |
|
|
}; |
|
|
|
|
|
trees.forEach(function(tree) { |
|
|
Object.keys(treeDefaults).forEach(function(key) { |
|
|
var val = treeDefaults[key]; |
|
|
if (tree[key] === undefined) { |
|
|
tree[key] = val; |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
var saveOnly = function(tree, treeKey, saveFields, sortFields) { |
|
|
var objects = tree[treeKey]; |
|
|
Object.keys(objects).forEach(function(objKey) { |
|
|
var obj = objects[objKey]; |
|
|
|
|
|
var blank = {}; |
|
|
saveFields.forEach(function(field) { |
|
|
if (obj[field] !== undefined) { |
|
|
blank[field] = obj[field]; |
|
|
} else if (defaults[field] !== undefined) { |
|
|
blank[field] = defaults[field]; |
|
|
} |
|
|
}); |
|
|
|
|
|
Object.values(sortFields || {}).forEach(function(field) { |
|
|
|
|
|
if (obj[field]) { |
|
|
obj[field].sort(); |
|
|
blank[field] = obj[field]; |
|
|
} |
|
|
}); |
|
|
tree[treeKey][objKey] = blank; |
|
|
}); |
|
|
}; |
|
|
|
|
|
trees.forEach(function(tree) { |
|
|
saveOnly(tree, 'commits', commitSaveFields, commitSortFields); |
|
|
saveOnly(tree, 'branches', branchSaveFields); |
|
|
saveOnly(tree, 'tags', tagSaveFields); |
|
|
|
|
|
tree.HEAD = { |
|
|
target: tree.HEAD.target, |
|
|
id: tree.HEAD.id |
|
|
}; |
|
|
if (tree.originTree) { |
|
|
this.reduceTreeFields([tree.originTree]); |
|
|
} |
|
|
}, this); |
|
|
}; |
|
|
|
|
|
TreeCompare.compareTrees = function(treeToCompare, goalTree) { |
|
|
treeToCompare = this.convertTreeSafe(treeToCompare); |
|
|
goalTree = this.convertTreeSafe(goalTree); |
|
|
|
|
|
|
|
|
|
|
|
this.reduceTreeFields([treeToCompare, goalTree]); |
|
|
|
|
|
return _.isEqual(treeToCompare, goalTree); |
|
|
}; |
|
|
|
|
|
module.exports = TreeCompare; |
|
|
|