gitoxide_core/repository/merge/
commit.rs

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