use std::{collections::HashMap, fs, path::Path};
use anyhow::{Result, anyhow};
use objects::{
object::{Tree, TreeEntry},
store::ObjectStore,
};
use repo::Repository;
use super::super::prepare_dir_for_file_replacement;
use crate::cli::commands::RecoveryAdvice;
pub(crate) fn apply_merged_tree(repo: &Repository, tree: &Tree) -> Result<()> {
let current_tree = match repo.current_state()? {
Some(state) => repo.store().get_tree(&state.tree)?.ok_or_else(|| {
anyhow!(RecoveryAdvice::merge_integrity_refusal(
format!(
"current state {} references tree {} but the object store has no such tree; \
aborting merge application to avoid silently replacing tracked content with \
an empty baseline",
state.change_id.short(),
state.tree,
),
format!(
"current state {} references missing tree {} in the object store",
state.change_id.short(),
state.tree,
),
"merge application would compare against an empty baseline and could silently replace tracked content with the merged tree, losing tracked paths outside it",
"merge application stopped before materializing the merged tree; current HEAD, refs, object store, and worktree were left unchanged",
))
})?,
None => Tree::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_computed_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 {
return Ok(Tree::default());
}
repo.resolve_subtree(current_tree, Path::new(name))?
.ok_or_else(|| {
anyhow!(RecoveryAdvice::merge_integrity_refusal(
format!(
"current tree records subtree {:?} (hash {}) but the object store cannot \
resolve it; aborting merge application to avoid leaving the subtree's tracked \
descendants orphaned on disk",
name,
entry.hash,
),
format!(
"current tree records subtree {:?} with missing hash {} in the object store",
name,
entry.hash,
),
"merge application would drop the directory entry without a source subtree and could leave its tracked descendants orphaned on disk as untracked additions",
"repository HEAD, refs, and object store were left unchanged; merge application stopped before removing this subtree's tracked descendants or writing the final merged tree",
))
})
}