use std::{path::Path, time::Instant};
use log::debug;
use typed_arena::Arena;
use crate::{
MergeResult, Revision, ast::AstNode, lang_profile::LangProfile, matching::ApproxExactMatching,
merge_3dm::three_way_merge, parsed_merge::ParsedMerge, settings::DisplaySettings,
tree_matcher::TreeMatcher,
};
pub const STRUCTURED_RESOLUTION_METHOD: &str = "structured_resolution";
pub const FULLY_STRUCTURED_METHOD: &str = "fully_structured";
pub(crate) const ZDIFF3_DETECTED: &str =
"Mergiraf cannot solve conflicts displayed in the zdiff style";
pub fn structured_merge(
contents_base: &str,
contents_left: &str,
contents_right: &str,
parsed_merge: Option<&ParsedMerge>,
settings: &DisplaySettings,
lang_profile: &LangProfile,
debug_dir: Option<&Path>,
) -> Result<MergeResult, String> {
let arena = Arena::new();
let ref_arena = Arena::new();
let primary_matcher = TreeMatcher {
min_height: 1,
sim_threshold: 0.4,
max_recovery_size: 100,
use_rted: true,
};
let auxiliary_matcher = TreeMatcher {
min_height: 2,
sim_threshold: 0.6,
max_recovery_size: 100,
use_rted: false,
};
let start = Instant::now();
let tree_base = AstNode::parse(contents_base, lang_profile, &arena, &ref_arena);
let tree_left = AstNode::parse(contents_left, lang_profile, &arena, &ref_arena);
let tree_right = AstNode::parse(contents_right, lang_profile, &arena, &ref_arena);
debug!("parsing all three files took {:?}", start.elapsed());
let (tree_base, tree_left, tree_right) = match (tree_base, tree_left, tree_right) {
(Err(_), Ok(_), Ok(_)) => return Err(ZDIFF3_DETECTED.into()),
(b, l, r) => (
b.map_err(|e| e.to_string())?,
l.map_err(|e| e.to_string())?,
r.map_err(|e| e.to_string())?,
),
};
let initial_matchings = parsed_merge.map(|parsed_merge| {
(
ApproxExactMatching::from_exact(
parsed_merge
.generate_matching(Revision::Base, Revision::Left, tree_base, tree_left)
.add_submatches(),
),
ApproxExactMatching::from_exact(
parsed_merge
.generate_matching(Revision::Base, Revision::Right, tree_base, tree_right)
.add_submatches(),
),
)
});
let (result_tree, class_mapping) = three_way_merge(
tree_base,
tree_left,
tree_right,
initial_matchings.as_ref(),
&primary_matcher,
&auxiliary_matcher,
settings,
debug_dir,
);
debug!("{result_tree}");
let merged_text = result_tree.to_merged_text(&class_mapping);
let revisions_to_check = if merged_text.count_conflicts() == 0 {
[Revision::Base].as_slice()
} else {
[Revision::Base, Revision::Left, Revision::Right].as_slice()
};
for revision in revisions_to_check {
let merged_revision = merged_text.reconstruct_revision(*revision);
let arena = Arena::new();
let ref_arena = Arena::new();
let tree = AstNode::parse(
&merged_revision,
lang_profile,
&arena,
&ref_arena,
)
.map_err(|err| {
format!(
"merge discarded because rendered revision {revision} has a parsing error: {err}"
)
})?;
if !result_tree.isomorphic_to_source(tree, *revision, &class_mapping) {
debug!(
"discarding merge because rendered revision {revision} isn't isomorphic to the merged tree"
);
return Err("merge discarded after isomorphism check".to_owned());
}
}
let method = if parsed_merge.is_none() {
FULLY_STRUCTURED_METHOD
} else {
STRUCTURED_RESOLUTION_METHOD
};
Ok(merged_text.into_merge_result(settings, method))
}