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