gitoxide_core/repository/merge/
file.rs1use crate::OutputFormat;
2use anyhow::{anyhow, bail, Context};
3use gix::bstr::BString;
4use gix::bstr::ByteSlice;
5use gix::merge::blob::builtin_driver::binary;
6use gix::merge::blob::builtin_driver::text::Conflict;
7use gix::merge::blob::pipeline::WorktreeRoots;
8use gix::merge::blob::{Resolution, ResourceKind};
9use gix::object::tree::EntryKind;
10use gix::Id;
11use std::path::Path;
12
13pub fn file(
14 repo: gix::Repository,
15 out: &mut dyn std::io::Write,
16 format: OutputFormat,
17 conflict: Option<gix::merge::blob::builtin_driver::text::Conflict>,
18 base: BString,
19 ours: BString,
20 theirs: BString,
21) -> anyhow::Result<()> {
22 if format != OutputFormat::Human {
23 bail!("JSON output isn't implemented yet");
24 }
25 let index = &repo.index_or_load_from_head()?;
26 let specs = repo.pathspec(
27 false,
28 [base, ours, theirs],
29 true,
30 index,
31 gix::worktree::stack::state::attributes::Source::WorktreeThenIdMapping.adjust_for_bare(repo.is_bare()),
32 )?;
33 let mut patterns = specs.search().patterns().map(|p| p.path().to_owned());
36 let base = patterns.next().unwrap();
37 let ours = patterns.next().unwrap();
38 let theirs = patterns.next().unwrap();
39
40 let base_id = repo.rev_parse_single(base.as_bstr()).ok();
41 let ours_id = repo.rev_parse_single(ours.as_bstr()).ok();
42 let theirs_id = repo.rev_parse_single(theirs.as_bstr()).ok();
43 let roots = worktree_roots(base_id, ours_id, theirs_id, repo.workdir())?;
44
45 let mut cache = repo.merge_resource_cache(roots)?;
46 let null = repo.object_hash().null();
47 cache.set_resource(
48 base_id.map_or(null, Id::detach),
49 EntryKind::Blob,
50 base.as_bstr(),
51 ResourceKind::CommonAncestorOrBase,
52 &repo.objects,
53 )?;
54 cache.set_resource(
55 ours_id.map_or(null, Id::detach),
56 EntryKind::Blob,
57 ours.as_bstr(),
58 ResourceKind::CurrentOrOurs,
59 &repo.objects,
60 )?;
61 cache.set_resource(
62 theirs_id.map_or(null, Id::detach),
63 EntryKind::Blob,
64 theirs.as_bstr(),
65 ResourceKind::OtherOrTheirs,
66 &repo.objects,
67 )?;
68
69 let mut options = repo.blob_merge_options()?;
70 if let Some(conflict) = conflict {
71 options.text.conflict = conflict;
72 options.resolve_binary_with = match conflict {
73 Conflict::Keep { .. } => None,
74 Conflict::ResolveWithOurs => Some(binary::ResolveWith::Ours),
75 Conflict::ResolveWithTheirs => Some(binary::ResolveWith::Theirs),
76 Conflict::ResolveWithUnion => None,
77 };
78 }
79 let platform = cache.prepare_merge(&repo.objects, options)?;
80 let labels = gix::merge::blob::builtin_driver::text::Labels {
81 ancestor: Some(base.as_bstr()),
82 current: Some(ours.as_bstr()),
83 other: Some(theirs.as_bstr()),
84 };
85 let mut buf = repo.empty_reusable_buffer();
86 let (pick, resolution) = platform.merge(&mut buf, labels, &repo.command_context()?)?;
87 let buf = platform
88 .buffer_by_pick(pick)
89 .map_err(|_| anyhow!("Participating object was too large"))?
90 .unwrap_or(&buf);
91 out.write_all(buf)?;
92
93 if resolution == Resolution::Conflict {
94 bail!("File conflicted")
95 }
96 Ok(())
97}
98
99fn worktree_roots(
100 base: Option<gix::Id<'_>>,
101 ours: Option<gix::Id<'_>>,
102 theirs: Option<gix::Id<'_>>,
103 workdir: Option<&Path>,
104) -> anyhow::Result<gix::merge::blob::pipeline::WorktreeRoots> {
105 let roots = if base.is_none() || ours.is_none() || theirs.is_none() {
106 let workdir = workdir.context("A workdir is required if one of the bases are provided as path.")?;
107 gix::merge::blob::pipeline::WorktreeRoots {
108 current_root: ours.is_none().then(|| workdir.to_owned()),
109 other_root: theirs.is_none().then(|| workdir.to_owned()),
110 common_ancestor_root: base.is_none().then(|| workdir.to_owned()),
111 }
112 } else {
113 WorktreeRoots::default()
114 };
115 Ok(roots)
116}