gitoxide_core/repository/merge/
tree.rs1use crate::OutputFormat;
2
3pub struct Options {
4 pub format: OutputFormat,
5 pub file_favor: Option<gix::merge::tree::FileFavor>,
6 pub tree_favor: Option<gix::merge::tree::TreeFavor>,
7 pub in_memory: bool,
8 pub debug: bool,
9}
10
11pub(super) mod function {
12
13 use crate::OutputFormat;
14 use anyhow::{anyhow, bail, Context};
15 use gix::bstr::BString;
16 use gix::bstr::ByteSlice;
17 use gix::merge::tree::TreatAsUnresolved;
18 use gix::prelude::Write;
19
20 use super::Options;
21
22 #[allow(clippy::too_many_arguments)]
23 pub fn tree(
24 mut repo: gix::Repository,
25 out: &mut dyn std::io::Write,
26 err: &mut dyn std::io::Write,
27 base: BString,
28 ours: BString,
29 theirs: BString,
30 Options {
31 format,
32 file_favor,
33 tree_favor,
34 in_memory,
35 debug,
36 }: Options,
37 ) -> anyhow::Result<()> {
38 if format != OutputFormat::Human {
39 bail!("JSON output isn't implemented yet");
40 }
41 repo.object_cache_size_if_unset(repo.compute_object_cache_size_for_tree_diffs(&**repo.index_or_empty()?));
42 if in_memory {
43 repo.objects.enable_object_memory();
44 }
45 let (base_ref, base_id) = refname_and_tree(&repo, base)?;
46 let (ours_ref, ours_id) = refname_and_tree(&repo, ours)?;
47 let (theirs_ref, theirs_id) = refname_and_tree(&repo, theirs)?;
48
49 let options = repo
50 .tree_merge_options()?
51 .with_file_favor(file_favor)
52 .with_tree_favor(tree_favor);
53 let base_id_str = base_id.to_string();
54 let ours_id_str = ours_id.to_string();
55 let theirs_id_str = theirs_id.to_string();
56 let labels = gix::merge::blob::builtin_driver::text::Labels {
57 ancestor: base_ref
58 .as_ref()
59 .map_or(base_id_str.as_str().into(), |n| n.as_bstr())
60 .into(),
61 current: ours_ref
62 .as_ref()
63 .map_or(ours_id_str.as_str().into(), |n| n.as_bstr())
64 .into(),
65 other: theirs_ref
66 .as_ref()
67 .map_or(theirs_id_str.as_str().into(), |n| n.as_bstr())
68 .into(),
69 };
70 let res = repo.merge_trees(base_id, ours_id, theirs_id, labels, options)?;
71 let has_conflicts = res.conflicts.is_empty();
72 let has_unresolved_conflicts = res.has_unresolved_conflicts(TreatAsUnresolved::default());
73 {
74 let _span = gix::trace::detail!("Writing merged tree");
75 let mut written = 0;
76 let tree_id = res
77 .tree
78 .detach()
79 .write(|tree| {
80 written += 1;
81 repo.write(tree)
82 })
83 .map_err(|err| anyhow!("{err}"))?;
84 writeln!(out, "{tree_id} (wrote {written} trees)")?;
85 }
86
87 if debug {
88 writeln!(err, "{:#?}", &res.conflicts)?;
89 }
90 if !has_conflicts {
91 writeln!(err, "{} possibly resolved conflicts", res.conflicts.len())?;
92 }
93 if has_unresolved_conflicts {
94 bail!("Tree conflicted")
95 }
96 Ok(())
97 }
98
99 fn refname_and_tree(
100 repo: &gix::Repository,
101 revspec: BString,
102 ) -> anyhow::Result<(Option<BString>, gix::hash::ObjectId)> {
103 let spec = repo.rev_parse(revspec.as_bstr())?;
104 let tree_id = spec
105 .single()
106 .context("Expected revspec to expand to a single rev only")?
107 .object()?
108 .peel_to_tree()?
109 .id;
110 let refname = spec.first_reference().map(|r| r.name.shorten().as_bstr().to_owned());
111 Ok((refname, tree_id))
112 }
113}