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
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}