gix_merge/commit/
virtual_merge_base.rs1pub struct Outcome {
3 pub virtual_merge_bases: Vec<gix_hash::ObjectId>,
8 pub commit_id: gix_hash::ObjectId,
10 pub tree_id: gix_hash::ObjectId,
12}
13
14#[derive(Debug, thiserror::Error)]
16#[allow(missing_docs)]
17pub enum Error {
18 #[error(transparent)]
19 MergeTree(#[from] crate::tree::Error),
20 #[error("Failed to write tree for merged merge-base or virtual commit")]
21 WriteObject(gix_object::write::Error),
22 #[error("Failed to decode a commit needed to build a virtual merge-base")]
23 DecodeCommit(#[from] gix_object::decode::Error),
24 #[error(
25 "Conflicts occurred when trying to resolve multiple merge-bases by merging them. This is most certainly a bug."
26 )]
27 VirtualMergeBaseConflict,
28 #[error("Could not find commit to use as basis for a virtual commit")]
29 FindCommit(#[from] gix_object::find::existing_object::Error),
30}
31
32pub(super) mod function {
33 use gix_object::FindExt;
34
35 use super::Error;
36 use crate::{
37 blob::builtin_driver,
38 tree::{treat_as_unresolved, TreatAsUnresolved},
39 };
40
41 #[allow(clippy::too_many_arguments)]
49 pub fn virtual_merge_base<'objects>(
50 first_commit: gix_hash::ObjectId,
51 second_commit: gix_hash::ObjectId,
52 mut others: Vec<gix_hash::ObjectId>,
53 graph: &mut gix_revwalk::Graph<'_, '_, gix_revwalk::graph::Commit<gix_revision::merge_base::Flags>>,
54 diff_resource_cache: &mut gix_diff::blob::Platform,
55 blob_merge: &mut crate::blob::Platform,
56 objects: &'objects (impl gix_object::FindObjectOrHeader + gix_object::Write),
57 abbreviate_hash: &mut dyn FnMut(&gix_hash::oid) -> String,
58 mut options: crate::tree::Options,
59 ) -> Result<super::Outcome, crate::commit::Error> {
60 let mut merged_commit_id = first_commit;
61 others.push(second_commit);
62
63 options.tree_conflicts = Some(crate::tree::ResolveWith::Ancestor);
64 options.blob_merge.is_virtual_ancestor = true;
65 options.blob_merge.text.conflict = builtin_driver::text::Conflict::ResolveWithOurs;
66 let favor_ancestor = Some(builtin_driver::binary::ResolveWith::Ancestor);
67 options.blob_merge.resolve_binary_with = favor_ancestor;
68 options.symlink_conflicts = favor_ancestor;
69 let labels = builtin_driver::text::Labels {
70 current: Some("Temporary merge branch 1".into()),
71 other: Some("Temporary merge branch 2".into()),
72 ancestor: None,
73 };
74 let mut virtual_merge_bases = Vec::new();
75 let mut tree_id = None;
76 while let Some(next_commit_id) = others.pop() {
77 options.marker_size_multiplier += 1;
78 let mut out = crate::commit(
79 merged_commit_id,
80 next_commit_id,
81 labels,
82 graph,
83 diff_resource_cache,
84 blob_merge,
85 objects,
86 abbreviate_hash,
87 crate::commit::Options {
88 allow_missing_merge_base: false,
89 tree_merge: options.clone(),
90 use_first_merge_base: false,
91 },
92 )?;
93 if out.tree_merge.has_unresolved_conflicts(TreatAsUnresolved {
95 content_merge: treat_as_unresolved::ContentMerge::Markers,
96 tree_merge: treat_as_unresolved::TreeMerge::Undecidable,
97 }) {
98 return Err(Error::VirtualMergeBaseConflict.into());
99 }
100 let merged_tree_id = out
101 .tree_merge
102 .tree
103 .write(|tree| objects.write(tree))
104 .map_err(Error::WriteObject)?;
105
106 tree_id = Some(merged_tree_id);
107 merged_commit_id = create_virtual_commit(objects, merged_commit_id, next_commit_id, merged_tree_id)?;
108
109 virtual_merge_bases.extend(out.virtual_merge_bases);
110 virtual_merge_bases.push(merged_commit_id);
111 }
112
113 Ok(super::Outcome {
114 virtual_merge_bases,
115 commit_id: merged_commit_id,
116 tree_id: tree_id.map_or_else(
117 || {
118 let mut buf = Vec::new();
119 objects.find_commit(&merged_commit_id, &mut buf).map(|c| c.tree())
120 },
121 Ok,
122 )?,
123 })
124 }
125
126 fn create_virtual_commit(
127 objects: &(impl gix_object::Find + gix_object::Write),
128 parent_a: gix_hash::ObjectId,
129 parent_b: gix_hash::ObjectId,
130 tree_id: gix_hash::ObjectId,
131 ) -> Result<gix_hash::ObjectId, Error> {
132 let mut buf = Vec::new();
133 let commit_ref = objects.find_commit(&parent_a, &mut buf)?;
134 let mut commit = commit_ref.to_owned()?;
135 commit.parents = vec![parent_a, parent_b].into();
136 commit.tree = tree_id;
137 objects.write(&commit).map_err(Error::WriteObject)
138 }
139}