Skip to main content

gitoxide_core/repository/merge/
commit.rs

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