gix_merge/commit/
virtual_merge_base.rs

1/// The outcome produced by [`commit::merge_base()`](crate::commit::virtual_merge_base()).
2pub struct Outcome {
3    /// The commit ids of all the virtual merge bases we have produced in the process of recursively merging the merge-bases.
4    /// As they have been written to the object database, they are still available until they are garbage collected.
5    /// The last one is the most recently produced and the one returned as `commit_id`.
6    /// This is never empty.
7    pub virtual_merge_bases: Vec<gix_hash::ObjectId>,
8    /// The id of the commit that was created to hold the merged tree.
9    pub commit_id: gix_hash::ObjectId,
10    /// The hash of the merged tree.
11    pub tree_id: gix_hash::ObjectId,
12}
13
14/// The error returned by [`commit::merge_base()`](crate::commit::virtual_merge_base()).
15#[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(
23        "Conflicts occurred when trying to resolve multiple merge-bases by merging them. This is most certainly a bug."
24    )]
25    VirtualMergeBaseConflict,
26    #[error("Could not find commit to use as basis for a virtual commit")]
27    FindCommit(#[from] gix_object::find::existing_object::Error),
28}
29
30pub(super) mod function {
31    use gix_object::FindExt;
32
33    use super::Error;
34    use crate::{
35        blob::builtin_driver,
36        tree::{treat_as_unresolved, TreatAsUnresolved},
37    };
38
39    /// Create a single virtual merge-base by merging `first_commit`, `second_commit` and `others` into one.
40    /// Note that `first_commit` and `second_commit` are expected to have been popped off `others`, so `first_commit`
41    /// was the last provided merge-base of function that provides multiple merge-bases for a pair of commits.
42    ///
43    /// The parameters `graph`, `diff_resource_cache`, `blob_merge`, `objects`, `abbreviate_hash` and `options` are passed
44    /// directly to [`tree()`](crate::tree()) for merging the trees of two merge-bases at a time.
45    /// Note that most of `options` are overwritten to match the requirements of a merge-base merge.
46    #[allow(clippy::too_many_arguments)]
47    pub fn virtual_merge_base<'objects>(
48        first_commit: gix_hash::ObjectId,
49        second_commit: gix_hash::ObjectId,
50        mut others: Vec<gix_hash::ObjectId>,
51        graph: &mut gix_revwalk::Graph<'_, '_, gix_revwalk::graph::Commit<gix_revision::merge_base::Flags>>,
52        diff_resource_cache: &mut gix_diff::blob::Platform,
53        blob_merge: &mut crate::blob::Platform,
54        objects: &'objects (impl gix_object::FindObjectOrHeader + gix_object::Write),
55        abbreviate_hash: &mut dyn FnMut(&gix_hash::oid) -> String,
56        mut options: crate::tree::Options,
57    ) -> Result<super::Outcome, crate::commit::Error> {
58        let mut merged_commit_id = first_commit;
59        others.push(second_commit);
60
61        options.tree_conflicts = Some(crate::tree::ResolveWith::Ancestor);
62        options.blob_merge.is_virtual_ancestor = true;
63        options.blob_merge.text.conflict = builtin_driver::text::Conflict::ResolveWithOurs;
64        let favor_ancestor = Some(builtin_driver::binary::ResolveWith::Ancestor);
65        options.blob_merge.resolve_binary_with = favor_ancestor;
66        options.symlink_conflicts = favor_ancestor;
67        let labels = builtin_driver::text::Labels {
68            current: Some("Temporary merge branch 1".into()),
69            other: Some("Temporary merge branch 2".into()),
70            ancestor: None,
71        };
72        let mut virtual_merge_bases = Vec::new();
73        let mut tree_id = None;
74        while let Some(next_commit_id) = others.pop() {
75            options.marker_size_multiplier += 1;
76            let mut out = crate::commit(
77                merged_commit_id,
78                next_commit_id,
79                labels,
80                graph,
81                diff_resource_cache,
82                blob_merge,
83                objects,
84                abbreviate_hash,
85                crate::commit::Options {
86                    allow_missing_merge_base: false,
87                    tree_merge: options.clone(),
88                    use_first_merge_base: false,
89                },
90            )?;
91            // This shouldn't happen, but if for some buggy reason it does, we rather bail.
92            if out.tree_merge.has_unresolved_conflicts(TreatAsUnresolved {
93                content_merge: treat_as_unresolved::ContentMerge::Markers,
94                tree_merge: treat_as_unresolved::TreeMerge::Undecidable,
95            }) {
96                return Err(Error::VirtualMergeBaseConflict.into());
97            }
98            let merged_tree_id = out
99                .tree_merge
100                .tree
101                .write(|tree| objects.write(tree))
102                .map_err(Error::WriteObject)?;
103
104            tree_id = Some(merged_tree_id);
105            merged_commit_id = create_virtual_commit(objects, merged_commit_id, next_commit_id, merged_tree_id)?;
106
107            virtual_merge_bases.extend(out.virtual_merge_bases);
108            virtual_merge_bases.push(merged_commit_id);
109        }
110
111        Ok(super::Outcome {
112            virtual_merge_bases,
113            commit_id: merged_commit_id,
114            tree_id: tree_id.map_or_else(
115                || {
116                    let mut buf = Vec::new();
117                    objects.find_commit(&merged_commit_id, &mut buf).map(|c| c.tree())
118                },
119                Ok,
120            )?,
121        })
122    }
123
124    fn create_virtual_commit(
125        objects: &(impl gix_object::Find + gix_object::Write),
126        parent_a: gix_hash::ObjectId,
127        parent_b: gix_hash::ObjectId,
128        tree_id: gix_hash::ObjectId,
129    ) -> Result<gix_hash::ObjectId, Error> {
130        let mut buf = Vec::new();
131        let mut commit: gix_object::Commit = objects.find_commit(&parent_a, &mut buf)?.into();
132        commit.parents = vec![parent_a, parent_b].into();
133        commit.tree = tree_id;
134        objects.write(&commit).map_err(Error::WriteObject)
135    }
136}