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