gitoxide_core/repository/merge/
commit.rs

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