use std::{collections::HashMap, fs, path::Path};
use anyhow::Result;
use objects::object::{Tree, TreeEntry};
use repo::Repository;
use super::super::prepare_dir_for_file_replacement;
pub(crate) fn apply_merged_tree(repo: &Repository, tree: &Tree) -> Result<()> {
let current_tree = repo
.current_state()?
.and_then(|s| repo.store().get_tree(&s.tree).transpose())
.transpose()?
.unwrap_or_default();
let current_entries: HashMap<&str, &TreeEntry> = current_tree
.entries()
.iter()
.map(|e| (e.name.as_str(), e))
.collect();
let merged_entries: HashMap<&str, &TreeEntry> = tree
.entries()
.iter()
.map(|e| (e.name.as_str(), e))
.collect();
for (name, current) in ¤t_entries {
if !merged_entries.contains_key(name) {
let path = repo.root().join(name);
remove_path_for_drop(repo, &path, current, ¤t_tree)?;
}
}
for (name, merged) in &merged_entries {
if let Some(current) = current_entries.get(name)
&& current.entry_type != merged.entry_type
{
let path = repo.root().join(name);
remove_path_for_type_change(repo, &path, current, merged, ¤t_tree)?;
}
}
repo.materialize_tree(tree, repo.root())?;
Ok(())
}
fn remove_path_for_drop(
repo: &Repository,
path: &Path,
current: &TreeEntry,
current_tree: &Tree,
) -> Result<()> {
let metadata = match fs::symlink_metadata(path) {
Ok(m) => m,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
Err(error) => return Err(anyhow::Error::from(error)),
};
if metadata.is_symlink() || metadata.is_file() {
fs::remove_file(path)?;
return Ok(());
}
if !metadata.is_dir() {
return Ok(());
}
let source_subtree = source_subtree_for(repo, current, current_tree, ¤t.name)?;
repo.remove_tracked_descendants_with_source(path, &source_subtree)?;
Ok(())
}
fn remove_path_for_type_change(
repo: &Repository,
path: &Path,
current: &TreeEntry,
merged: &TreeEntry,
current_tree: &Tree,
) -> Result<()> {
let metadata = match fs::symlink_metadata(path) {
Ok(m) => m,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
Err(error) => return Err(anyhow::Error::from(error)),
};
if metadata.is_symlink() || metadata.is_file() {
fs::remove_file(path)?;
return Ok(());
}
if !metadata.is_dir() {
return Ok(());
}
let _ = merged; let source_subtree = source_subtree_for(repo, current, current_tree, ¤t.name)?;
repo.remove_tracked_descendants_with_source(path, &source_subtree)?;
if path.exists() {
prepare_dir_for_file_replacement(path)?;
}
Ok(())
}
fn source_subtree_for(
repo: &Repository,
entry: &TreeEntry,
current_tree: &Tree,
name: &str,
) -> Result<Tree> {
if entry.entry_type == objects::object::EntryType::Tree {
Ok(repo
.resolve_subtree(current_tree, Path::new(name))?
.unwrap_or_default())
} else {
Ok(Tree::default())
}
}