gitoxide_core/repository/merge/
tree.rs

1use 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}