use crate::{Error, Result};
use rs_merkle::{algorithms::Sha256, Hasher, MerkleTree};
use std::{
collections::{HashMap, HashSet},
hash::Hash,
ops::Range,
};
use super::{CommitPair, CommitProof, CommitRelationship, Comparison};
#[derive(Default)]
pub struct CommitTree {
tree: MerkleTree<Sha256>,
}
impl CommitTree {
pub fn new() -> Self {
Self {
tree: MerkleTree::<Sha256>::new(),
}
}
pub fn hash(data: &[u8]) -> [u8; 32] {
Sha256::hash(data)
}
pub fn len(&self) -> usize {
self.tree.leaves_len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn insert(&mut self, hash: <Sha256 as Hasher>::Hash) -> &mut Self {
self.tree.insert(hash);
self
}
pub fn append(
&mut self,
hashes: &mut Vec<<Sha256 as Hasher>::Hash>,
) -> &mut Self {
self.tree.append(hashes);
self
}
pub fn commit(&mut self) {
self.tree.commit()
}
pub fn rollback(&mut self) {
self.tree.rollback()
}
pub fn leaves(&self) -> Option<Vec<<Sha256 as Hasher>::Hash>> {
self.tree.leaves()
}
pub fn head(&self) -> Result<CommitProof> {
let range = self.tree.leaves_len() - 1..self.tree.leaves_len();
self.proof_range(range)
}
pub fn proof_range(&self, indices: Range<usize>) -> Result<CommitProof> {
let leaf_indices = indices.collect::<Vec<_>>();
self.proof(&leaf_indices)
}
pub fn proof(&self, leaf_indices: &[usize]) -> Result<CommitProof> {
let root = self.root().ok_or(Error::NoRootCommit)?;
let proof = self.tree.proof(leaf_indices);
let indices = if leaf_indices.is_empty() {
0..0
} else if leaf_indices.len() > 1 {
leaf_indices[0]..leaf_indices[leaf_indices.len() - 1] + 1
} else {
leaf_indices[0]..leaf_indices[0] + 1
};
Ok(CommitProof {
root,
proof,
length: self.len(),
indices,
})
}
pub fn compare(&self, proof: &CommitProof) -> Result<Comparison> {
let CommitProof {
root: other_root,
proof,
length,
indices,
} = proof;
let root = self.root().ok_or(Error::NoRootCommit)?;
if &root == other_root {
Ok(Comparison::Equal)
} else if indices.start < self.len() && indices.end <= self.len() {
let leaves = self.tree.leaves().unwrap_or_default();
let indices_to_prove = indices.clone().collect::<Vec<_>>();
let leaves_to_prove = indices
.clone()
.map(|i| *leaves.get(i).unwrap())
.collect::<Vec<_>>();
if proof.verify(
*other_root,
indices_to_prove.as_slice(),
leaves_to_prove.as_slice(),
*length,
) {
Ok(Comparison::Contains(indices_to_prove, leaves_to_prove))
} else {
Ok(Comparison::Unknown)
}
} else {
Ok(Comparison::Unknown)
}
}
pub fn root(&self) -> Option<<Sha256 as Hasher>::Hash> {
self.tree.root()
}
pub fn root_hex(&self) -> Option<String> {
self.tree.root_hex()
}
pub fn relationship(
&self,
other_proof: CommitProof,
match_proof: Option<CommitProof>,
) -> Result<CommitRelationship> {
let self_proof = self.head()?;
let equals = self_proof.root() == other_proof.root();
let pair = CommitPair {
local: self_proof,
remote: other_proof.clone(),
};
Ok(if equals {
CommitRelationship::Equal(pair)
} else if match_proof.is_some() {
let (diff, _) =
pair.remote.len().overflowing_sub(pair.local.len());
CommitRelationship::Behind(pair, diff)
} else {
let comparison = self.compare(&other_proof)?;
let is_ahead = matches!(comparison, Comparison::Contains(_, _));
if is_ahead {
let (diff, _) =
pair.local.len().overflowing_sub(pair.remote.len());
CommitRelationship::Ahead(pair, diff)
} else {
CommitRelationship::Diverged(pair)
}
})
}
pub fn contains(
&self,
other_proof: &CommitProof,
) -> Result<Option<CommitProof>> {
Ok(match self.compare(other_proof)? {
Comparison::Contains(indices, _leaves) => {
Some(self.proof(&indices)?)
}
_ => None,
})
}
}
#[derive(Default)]
pub struct MultiTree<'a, K>
where
K: Hash + Eq + PartialEq + Copy,
{
trees: HashMap<K, &'a CommitTree>,
}
impl<'a, K> MultiTree<'a, K>
where
K: Hash + Eq + PartialEq + Copy,
{
pub fn insert(&mut self, key: K, tree: &'a CommitTree) {
self.trees.insert(key, tree);
}
pub fn head(&'a self) -> Result<HashMap<&'a K, CommitProof>> {
let mut proofs = HashMap::new();
for (id, tree) in self.trees.iter() {
proofs.insert(id, tree.head()?);
}
Ok(proofs)
}
pub fn contains(
&'a self,
other: &HashMap<&'a K, CommitProof>,
) -> Result<HashMap<&'a K, Option<CommitProof>>> {
let mut matches = HashMap::new();
for (id, tree) in self.trees.iter() {
if let Some(proof) = other.get(id) {
matches.insert(id, tree.contains(proof)?);
} else {
matches.insert(id, None);
}
}
Ok(matches)
}
pub fn relationship(
&'a self,
proofs: &HashMap<&'a K, CommitProof>,
matches: &HashMap<&'a K, Option<CommitProof>>,
) -> Result<HashMap<&'a K, CommitRelationship>> {
let mut relationships = HashMap::new();
for (id, tree) in self.trees.iter() {
if let (Some(proof), Some(match_proof)) =
(proofs.get(id), matches.get(id))
{
relationships.insert(
id,
tree.relationship(proof.clone(), match_proof.clone())?,
);
}
}
Ok(relationships)
}
}
pub struct NodeTree<'a, I, K>
where
I: Hash + Eq + PartialEq + Copy,
K: Hash + Eq + PartialEq + Copy,
{
id: I,
tree: &'a MultiTree<'a, K>,
nodes: HashMap<I, &'a MultiTree<'a, K>>,
}
impl<'a, I, K> NodeTree<'a, I, K>
where
I: Hash + Eq + PartialEq + Copy,
K: Hash + Eq + PartialEq + Copy,
{
pub fn new(id: I, tree: &'a MultiTree<'a, K>) -> Self {
Self {
id,
tree,
nodes: HashMap::new(),
}
}
pub fn insert(&mut self, key: I, tree: &'a MultiTree<'a, K>) {
self.nodes.insert(key, tree);
}
pub fn compute(
&self,
matches: HashMap<&'a I, HashMap<&'a K, Option<CommitProof>>>,
) -> Result<HashSet<Operation<I, K>>> {
let mut ops = HashSet::new();
for (node_id, tree) in self.nodes.iter() {
if let Some(matches) = matches.get(node_id) {
let proof = tree.head()?;
let relationships =
self.tree.relationship(&proof, matches)?;
for (id, relationship) in relationships {
match relationship {
CommitRelationship::Ahead(commit, difference) => {
ops.insert(Operation::Push {
local: self.id,
remote: *node_id,
id: *id,
commit,
difference,
});
}
CommitRelationship::Behind(commit, difference) => {
ops.insert(Operation::Pull {
local: self.id,
remote: *node_id,
id: *id,
commit,
difference,
});
}
CommitRelationship::Diverged(commit) => {
ops.insert(Operation::Conflict {
local: self.id,
remote: *node_id,
id: *id,
commit,
});
}
CommitRelationship::Equal(_) => {}
}
}
}
}
Ok(ops)
}
}
#[derive(Debug, Hash, Eq, PartialEq)]
pub enum Operation<I, K>
where
I: Hash + Eq + PartialEq,
K: Hash + Eq + PartialEq,
{
Push {
local: I,
remote: I,
id: K,
commit: CommitPair,
difference: usize,
},
Pull {
local: I,
remote: I,
id: K,
commit: CommitPair,
difference: usize,
},
Conflict {
local: I,
remote: I,
id: K,
commit: CommitPair,
},
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
events::WriteEvent,
test_utils::*,
vault::{Vault, VaultAccess, VaultBuilder, VaultEntry},
};
use anyhow::Result;
use uuid::Uuid;
fn from_vault(vault: &Vault) -> CommitTree {
let mut commit_tree = CommitTree::new();
for (_, commit) in vault.commits() {
commit_tree.tree.insert(commit.to_bytes());
}
commit_tree.tree.commit();
commit_tree
}
async fn mock_commit_tree() -> Result<CommitTree> {
let (encryption_key, _, passphrase) = mock_encryption_key()?;
let mut vault =
VaultBuilder::new().password(passphrase, None).await?;
let secrets = [
("Note one", "First note"),
("Note two", "Second note"),
("Note three", "Third note"),
];
for (label, note) in secrets {
let (_secret_meta, _secret_value, meta_bytes, secret_bytes) =
mock_secret_note(label, note).await?;
let meta_aead =
vault.encrypt(&encryption_key, &meta_bytes).await?;
let secret_aead =
vault.encrypt(&encryption_key, &secret_bytes).await?;
let (commit, _) =
Vault::commit_hash(&meta_aead, &secret_aead).await?;
let _secret_id = match vault
.create(commit, VaultEntry(meta_aead, secret_aead))
.await?
{
WriteEvent::CreateSecret(secret_id, _) => secret_id,
_ => unreachable!(),
};
}
Ok(from_vault(&vault))
}
#[tokio::test]
async fn commit_tree_from_vault() -> Result<()> {
let commit_tree = mock_commit_tree().await?;
assert!(commit_tree.root().is_some());
Ok(())
}
#[tokio::test]
async fn commit_proof_serde() -> Result<()> {
let commit_tree = mock_commit_tree().await?;
let proof = commit_tree.head()?;
let json = serde_json::to_string_pretty(&proof)?;
let commit_proof: CommitProof = serde_json::from_str(&json)?;
assert_eq!(proof.root, commit_proof.root);
assert_eq!(
proof.proof.proof_hashes(),
commit_proof.proof.proof_hashes()
);
assert_eq!(proof.length, commit_proof.length);
assert_eq!(proof.indices, commit_proof.indices);
Ok(())
}
#[test]
fn commit_proof_compare() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let hash2 = CommitTree::hash(b"world");
let hash3 = CommitTree::hash(b"goodbye");
let mut tree1 = CommitTree::new();
tree1.insert(hash1);
tree1.commit();
let mut tree2 = CommitTree::new();
tree2.insert(hash1);
tree2.commit();
let mut tree3 = CommitTree::new();
tree3.insert(hash3);
tree3.commit();
assert!(tree1.root().is_some());
assert!(tree2.root().is_some());
assert_eq!(tree1.root(), tree2.root());
let proof = tree1.proof(&[0])?;
let comparison = tree2.compare(&proof)?;
assert_eq!(Comparison::Equal, comparison);
tree2.insert(hash2);
tree2.commit();
assert_ne!(tree1.root(), tree2.root());
let proof = tree1.proof(&[0])?;
let comparison = tree2.compare(&proof)?;
assert!(matches!(comparison, Comparison::Contains(_, _)));
let proof = tree2.proof(&[0])?;
let comparison = tree1.compare(&proof)?;
assert!(matches!(comparison, Comparison::Contains(_, _)));
let proof = tree1.proof(&[0])?;
let comparison = tree3.compare(&proof)?;
assert!(matches!(comparison, Comparison::Unknown));
let proof = tree3.proof(&[0])?;
let comparison = tree2.compare(&proof)?;
assert!(matches!(comparison, Comparison::Unknown));
Ok(())
}
#[test]
fn commit_proof_relationship_equal() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let mut local = CommitTree::new();
local.insert(hash1);
local.commit();
let mut remote = CommitTree::new();
remote.insert(hash1);
remote.commit();
let local_proof = local.head()?;
let match_proof = remote.contains(&local_proof)?;
let remote_proof = remote.head()?;
let relationship = local.relationship(remote_proof, match_proof)?;
assert!(matches!(relationship, CommitRelationship::Equal(_)));
Ok(())
}
#[test]
fn commit_proof_relationship_diverged() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let hash2 = CommitTree::hash(b"world");
let mut local = CommitTree::new();
local.insert(hash1);
local.commit();
let mut remote = CommitTree::new();
remote.insert(hash2);
remote.commit();
let local_proof = local.head()?;
let match_proof = remote.contains(&local_proof)?;
let remote_proof = remote.head()?;
let relationship = local.relationship(remote_proof, match_proof)?;
assert!(matches!(relationship, CommitRelationship::Diverged(_)));
Ok(())
}
#[test]
fn commit_proof_relationship_behind() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let hash2 = CommitTree::hash(b"world");
let mut local = CommitTree::new();
local.insert(hash1);
local.commit();
let mut remote = CommitTree::new();
remote.insert(hash1);
remote.insert(hash2);
remote.commit();
let local_proof = local.head()?;
let match_proof = remote.contains(&local_proof)?;
let remote_proof = remote.head()?;
let relationship = local.relationship(remote_proof, match_proof)?;
assert!(matches!(relationship, CommitRelationship::Behind(_, _)));
Ok(())
}
#[test]
fn commit_proof_relationship_ahead() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let hash2 = CommitTree::hash(b"world");
let mut local = CommitTree::new();
local.insert(hash1);
local.insert(hash2);
local.commit();
let mut remote = CommitTree::new();
remote.insert(hash1);
remote.commit();
let local_proof = local.head()?;
let match_proof = remote.contains(&local_proof)?;
let remote_proof = remote.head()?;
let relationship = local.relationship(remote_proof, match_proof)?;
assert!(matches!(relationship, CommitRelationship::Ahead(_, _)));
Ok(())
}
#[test]
fn commit_multi_equal() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let hash2 = CommitTree::hash(b"world");
let tree_id1 = Uuid::new_v4();
let tree_id2 = Uuid::new_v4();
let mut local_tree1 = CommitTree::new();
local_tree1.insert(hash1);
local_tree1.commit();
let mut local_tree2 = CommitTree::new();
local_tree2.insert(hash2);
local_tree2.commit();
let mut local: MultiTree<Uuid> = Default::default();
local.insert(tree_id1, &local_tree1);
local.insert(tree_id2, &local_tree2);
let mut remote_tree1 = CommitTree::new();
remote_tree1.insert(hash1);
remote_tree1.commit();
let mut remote_tree2 = CommitTree::new();
remote_tree2.insert(hash2);
remote_tree2.commit();
let mut remote: MultiTree<Uuid> = Default::default();
remote.insert(tree_id1, &remote_tree1);
remote.insert(tree_id2, &remote_tree2);
let local_proofs = local.head()?;
let match_proofs = remote.contains(&local_proofs)?;
let remote_proofs = remote.head()?;
let mut relationships =
local.relationship(&remote_proofs, &match_proofs)?;
assert!(matches!(
relationships.remove(&tree_id1).unwrap(),
CommitRelationship::Equal(_)
));
assert!(matches!(
relationships.remove(&tree_id2).unwrap(),
CommitRelationship::Equal(_)
));
Ok(())
}
#[test]
fn commit_multi_ahead() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let hash2 = CommitTree::hash(b"world");
let hash3 = CommitTree::hash(b"goodbye");
let tree_id1 = Uuid::new_v4();
let tree_id2 = Uuid::new_v4();
let mut local_tree1 = CommitTree::new();
local_tree1.insert(hash1);
local_tree1.commit();
let mut local_tree2 = CommitTree::new();
local_tree2.insert(hash2);
local_tree2.insert(hash3); local_tree2.commit();
let mut local: MultiTree<Uuid> = Default::default();
local.insert(tree_id1, &local_tree1);
local.insert(tree_id2, &local_tree2);
let mut remote_tree1 = CommitTree::new();
remote_tree1.insert(hash1);
remote_tree1.commit();
let mut remote_tree2 = CommitTree::new();
remote_tree2.insert(hash2);
remote_tree2.commit();
let mut remote: MultiTree<Uuid> = Default::default();
remote.insert(tree_id1, &remote_tree1);
remote.insert(tree_id2, &remote_tree2);
let local_proofs = local.head()?;
let match_proofs = remote.contains(&local_proofs)?;
let remote_proofs = remote.head()?;
let mut relationships =
local.relationship(&remote_proofs, &match_proofs)?;
assert!(matches!(
relationships.remove(&tree_id1).unwrap(),
CommitRelationship::Equal(_)
));
assert!(matches!(
relationships.remove(&tree_id2).unwrap(),
CommitRelationship::Ahead(_, _)
));
Ok(())
}
#[test]
fn commit_multi_behind() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let hash2 = CommitTree::hash(b"world");
let hash3 = CommitTree::hash(b"goodbye");
let tree_id1 = Uuid::new_v4();
let tree_id2 = Uuid::new_v4();
let mut local_tree1 = CommitTree::new();
local_tree1.insert(hash1);
local_tree1.commit();
let mut local_tree2 = CommitTree::new();
local_tree2.insert(hash2);
local_tree2.commit();
let mut local: MultiTree<Uuid> = Default::default();
local.insert(tree_id1, &local_tree1);
local.insert(tree_id2, &local_tree2);
let mut remote_tree1 = CommitTree::new();
remote_tree1.insert(hash1);
remote_tree1.commit();
let mut remote_tree2 = CommitTree::new();
remote_tree2.insert(hash2);
remote_tree2.insert(hash3); remote_tree2.commit();
let mut remote: MultiTree<Uuid> = Default::default();
remote.insert(tree_id1, &remote_tree1);
remote.insert(tree_id2, &remote_tree2);
let local_proofs = local.head()?;
let match_proofs = remote.contains(&local_proofs)?;
let remote_proofs = remote.head()?;
let mut relationships =
local.relationship(&remote_proofs, &match_proofs)?;
assert!(matches!(
relationships.remove(&tree_id1).unwrap(),
CommitRelationship::Equal(_)
));
assert!(matches!(
relationships.remove(&tree_id2).unwrap(),
CommitRelationship::Behind(_, _)
));
Ok(())
}
#[test]
fn commit_multi_diverged() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let hash2 = CommitTree::hash(b"world");
let tree_id1 = Uuid::new_v4();
let tree_id2 = Uuid::new_v4();
let mut local_tree1 = CommitTree::new();
local_tree1.insert(hash1);
local_tree1.commit();
let mut local_tree2 = CommitTree::new();
local_tree2.insert(hash2);
local_tree2.commit();
let mut local: MultiTree<Uuid> = Default::default();
local.insert(tree_id1, &local_tree1);
local.insert(tree_id2, &local_tree2);
let mut remote_tree1 = CommitTree::new();
remote_tree1.insert(hash2);
remote_tree1.commit();
let mut remote_tree2 = CommitTree::new();
remote_tree2.insert(hash1);
remote_tree2.commit();
let mut remote: MultiTree<Uuid> = Default::default();
remote.insert(tree_id1, &remote_tree1);
remote.insert(tree_id2, &remote_tree2);
let local_proofs = local.head()?;
let match_proofs = remote.contains(&local_proofs)?;
let remote_proofs = remote.head()?;
let mut relationships =
local.relationship(&remote_proofs, &match_proofs)?;
assert!(matches!(
relationships.remove(&tree_id1).unwrap(),
CommitRelationship::Diverged(_)
));
assert!(matches!(
relationships.remove(&tree_id2).unwrap(),
CommitRelationship::Diverged(_)
));
Ok(())
}
#[test]
fn commit_node_equal() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let hash2 = CommitTree::hash(b"world");
let node_id1 = Uuid::new_v4();
let node_id2 = Uuid::new_v4();
let tree_id1 = Uuid::new_v4();
let tree_id2 = Uuid::new_v4();
let mut local_tree1 = CommitTree::new();
local_tree1.insert(hash1);
local_tree1.commit();
let mut local_tree2 = CommitTree::new();
local_tree2.insert(hash2);
local_tree2.commit();
let mut local: MultiTree<Uuid> = Default::default();
local.insert(tree_id1, &local_tree1);
local.insert(tree_id2, &local_tree2);
let mut remote_tree1 = CommitTree::new();
remote_tree1.insert(hash1);
remote_tree1.commit();
let mut remote_tree2 = CommitTree::new();
remote_tree2.insert(hash2);
remote_tree2.commit();
let mut remote: MultiTree<Uuid> = Default::default();
remote.insert(tree_id1, &remote_tree1);
remote.insert(tree_id2, &remote_tree2);
let mut node: NodeTree<Uuid, Uuid> = NodeTree::new(node_id1, &local);
let local_head = local.head()?;
node.insert(node_id2, &remote);
let mut matches = HashMap::new();
matches.insert(&node_id2, remote.contains(&local_head)?);
let operations = node.compute(matches)?;
assert!(operations.is_empty());
Ok(())
}
#[test]
fn commit_node_push() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let hash2 = CommitTree::hash(b"world");
let hash3 = CommitTree::hash(b"goodbye");
let node_id1 = Uuid::new_v4();
let node_id2 = Uuid::new_v4();
let tree_id1 = Uuid::new_v4();
let tree_id2 = Uuid::new_v4();
let mut local_tree1 = CommitTree::new();
local_tree1.insert(hash1);
local_tree1.commit();
let mut local_tree2 = CommitTree::new();
local_tree2.insert(hash2);
local_tree2.insert(hash3); local_tree2.commit();
let mut local: MultiTree<Uuid> = Default::default();
local.insert(tree_id1, &local_tree1);
local.insert(tree_id2, &local_tree2);
let mut remote_tree1 = CommitTree::new();
remote_tree1.insert(hash1);
remote_tree1.commit();
let mut remote_tree2 = CommitTree::new();
remote_tree2.insert(hash2);
remote_tree2.commit();
let mut remote: MultiTree<Uuid> = Default::default();
remote.insert(tree_id1, &remote_tree1);
remote.insert(tree_id2, &remote_tree2);
let mut node: NodeTree<Uuid, Uuid> = NodeTree::new(node_id1, &local);
let mut local_head = local.head()?;
let mut remote_head = remote.head()?;
node.insert(node_id2, &remote);
let mut matches = HashMap::new();
matches.insert(&node_id2, remote.contains(&local_head)?);
let operations = node.compute(matches)?;
assert_eq!(1, operations.len());
let push = Operation::Push {
local: node_id1,
remote: node_id2,
id: tree_id2,
difference: 1,
commit: CommitPair {
local: local_head.remove(&tree_id2).unwrap(),
remote: remote_head.remove(&tree_id2).unwrap(),
},
};
assert!(operations.contains(&push));
Ok(())
}
#[test]
fn commit_node_pull() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let hash2 = CommitTree::hash(b"world");
let hash3 = CommitTree::hash(b"goodbye");
let node_id1 = Uuid::new_v4();
let node_id2 = Uuid::new_v4();
let tree_id1 = Uuid::new_v4();
let tree_id2 = Uuid::new_v4();
let mut local_tree1 = CommitTree::new();
local_tree1.insert(hash1);
local_tree1.commit();
let mut local_tree2 = CommitTree::new();
local_tree2.insert(hash2);
local_tree2.commit();
let mut local: MultiTree<Uuid> = Default::default();
local.insert(tree_id1, &local_tree1);
local.insert(tree_id2, &local_tree2);
let mut remote_tree1 = CommitTree::new();
remote_tree1.insert(hash1);
remote_tree1.commit();
let mut remote_tree2 = CommitTree::new();
remote_tree2.insert(hash2);
remote_tree2.insert(hash3); remote_tree2.commit();
let mut remote: MultiTree<Uuid> = Default::default();
remote.insert(tree_id1, &remote_tree1);
remote.insert(tree_id2, &remote_tree2);
let mut node: NodeTree<Uuid, Uuid> = NodeTree::new(node_id1, &local);
let mut local_head = local.head()?;
let mut remote_head = remote.head()?;
node.insert(node_id2, &remote);
let mut matches = HashMap::new();
matches.insert(&node_id2, remote.contains(&local_head)?);
let operations = node.compute(matches)?;
assert_eq!(1, operations.len());
let pull = Operation::Pull {
local: node_id1,
remote: node_id2,
id: tree_id2,
difference: 1,
commit: CommitPair {
local: local_head.remove(&tree_id2).unwrap(),
remote: remote_head.remove(&tree_id2).unwrap(),
},
};
assert!(operations.contains(&pull));
Ok(())
}
#[test]
fn commit_node_conflict() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let hash2 = CommitTree::hash(b"world");
let hash3 = CommitTree::hash(b"goodbye");
let node_id1 = Uuid::new_v4();
let node_id2 = Uuid::new_v4();
let tree_id1 = Uuid::new_v4();
let tree_id2 = Uuid::new_v4();
let mut local_tree1 = CommitTree::new();
local_tree1.insert(hash1);
local_tree1.commit();
let mut local_tree2 = CommitTree::new();
local_tree2.insert(hash2);
local_tree2.commit();
let mut local: MultiTree<Uuid> = Default::default();
local.insert(tree_id1, &local_tree1);
local.insert(tree_id2, &local_tree2);
let mut remote_tree1 = CommitTree::new();
remote_tree1.insert(hash1);
remote_tree1.commit();
let mut remote_tree2 = CommitTree::new();
remote_tree2.insert(hash3); remote_tree2.commit();
let mut remote: MultiTree<Uuid> = Default::default();
remote.insert(tree_id1, &remote_tree1);
remote.insert(tree_id2, &remote_tree2);
let mut node: NodeTree<Uuid, Uuid> = NodeTree::new(node_id1, &local);
let mut local_head = local.head()?;
let mut remote_head = remote.head()?;
node.insert(node_id2, &remote);
let mut matches = HashMap::new();
matches.insert(&node_id2, remote.contains(&local_head)?);
let operations = node.compute(matches)?;
assert_eq!(1, operations.len());
let conflict = Operation::Conflict {
local: node_id1,
remote: node_id2,
id: tree_id2,
commit: CommitPair {
local: local_head.remove(&tree_id2).unwrap(),
remote: remote_head.remove(&tree_id2).unwrap(),
},
};
assert!(operations.contains(&conflict));
Ok(())
}
#[test]
fn commit_node_pull_push() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let hash2 = CommitTree::hash(b"world");
let hash3 = CommitTree::hash(b"goodbye");
let node_id1 = Uuid::new_v4();
let node_id2 = Uuid::new_v4();
let tree_id1 = Uuid::new_v4();
let tree_id2 = Uuid::new_v4();
let mut local_tree1 = CommitTree::new();
local_tree1.insert(hash1);
local_tree1.commit();
let mut local_tree2 = CommitTree::new();
local_tree2.insert(hash2);
local_tree2.insert(hash3); local_tree2.commit();
let mut local: MultiTree<Uuid> = Default::default();
local.insert(tree_id1, &local_tree1);
local.insert(tree_id2, &local_tree2);
let mut remote_tree1 = CommitTree::new();
remote_tree1.insert(hash1);
remote_tree1.insert(hash3); remote_tree1.commit();
let mut remote_tree2 = CommitTree::new();
remote_tree2.insert(hash2);
remote_tree2.commit();
let mut remote: MultiTree<Uuid> = Default::default();
remote.insert(tree_id1, &remote_tree1);
remote.insert(tree_id2, &remote_tree2);
let mut node: NodeTree<Uuid, Uuid> = NodeTree::new(node_id1, &local);
let mut local_head = local.head()?;
let mut remote_head = remote.head()?;
node.insert(node_id2, &remote);
let mut matches = HashMap::new();
matches.insert(&node_id2, remote.contains(&local_head)?);
let operations = node.compute(matches)?;
assert_eq!(2, operations.len());
let push = Operation::Push {
local: node_id1,
remote: node_id2,
id: tree_id2,
difference: 1,
commit: CommitPair {
local: local_head.remove(&tree_id2).unwrap(),
remote: remote_head.remove(&tree_id2).unwrap(),
},
};
assert!(operations.contains(&push));
let pull = Operation::Pull {
local: node_id1,
remote: node_id2,
id: tree_id1,
difference: 1,
commit: CommitPair {
local: local_head.remove(&tree_id1).unwrap(),
remote: remote_head.remove(&tree_id1).unwrap(),
},
};
assert!(operations.contains(&pull));
Ok(())
}
#[test]
fn commit_node_pull_push_conflict() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let hash2 = CommitTree::hash(b"world");
let hash3 = CommitTree::hash(b"goodbye");
let node_id1 = Uuid::new_v4();
let node_id2 = Uuid::new_v4();
let tree_id1 = Uuid::new_v4();
let tree_id2 = Uuid::new_v4();
let tree_id3 = Uuid::new_v4();
let mut local_tree1 = CommitTree::new();
local_tree1.insert(hash1);
local_tree1.commit();
let mut local_tree2 = CommitTree::new();
local_tree2.insert(hash2);
local_tree2.insert(hash3); local_tree2.commit();
let mut local_tree3 = CommitTree::new();
local_tree3.insert(hash1);
local_tree3.commit();
let mut local: MultiTree<Uuid> = Default::default();
local.insert(tree_id1, &local_tree1);
local.insert(tree_id2, &local_tree2);
local.insert(tree_id3, &local_tree3);
let mut remote_tree1 = CommitTree::new();
remote_tree1.insert(hash1);
remote_tree1.insert(hash3); remote_tree1.commit();
let mut remote_tree2 = CommitTree::new();
remote_tree2.insert(hash2);
remote_tree2.commit();
let mut remote_tree3 = CommitTree::new();
remote_tree3.insert(hash2); remote_tree3.commit();
let mut remote: MultiTree<Uuid> = Default::default();
remote.insert(tree_id1, &remote_tree1);
remote.insert(tree_id2, &remote_tree2);
remote.insert(tree_id3, &remote_tree3);
let mut node: NodeTree<Uuid, Uuid> = NodeTree::new(node_id1, &local);
let mut local_head = local.head()?;
let mut remote_head = remote.head()?;
node.insert(node_id2, &remote);
let mut matches = HashMap::new();
matches.insert(&node_id2, remote.contains(&local_head)?);
let operations = node.compute(matches)?;
assert_eq!(3, operations.len());
let push = Operation::Push {
local: node_id1,
remote: node_id2,
id: tree_id2,
difference: 1,
commit: CommitPair {
local: local_head.remove(&tree_id2).unwrap(),
remote: remote_head.remove(&tree_id2).unwrap(),
},
};
assert!(operations.contains(&push));
let pull = Operation::Pull {
local: node_id1,
remote: node_id2,
id: tree_id1,
difference: 1,
commit: CommitPair {
local: local_head.remove(&tree_id1).unwrap(),
remote: remote_head.remove(&tree_id1).unwrap(),
},
};
assert!(operations.contains(&pull));
let conflict = Operation::Conflict {
local: node_id1,
remote: node_id2,
id: tree_id3,
commit: CommitPair {
local: local_head.remove(&tree_id3).unwrap(),
remote: remote_head.remove(&tree_id3).unwrap(),
},
};
assert!(operations.contains(&conflict));
Ok(())
}
#[test]
fn commit_multi_node() -> Result<()> {
let hash1 = CommitTree::hash(b"hello");
let hash2 = CommitTree::hash(b"world");
let hash3 = CommitTree::hash(b"goodbye");
let node_id1 = Uuid::new_v4();
let node_id2 = Uuid::new_v4();
let node_id3 = Uuid::new_v4();
let tree_id1 = Uuid::new_v4();
let tree_id2 = Uuid::new_v4();
let tree_id3 = Uuid::new_v4();
let mut local_tree1 = CommitTree::new();
local_tree1.insert(hash1);
local_tree1.commit();
let mut local_tree2 = CommitTree::new();
local_tree2.insert(hash2);
local_tree2.insert(hash3);
local_tree2.commit();
let mut local_tree3 = CommitTree::new();
local_tree3.insert(hash3);
local_tree3.commit();
let mut local: MultiTree<Uuid> = Default::default();
local.insert(tree_id1, &local_tree1);
local.insert(tree_id2, &local_tree2);
local.insert(tree_id3, &local_tree3);
let mut node2_tree1 = CommitTree::new();
node2_tree1.insert(hash1);
node2_tree1.insert(hash3);
node2_tree1.commit();
let mut node2_tree2 = CommitTree::new();
node2_tree2.insert(hash2);
node2_tree2.commit();
let mut node2_tree3 = CommitTree::new();
node2_tree3.insert(hash2); node2_tree3.commit();
let mut node2: MultiTree<Uuid> = Default::default();
node2.insert(tree_id1, &node2_tree1);
node2.insert(tree_id2, &node2_tree2);
node2.insert(tree_id3, &node2_tree3);
let mut node3_tree1 = CommitTree::new();
node3_tree1.insert(hash1);
node3_tree1.commit();
let mut node3_tree2 = CommitTree::new();
node3_tree2.insert(hash2);
node3_tree2.commit();
let mut node3_tree3 = CommitTree::new();
node3_tree3.insert(hash3);
node3_tree3.commit();
let mut node3: MultiTree<Uuid> = Default::default();
node3.insert(tree_id1, &node3_tree1);
node3.insert(tree_id2, &node3_tree2);
node3.insert(tree_id3, &node3_tree3);
let mut node: NodeTree<Uuid, Uuid> = NodeTree::new(node_id1, &local);
let mut local_head = local.head()?;
let mut node2_head = node2.head()?;
let mut node3_head = node3.head()?;
node.insert(node_id2, &node2);
node.insert(node_id3, &node3);
let mut matches = HashMap::new();
matches.insert(&node_id2, node2.contains(&local_head)?);
matches.insert(&node_id3, node3.contains(&local_head)?);
let operations = node.compute(matches)?;
let conflict = Operation::Conflict {
local: node_id1,
remote: node_id2,
id: tree_id3,
commit: CommitPair {
local: local_head.remove(&tree_id3).unwrap(),
remote: node2_head.remove(&tree_id3).unwrap(),
},
};
assert!(operations.contains(&conflict));
let node2_push = Operation::Push {
local: node_id1,
remote: node_id2,
id: tree_id2,
difference: 1,
commit: CommitPair {
local: local_head.get(&tree_id2).cloned().unwrap(),
remote: node2_head.remove(&tree_id2).unwrap(),
},
};
assert!(operations.contains(&node2_push));
let node3_push = Operation::Push {
local: node_id1,
remote: node_id3,
id: tree_id2,
difference: 1,
commit: CommitPair {
local: local_head.get(&tree_id2).cloned().unwrap(),
remote: node3_head.remove(&tree_id2).unwrap(),
},
};
assert!(operations.contains(&node3_push));
let pull = Operation::Pull {
local: node_id1,
remote: node_id2,
id: tree_id1,
difference: 1,
commit: CommitPair {
local: local_head.get(&tree_id1).cloned().unwrap(),
remote: node2_head.remove(&tree_id1).unwrap(),
},
};
assert!(operations.contains(&pull));
Ok(())
}
}