use crate::core::db::tree_db::{TreeObject, TreeObjectChild};
use crate::core::db::{self, path_db};
use crate::error::OxenError;
use rocksdb::{DBWithThreadMode, MultiThreaded};
use std::collections::HashSet;
use std::path::PathBuf;
use super::TreeObjectReader;
pub enum CommitTreeReader {
TreeObjectReader(TreeObjectReader),
DB(DBWithThreadMode<MultiThreaded>),
}
impl CommitTreeReader {
pub fn get_entry_from_child(
&self,
child: &TreeObjectChild,
) -> Result<Option<TreeObject>, OxenError> {
match self {
CommitTreeReader::TreeObjectReader(reader) => reader.get_node_from_child(child),
CommitTreeReader::DB(db) => path_db::get_entry(db, child.hash()),
}
}
pub fn get_root_entry(&self) -> Result<Option<TreeObject>, OxenError> {
match self {
CommitTreeReader::TreeObjectReader(reader) => reader.get_root_node(),
CommitTreeReader::DB(db) => path_db::get_entry(db, ""),
}
}
}
pub struct TreeDBMerger {
pub client_reader: CommitTreeReader,
pub server_reader: CommitTreeReader,
pub lca_reader: CommitTreeReader,
}
impl TreeDBMerger {
pub fn new(
client_db_path: PathBuf,
server_reader: TreeObjectReader,
lca_reader: TreeObjectReader,
) -> TreeDBMerger {
let opts = db::opts::default();
let client_db: DBWithThreadMode<MultiThreaded> =
DBWithThreadMode::open(&opts, client_db_path).unwrap();
TreeDBMerger {
client_reader: CommitTreeReader::DB(client_db),
server_reader: CommitTreeReader::TreeObjectReader(server_reader),
lca_reader: CommitTreeReader::TreeObjectReader(lca_reader),
}
}
pub fn r_tree_has_conflict(
&self,
client_node: &Option<TreeObject>,
server_node: &Option<TreeObject>,
lca_node: &Option<TreeObject>,
) -> Result<bool, OxenError> {
log::debug!(
"calling tree on client {:?} server {:?} lca {:?}",
client_node,
server_node,
lca_node
);
match (client_node, server_node, lca_node) {
(Some(client_node), Some(server_node), Some(lca_node)) => {
self.handle_all_nodes_present(client_node, server_node, lca_node)
}
(Some(client_node), Some(server_node), None) => {
self.handle_missing_lca(client_node, server_node)
}
(Some(head), None, Some(lca)) | (None, Some(head), Some(lca)) => {
self.handle_missing_head(head, lca)
}
(Some(_head), None, None) | (None, Some(_head), None) => {
Ok(false)
}
_ => {
log::debug!("flagging conflict due to undefined pattern");
log::debug!("client node is {:?}", client_node);
log::debug!("server node is {:?}", server_node);
log::debug!("lca node is {:?}", lca_node);
Ok(true)
}
}
}
pub fn handle_all_nodes_present(
&self,
client_node: &TreeObject,
server_node: &TreeObject,
lca_node: &TreeObject,
) -> Result<bool, OxenError> {
if client_node.hash() == server_node.hash() {
return Ok(false); }
if client_node.hash() == lca_node.hash() || server_node.hash() == lca_node.hash() {
return Ok(false); }
match (client_node, server_node, lca_node) {
(
TreeObject::Dir {
children: client_children,
..
}
| TreeObject::VNode {
children: client_children,
..
},
TreeObject::Dir {
children: server_children,
..
}
| TreeObject::VNode {
children: server_children,
..
},
TreeObject::Dir {
children: _lca_children,
..
}
| TreeObject::VNode {
children: _lca_children,
..
},
) => {
let mut visited_paths: HashSet<&PathBuf> = HashSet::new();
for child in client_children {
visited_paths.insert(child.path());
let client_child: Option<TreeObject> =
self.client_reader.get_entry_from_child(child)?;
let maybe_server_child = server_node.binary_search_on_path(child.path())?;
let maybe_lca_child = lca_node.binary_search_on_path(child.path())?;
let server_child: Option<TreeObject> = match maybe_server_child {
Some(child) => self.server_reader.get_entry_from_child(&child)?,
None => None,
};
let lca_child: Option<TreeObject> = match maybe_lca_child {
Some(child) => self.lca_reader.get_entry_from_child(&child)?,
None => None,
};
if self.r_tree_has_conflict(&client_child, &server_child, &lca_child)? {
return Ok(true);
}
}
for child in server_children {
if !visited_paths.contains(child.path()) {
let maybe_lca_child = lca_node.binary_search_on_path(child.path())?;
let maybe_lca_node = match maybe_lca_child {
Some(child) => self.lca_reader.get_entry_from_child(&child)?,
None => None,
};
if let Some(lca_node) = maybe_lca_node {
if lca_node.hash() != child.hash() {
log::debug!("flagging conflict due to deletion on client and modification on server");
return Ok(true);
}
}
}
}
Ok(false)
}
(_, _, _) => {
log::debug!("flagging conflict due to different file type in all 3 nodes 1");
Ok(true) }
}
}
fn handle_missing_lca(
&self,
client_node: &TreeObject,
server_node: &TreeObject,
) -> Result<bool, OxenError> {
if client_node.hash() == server_node.hash() {
return Ok(false);
}
match (client_node, server_node) {
(
TreeObject::Dir {
children: client_children,
..
}
| TreeObject::VNode {
children: client_children,
..
},
TreeObject::Dir { .. } | TreeObject::VNode { .. },
) => {
for child in client_children {
let client_child: Option<TreeObject> =
self.client_reader.get_entry_from_child(child)?;
let maybe_server_child = server_node.binary_search_on_path(child.path())?;
let server_child: Option<TreeObject> = match maybe_server_child {
Some(child) => self.server_reader.get_entry_from_child(&child)?,
None => None,
};
let lca_child: Option<TreeObject> = None;
if self.r_tree_has_conflict(&client_child, &server_child, &lca_child)? {
return Ok(true);
}
}
Ok(false)
}
(_, _) => {
log::debug!("flagging conflict due to different file type in all 3 nodes 2");
Ok(true)
}
}
}
fn handle_missing_head(&self, head: &TreeObject, lca: &TreeObject) -> Result<bool, OxenError> {
if head.hash() == lca.hash() {
return Ok(false);
}
match (head, lca) {
(
TreeObject::Dir {
children: head_children,
..
}
| TreeObject::VNode {
children: head_children,
..
},
TreeObject::Dir {
children: _lca_children,
..
}
| TreeObject::VNode {
children: _lca_children,
..
},
) => {
let mut visited_paths: HashSet<&PathBuf> = HashSet::new();
for head_child in head_children {
visited_paths.insert(head_child.path());
let head_node: Option<TreeObject> =
self.client_reader.get_entry_from_child(head_child)?;
let other_head_node: Option<TreeObject> = None;
let maybe_lca_child = lca.binary_search_on_path(head_child.path())?;
let lca_node = match maybe_lca_child {
Some(child) => self.lca_reader.get_entry_from_child(&child)?,
None => None,
};
if self.r_tree_has_conflict(&head_node, &other_head_node, &lca_node)? {
return Ok(true);
}
}
Ok(false)
}
(_, _) => {
log::debug!("flagging conflict due to different file type in all 3 nodes 3");
Ok(true)
}
}
}
}