use crate::document::Revision;
use crate::rev_tree::{RevNode, RevPath, RevStatus, RevTree, collect_leaves};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MergeResult {
NewLeaf,
NewBranch,
InternalNode,
}
pub fn merge_tree(tree: &RevTree, new_path: &RevPath, rev_limit: u64) -> (RevTree, MergeResult) {
let mut result_tree = tree.clone();
let merge_result = do_merge(&mut result_tree, new_path);
if rev_limit > 0 {
let _stemmed = stem(&mut result_tree, rev_limit);
}
(result_tree, merge_result)
}
fn do_merge(tree: &mut RevTree, new_path: &RevPath) -> MergeResult {
for existing in tree.iter_mut() {
let result = try_merge_path(existing, new_path);
if let Some(merge_result) = result {
return merge_result;
}
}
tree.push(new_path.clone());
MergeResult::NewBranch
}
fn try_merge_path(existing: &mut RevPath, new_path: &RevPath) -> Option<MergeResult> {
let overlap = find_overlap(existing, new_path);
match overlap {
None => None, Some(OverlapInfo {
existing_node_path,
new_remainder,
is_exact_match,
}) => {
if is_exact_match && new_remainder.is_empty() {
return Some(MergeResult::InternalNode);
}
let target = navigate_to_mut(&mut existing.tree, &existing_node_path);
if new_remainder.is_empty() {
Some(MergeResult::InternalNode)
} else {
let result = graft_nodes(target, &new_remainder);
Some(result)
}
}
}
}
struct OverlapInfo {
existing_node_path: Vec<usize>,
new_remainder: Vec<RevNode>,
is_exact_match: bool,
}
fn find_overlap(existing: &RevPath, new_path: &RevPath) -> Option<OverlapInfo> {
let new_chain = flatten_chain(&new_path.tree, new_path.pos);
for (i, (new_pos, new_hash)) in new_chain.iter().enumerate() {
if let Some(path_indices) = find_node_path(&existing.tree, existing.pos, *new_pos, new_hash)
{
let remainder = build_remainder_from_chain(&new_chain, i, &new_path.tree, new_path.pos);
return Some(OverlapInfo {
existing_node_path: path_indices,
new_remainder: remainder,
is_exact_match: true,
});
}
}
let existing_leaves = collect_leaf_positions(existing);
let new_root_pos = new_path.pos;
let new_root_hash = &new_path.tree.hash;
for (leaf_pos, leaf_hash, leaf_path) in &existing_leaves {
if *leaf_pos == new_root_pos && leaf_hash == new_root_hash {
let remainder = if new_path.tree.children.is_empty() {
vec![]
} else {
new_path.tree.children.clone()
};
return Some(OverlapInfo {
existing_node_path: leaf_path.clone(),
new_remainder: remainder,
is_exact_match: true,
});
}
}
None
}
fn flatten_chain(node: &RevNode, start_pos: u64) -> Vec<(u64, String)> {
let mut chain = Vec::new();
fn walk(node: &RevNode, pos: u64, chain: &mut Vec<(u64, String)>) {
chain.push((pos, node.hash.clone()));
if let Some(child) = node.children.first() {
walk(child, pos + 1, chain);
}
}
walk(node, start_pos, &mut chain);
chain
}
fn find_node_path(
node: &RevNode,
current_pos: u64,
target_pos: u64,
target_hash: &str,
) -> Option<Vec<usize>> {
if current_pos == target_pos && node.hash == target_hash {
return Some(vec![]);
}
for (i, child) in node.children.iter().enumerate() {
if let Some(mut path) = find_node_path(child, current_pos + 1, target_pos, target_hash) {
path.insert(0, i);
return Some(path);
}
}
None
}
fn collect_leaf_positions(path: &RevPath) -> Vec<(u64, String, Vec<usize>)> {
let mut leaves = Vec::new();
fn walk(
node: &RevNode,
pos: u64,
current_path: &mut Vec<usize>,
leaves: &mut Vec<(u64, String, Vec<usize>)>,
) {
if node.children.is_empty() {
leaves.push((pos, node.hash.clone(), current_path.clone()));
}
for (i, child) in node.children.iter().enumerate() {
current_path.push(i);
walk(child, pos + 1, current_path, leaves);
current_path.pop();
}
}
let mut current = Vec::new();
walk(&path.tree, path.pos, &mut current, &mut leaves);
leaves
}
fn build_remainder_from_chain(
_chain: &[(u64, String)],
overlap_index: usize,
original_tree: &RevNode,
_original_pos: u64,
) -> Vec<RevNode> {
let depth_to_overlap = overlap_index;
fn get_subtree_at_depth(node: &RevNode, depth: usize) -> Option<&RevNode> {
if depth == 0 {
return Some(node);
}
if let Some(child) = node.children.first() {
get_subtree_at_depth(child, depth - 1)
} else {
None
}
}
if let Some(overlap_node) = get_subtree_at_depth(original_tree, depth_to_overlap) {
overlap_node.children.clone()
} else {
vec![]
}
}
fn navigate_to_mut<'a>(node: &'a mut RevNode, path: &[usize]) -> &'a mut RevNode {
let mut current = node;
for &idx in path {
current = &mut current.children[idx];
}
current
}
fn graft_nodes(target: &mut RevNode, new_nodes: &[RevNode]) -> MergeResult {
let mut is_new_branch = false;
let mut added_anything = false;
for new_node in new_nodes {
let existing_child = target.children.iter_mut().find(|c| c.hash == new_node.hash);
match existing_child {
Some(existing) => {
for grandchild in &new_node.children {
let sub_nodes = vec![grandchild.clone()];
let result = graft_nodes(existing, &sub_nodes);
match result {
MergeResult::NewBranch => {
is_new_branch = true;
added_anything = true;
}
MergeResult::NewLeaf => {
added_anything = true;
}
MergeResult::InternalNode => {}
}
}
}
None => {
if !target.children.is_empty() {
is_new_branch = true;
}
target.children.push(new_node.clone());
added_anything = true;
}
}
}
if !added_anything {
MergeResult::InternalNode
} else if is_new_branch {
MergeResult::NewBranch
} else {
MergeResult::NewLeaf
}
}
pub fn winning_rev(tree: &RevTree) -> Option<Revision> {
let leaves = collect_leaves(tree);
leaves.first().map(|l| Revision::new(l.pos, l.hash.clone()))
}
pub fn is_deleted(tree: &RevTree) -> bool {
collect_leaves(tree)
.first()
.map(|l| l.deleted)
.unwrap_or(false)
}
pub fn collect_conflicts(tree: &RevTree) -> Vec<Revision> {
let leaves = collect_leaves(tree);
leaves
.iter()
.skip(1) .filter(|l| !l.deleted)
.map(|l| Revision::new(l.pos, l.hash.clone()))
.collect()
}
fn max_depth(node: &RevNode) -> u64 {
if node.children.is_empty() {
return 0;
}
node.children
.iter()
.map(|c| 1 + max_depth(c))
.max()
.unwrap_or(0)
}
pub fn stem(tree: &mut RevTree, depth: u64) -> Vec<String> {
let mut stemmed = Vec::new();
let mut new_roots: RevTree = Vec::new();
for path in tree.drain(..) {
new_roots.extend(stem_path(path, depth, &mut stemmed));
}
new_roots.retain(|p| !is_empty_node(&p.tree));
*tree = new_roots;
stemmed
}
fn stem_path(path: RevPath, depth: u64, stemmed: &mut Vec<String>) -> Vec<RevPath> {
let mut pos = path.pos;
let mut node = path.tree;
loop {
if max_depth(&node) < depth {
return vec![RevPath { pos, tree: node }];
}
if node.children.len() <= 1 {
stemmed.push(node.hash.clone());
match node.children.pop() {
Some(child) => {
node = child;
pos += 1;
}
None => return vec![], }
} else {
stemmed.push(node.hash.clone());
let children = std::mem::take(&mut node.children);
let mut out = Vec::new();
for child in children {
out.extend(stem_path(
RevPath {
pos: pos + 1,
tree: child,
},
depth,
stemmed,
));
}
return out;
}
}
}
fn is_empty_node(node: &RevNode) -> bool {
node.hash.is_empty() && node.children.is_empty()
}
pub fn latest_leaf(tree: &RevTree, pos: u64, hash: &str) -> Option<Revision> {
fn find_node<'a>(
node: &'a RevNode,
cur: u64,
tpos: u64,
thash: &str,
) -> Option<(&'a RevNode, u64)> {
if cur == tpos && node.hash == thash {
return Some((node, cur));
}
for c in &node.children {
if let Some(found) = find_node(c, cur + 1, tpos, thash) {
return Some(found);
}
}
None
}
fn collect(node: &RevNode, pos: u64, out: &mut Vec<(u64, String, bool)>) {
if node.children.is_empty() {
out.push((pos, node.hash.clone(), node.opts.deleted));
} else {
for c in &node.children {
collect(c, pos + 1, out);
}
}
}
for path in tree {
if let Some((node, node_pos)) = find_node(&path.tree, path.pos, pos, hash) {
let mut leaves: Vec<(u64, String, bool)> = Vec::new();
collect(node, node_pos, &mut leaves);
leaves.sort_by(|a, b| {
a.2.cmp(&b.2)
.then_with(|| b.0.cmp(&a.0))
.then_with(|| b.1.cmp(&a.1))
});
return leaves
.into_iter()
.next()
.map(|(p, h, _)| Revision::new(p, h));
}
}
None
}
pub fn latest_rev(tree: &RevTree, pos: u64, hash: &str) -> Option<Revision> {
for path in tree {
if let Some(rev) = find_latest_in_node(&path.tree, path.pos, pos, hash) {
return Some(rev);
}
}
None
}
fn find_latest_in_node(
node: &RevNode,
current_pos: u64,
target_pos: u64,
target_hash: &str,
) -> Option<Revision> {
if current_pos == target_pos && node.hash == target_hash {
if node.status == RevStatus::Available {
return Some(Revision::new(current_pos, node.hash.clone()));
}
return find_first_available_leaf(node, current_pos);
}
for child in &node.children {
if let Some(rev) = find_latest_in_node(child, current_pos + 1, target_pos, target_hash) {
return Some(rev);
}
}
None
}
fn find_first_available_leaf(node: &RevNode, pos: u64) -> Option<Revision> {
if node.children.is_empty() {
if node.status == RevStatus::Available {
return Some(Revision::new(pos, node.hash.clone()));
}
return None;
}
for child in &node.children {
if let Some(rev) = find_first_available_leaf(child, pos + 1) {
return Some(rev);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rev_tree::{NodeOpts, RevNode, RevPath, build_path_from_revs};
fn leaf(hash: &str) -> RevNode {
RevNode {
hash: hash.into(),
status: RevStatus::Available,
opts: NodeOpts::default(),
children: vec![],
}
}
fn deleted_leaf(hash: &str) -> RevNode {
RevNode {
hash: hash.into(),
status: RevStatus::Available,
opts: NodeOpts { deleted: true },
children: vec![],
}
}
fn node(hash: &str, children: Vec<RevNode>) -> RevNode {
RevNode {
hash: hash.into(),
status: RevStatus::Available,
opts: NodeOpts::default(),
children,
}
}
fn simple_tree() -> RevTree {
vec![RevPath {
pos: 1,
tree: node("a", vec![node("b", vec![leaf("c")])]),
}]
}
#[test]
fn winning_rev_simple() {
let tree = simple_tree();
let winner = winning_rev(&tree).unwrap();
assert_eq!(winner.pos, 3);
assert_eq!(winner.hash, "c");
}
#[test]
fn winning_rev_conflict_picks_higher_hash() {
let tree = vec![RevPath {
pos: 1,
tree: node("a", vec![leaf("b"), leaf("c")]),
}];
let winner = winning_rev(&tree).unwrap();
assert_eq!(winner.hash, "c"); }
#[test]
fn winning_rev_conflict_prefers_longer() {
let tree = vec![RevPath {
pos: 1,
tree: node("a", vec![node("b", vec![leaf("d")]), leaf("c")]),
}];
let winner = winning_rev(&tree).unwrap();
assert_eq!(winner.pos, 3);
assert_eq!(winner.hash, "d"); }
#[test]
fn winning_rev_non_deleted_beats_deleted() {
let tree = vec![RevPath {
pos: 1,
tree: node("a", vec![leaf("b"), deleted_leaf("z")]),
}];
let winner = winning_rev(&tree).unwrap();
assert_eq!(winner.hash, "b");
}
#[test]
fn no_conflicts_on_linear() {
let tree = simple_tree();
assert!(collect_conflicts(&tree).is_empty());
}
#[test]
fn conflicts_on_branches() {
let tree = vec![RevPath {
pos: 1,
tree: node("a", vec![leaf("b"), leaf("c")]),
}];
let conflicts = collect_conflicts(&tree);
assert_eq!(conflicts.len(), 1);
assert_eq!(conflicts[0].hash, "b"); }
#[test]
fn is_deleted_false_for_normal() {
assert!(!is_deleted(&simple_tree()));
}
#[test]
fn is_deleted_true_when_winner_deleted() {
let tree = vec![RevPath {
pos: 1,
tree: deleted_leaf("a"),
}];
assert!(is_deleted(&tree));
}
#[test]
fn merge_extends_linear_chain() {
let tree = vec![RevPath {
pos: 1,
tree: node("a", vec![leaf("b")]),
}];
let new_path = build_path_from_revs(
3,
&["c".into(), "b".into()],
NodeOpts::default(),
RevStatus::Available,
);
let (merged, result) = merge_tree(&tree, &new_path, 1000);
assert_eq!(result, MergeResult::NewLeaf);
let winner = winning_rev(&merged).unwrap();
assert_eq!(winner.pos, 3);
assert_eq!(winner.hash, "c");
}
#[test]
fn merge_creates_conflict_branch() {
let tree = vec![RevPath {
pos: 1,
tree: node("a", vec![leaf("b")]),
}];
let new_path = build_path_from_revs(
2,
&["c".into(), "a".into()],
NodeOpts::default(),
RevStatus::Available,
);
let (merged, result) = merge_tree(&tree, &new_path, 1000);
assert_eq!(result, MergeResult::NewBranch);
let conflicts = collect_conflicts(&merged);
assert_eq!(conflicts.len(), 1);
}
#[test]
fn merge_duplicate_is_internal_node() {
let tree = vec![RevPath {
pos: 1,
tree: node("a", vec![leaf("b")]),
}];
let new_path = build_path_from_revs(
2,
&["b".into(), "a".into()],
NodeOpts::default(),
RevStatus::Available,
);
let (_merged, result) = merge_tree(&tree, &new_path, 1000);
assert_eq!(result, MergeResult::InternalNode);
}
#[test]
fn merge_disjoint_creates_new_root() {
let tree = vec![RevPath {
pos: 1,
tree: node("a", vec![leaf("b")]),
}];
let new_path = build_path_from_revs(
2,
&["y".into(), "x".into()],
NodeOpts::default(),
RevStatus::Available,
);
let (merged, result) = merge_tree(&tree, &new_path, 1000);
assert_eq!(result, MergeResult::NewBranch);
assert_eq!(merged.len(), 2); }
#[test]
fn stem_prunes_old_revisions() {
let mut tree = vec![RevPath {
pos: 1,
tree: node(
"a",
vec![node("b", vec![node("c", vec![node("d", vec![leaf("e")])])])],
),
}];
let stemmed = stem(&mut tree, 3);
assert!(!stemmed.is_empty());
assert!(tree[0].pos > 1);
let leaves = collect_leaves(&tree);
assert_eq!(leaves[0].hash, "e");
}
#[test]
fn stem_splits_at_branch_point() {
let mut tree = vec![RevPath {
pos: 1,
tree: node("a", vec![node("b", vec![leaf("c"), leaf("d")])]),
}];
let stemmed = stem(&mut tree, 1);
assert_eq!(stemmed.len(), 2); assert!(stemmed.contains(&"a".to_string()));
assert!(stemmed.contains(&"b".to_string()));
assert_eq!(tree.len(), 2); let leaves = collect_leaves(&tree);
let hashes: Vec<&str> = leaves.iter().map(|l| l.hash.as_str()).collect();
assert!(hashes.contains(&"c"));
assert!(hashes.contains(&"d"));
assert!(leaves.iter().all(|l| l.pos == 3));
}
#[test]
fn stem_prunes_deep_branch_above_cut_line() {
let mut tree = vec![RevPath {
pos: 1,
tree: node(
"a",
vec![
leaf("b"),
node("c", vec![node("d", vec![node("e", vec![leaf("f")])])]),
],
),
}];
let stemmed = stem(&mut tree, 2);
assert!(stemmed.contains(&"a".to_string()));
assert!(stemmed.contains(&"c".to_string()));
assert!(stemmed.contains(&"d".to_string()));
assert_eq!(tree.len(), 2);
for path in &tree {
assert!(max_depth(&path.tree) < 2, "every chain must fit the limit");
}
let winner = winning_rev(&tree).unwrap();
assert_eq!(winner.pos, 5);
assert_eq!(winner.hash, "f");
}
#[test]
fn stem_short_tree_unchanged() {
let mut tree = vec![RevPath {
pos: 1,
tree: node("a", vec![leaf("b")]),
}];
let stemmed = stem(&mut tree, 3);
assert!(stemmed.is_empty());
assert_eq!(tree[0].pos, 1);
}
#[test]
fn latest_rev_finds_available_node() {
let tree = simple_tree(); let rev = latest_rev(&tree, 3, "c").unwrap();
assert_eq!(rev.pos, 3);
assert_eq!(rev.hash, "c");
}
#[test]
fn latest_rev_walks_to_leaf_from_missing() {
let tree = vec![RevPath {
pos: 1,
tree: RevNode {
hash: "a".into(),
status: RevStatus::Missing,
opts: NodeOpts::default(),
children: vec![leaf("b")],
},
}];
let rev = latest_rev(&tree, 1, "a").unwrap();
assert_eq!(rev.pos, 2);
assert_eq!(rev.hash, "b");
}
#[test]
fn latest_rev_none_for_nonexistent() {
let tree = simple_tree();
assert!(latest_rev(&tree, 5, "zzz").is_none());
}
#[test]
fn latest_rev_finds_internal_node() {
let tree = simple_tree(); let rev = latest_rev(&tree, 2, "b").unwrap();
assert_eq!(rev.pos, 2);
assert_eq!(rev.hash, "b");
}
#[test]
fn latest_rev_on_empty_tree() {
let tree: RevTree = vec![];
assert!(latest_rev(&tree, 1, "a").is_none());
}
#[test]
fn latest_leaf_walks_linear_chain_to_tip() {
let tree = simple_tree();
let rev = latest_leaf(&tree, 1, "a").unwrap();
assert_eq!(rev.pos, 3);
assert_eq!(rev.hash, "c");
assert_eq!(latest_leaf(&tree, 3, "c").unwrap().hash, "c");
}
#[test]
fn latest_leaf_stays_on_requested_branch() {
let tree = vec![RevPath {
pos: 1,
tree: node(
"a",
vec![
node("b", vec![leaf("c")]),
node("z", vec![node("d", vec![leaf("e")])]),
],
),
}];
assert_eq!(latest_leaf(&tree, 2, "b").unwrap().to_string(), "3-c");
assert_eq!(latest_leaf(&tree, 2, "z").unwrap().to_string(), "4-e");
}
#[test]
fn merge_exact_root_match_no_children() {
let tree = vec![RevPath {
pos: 1,
tree: leaf("a"),
}];
let new_path = RevPath {
pos: 1,
tree: leaf("a"),
};
let (_, result) = merge_tree(&tree, &new_path, 1000);
assert_eq!(result, MergeResult::InternalNode);
}
#[test]
fn merge_same_branch_extends_deeper() {
let tree = simple_tree();
let new_path = build_path_from_revs(
4,
&["d".into(), "c".into(), "b".into(), "a".into()],
NodeOpts::default(),
RevStatus::Available,
);
let (merged, result) = merge_tree(&tree, &new_path, 1000);
assert_eq!(result, MergeResult::NewLeaf);
let winner = winning_rev(&merged).unwrap();
assert_eq!(winner.pos, 4);
assert_eq!(winner.hash, "d");
}
#[test]
fn winning_rev_empty_tree() {
let tree: RevTree = vec![];
assert!(winning_rev(&tree).is_none());
}
#[test]
fn is_deleted_empty_tree() {
let tree: RevTree = vec![];
assert!(!is_deleted(&tree));
}
#[test]
fn collect_conflicts_deleted_leaves_excluded() {
let tree = vec![RevPath {
pos: 1,
tree: node("a", vec![leaf("b"), deleted_leaf("c")]),
}];
let conflicts = collect_conflicts(&tree);
assert!(conflicts.is_empty());
}
}