gitoxide_core/repository/merge/
commit.rs1use 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}