#![allow(missing_docs)]
use std::collections::BTreeMap;
use std::sync::Arc;
use itertools::Itertools as _;
use crate::backend;
use crate::backend::{TreeId, TreeValue};
use crate::repo_path::RepoPath;
use crate::store::Store;
use crate::tree::Tree;
#[derive(Debug)]
enum Override {
Tombstone,
Replace(TreeValue),
}
#[derive(Debug)]
pub struct TreeBuilder {
store: Arc<Store>,
base_tree_id: TreeId,
overrides: BTreeMap<RepoPath, Override>,
}
impl TreeBuilder {
pub fn new(store: Arc<Store>, base_tree_id: TreeId) -> TreeBuilder {
let overrides = BTreeMap::new();
TreeBuilder {
store,
base_tree_id,
overrides,
}
}
pub fn store(&self) -> &Store {
self.store.as_ref()
}
pub fn has_overrides(&self) -> bool {
!self.overrides.is_empty()
}
pub fn set(&mut self, path: RepoPath, value: TreeValue) {
assert!(!path.is_root());
self.overrides.insert(path, Override::Replace(value));
}
pub fn remove(&mut self, path: RepoPath) {
assert!(!path.is_root());
self.overrides.insert(path, Override::Tombstone);
}
pub fn write_tree(self) -> TreeId {
if self.overrides.is_empty() {
return self.base_tree_id;
}
let mut trees_to_write = self.get_base_trees();
for (path, file_override) in self.overrides {
let (dir, basename) = path.split().unwrap();
let tree = trees_to_write.get_mut(&dir).unwrap();
match file_override {
Override::Replace(value) => {
tree.set(basename.clone(), value);
}
Override::Tombstone => {
tree.remove(basename);
}
}
}
let store = &self.store;
let mut dirs_to_write = trees_to_write.keys().cloned().collect_vec();
while let Some(dir) = dirs_to_write.pop() {
let tree = trees_to_write.remove(&dir).unwrap();
if let Some((parent, basename)) = dir.split() {
let parent_tree = trees_to_write.get_mut(&parent).unwrap();
if tree.is_empty() {
if let Some(TreeValue::Tree(_)) = parent_tree.value(basename) {
parent_tree.remove(basename);
} else {
}
} else {
let tree = store.write_tree(&dir, tree).unwrap();
parent_tree.set(basename.clone(), TreeValue::Tree(tree.id().clone()));
}
} else {
assert!(dirs_to_write.is_empty());
return store.write_tree(&dir, tree).unwrap().id().clone();
}
}
unreachable!("trees_to_write must contain the root tree");
}
fn get_base_trees(&self) -> BTreeMap<RepoPath, backend::Tree> {
let store = &self.store;
let mut tree_cache = {
let dir = RepoPath::root();
let tree = store.get_tree(&dir, &self.base_tree_id).unwrap();
BTreeMap::from([(dir, tree)])
};
fn populate_trees<'a>(
tree_cache: &'a mut BTreeMap<RepoPath, Tree>,
store: &Arc<Store>,
dir: RepoPath,
) -> &'a Tree {
if tree_cache.contains_key(&dir) {
return tree_cache.get(&dir).unwrap();
}
let (parent, basename) = dir.split().expect("root must be populated");
let tree = populate_trees(tree_cache, store, parent)
.sub_tree(basename)
.unwrap_or_else(|| Tree::null(store.clone(), dir.clone()));
tree_cache.entry(dir).or_insert(tree)
}
for path in self.overrides.keys() {
let parent = path.parent().unwrap();
populate_trees(&mut tree_cache, store, parent);
}
tree_cache
.into_iter()
.map(|(dir, tree)| (dir, tree.data().clone()))
.collect()
}
}