gix_merge/commit/
function.rs

1use std::borrow::Cow;
2
3use gix_object::FindExt;
4
5use crate::{
6    blob::builtin_driver,
7    commit::{Error, Options},
8};
9
10/// Like [`tree()`](crate::tree()), but it takes only two commits, `our_commit` and `their_commit` to automatically
11/// compute the merge-bases among them.
12/// If there are multiple merge bases, these will be auto-merged into one, recursively, if
13/// [`allow_missing_merge_base`](Options::allow_missing_merge_base) is `true`.
14///
15/// `labels` are names where [`current`](crate::blob::builtin_driver::text::Labels::current) is a name for `our_commit`
16/// and [`other`](crate::blob::builtin_driver::text::Labels::other) is a name for `their_commit`.
17/// If [`ancestor`](crate::blob::builtin_driver::text::Labels::ancestor) is unset, it will be set by us based on the
18/// merge-bases of `our_commit` and `their_commit`.
19///
20/// The `graph` is used to find the merge-base between `our_commit` and `their_commit`, and can also act as cache
21/// to speed up subsequent merge-base queries.
22///
23/// Use `abbreviate_hash(id)` to shorten the given `id` according to standard git shortening rules. It's used in case
24/// the ancestor-label isn't explicitly set so that the merge base label becomes the shortened `id`.
25/// Note that it's a dyn closure only to make it possible to recursively call this function in case of multiple merge-bases.
26///
27/// `write_object` is used only if it's allowed to merge multiple merge-bases into one, and if there
28/// are multiple merge bases, and to write merged buffers as blobs.
29///
30/// ### Performance
31///
32/// Note that `objects` *should* have an object cache to greatly accelerate tree-retrieval.
33///
34/// ### Notes
35///
36/// When merging merge-bases recursively, the options are adjusted automatically to act like Git, i.e. merge binary
37/// blobs and resolve with *ours*, while resorting to using the base/ancestor in case of unresolvable conflicts.
38///
39/// ### Deviation
40///
41/// * It's known that certain conflicts around symbolic links can be auto-resolved. We don't have an option for this
42///   at all, yet, primarily as Git seems to not implement the *ours*/*theirs* choice in other places even though it
43///   reasonably could. So we leave it to the caller to continue processing the returned tree at will.
44#[allow(clippy::too_many_arguments)]
45pub fn commit<'objects>(
46    our_commit: gix_hash::ObjectId,
47    their_commit: gix_hash::ObjectId,
48    labels: builtin_driver::text::Labels<'_>,
49    graph: &mut gix_revwalk::Graph<'_, '_, gix_revwalk::graph::Commit<gix_revision::merge_base::Flags>>,
50    diff_resource_cache: &mut gix_diff::blob::Platform,
51    blob_merge: &mut crate::blob::Platform,
52    objects: &'objects (impl gix_object::FindObjectOrHeader + gix_object::Write),
53    abbreviate_hash: &mut dyn FnMut(&gix_hash::oid) -> String,
54    options: Options,
55) -> Result<super::Outcome<'objects>, Error> {
56    let merge_bases = gix_revision::merge_base(our_commit, &[their_commit], graph)?;
57    let mut virtual_merge_bases = Vec::new();
58    let mut state = gix_diff::tree::State::default();
59    let mut commit_to_tree =
60        |commit_id: gix_hash::ObjectId| objects.find_commit(&commit_id, &mut state.buf1).map(|c| c.tree());
61
62    let (merge_base_tree_id, ancestor_name): (_, Cow<'_, str>) = match merge_bases.clone() {
63        Some(base_commit) if base_commit.len() == 1 => {
64            (commit_to_tree(base_commit[0])?, abbreviate_hash(&base_commit[0]).into())
65        }
66        Some(mut base_commits) => {
67            let virtual_base_tree = if options.use_first_merge_base {
68                let first = base_commits.first().expect("if Some() there is at least one.");
69                commit_to_tree(*first)?
70            } else {
71                let first = base_commits.pop().expect("at least two");
72                let second = base_commits.pop().expect("at least one left");
73                let out = crate::commit::virtual_merge_base(
74                    first,
75                    second,
76                    base_commits,
77                    graph,
78                    diff_resource_cache,
79                    blob_merge,
80                    objects,
81                    abbreviate_hash,
82                    options.tree_merge.clone(),
83                )?;
84                virtual_merge_bases = out.virtual_merge_bases;
85                out.tree_id
86            };
87            (virtual_base_tree, "merged common ancestors".into())
88        }
89        None => {
90            if options.allow_missing_merge_base {
91                (gix_hash::ObjectId::empty_tree(our_commit.kind()), "empty tree".into())
92            } else {
93                return Err(Error::NoMergeBase {
94                    our_commit_id: our_commit,
95                    their_commit_id: their_commit,
96                });
97            }
98        }
99    };
100
101    let mut labels = labels; // TODO(borrowchk): this re-assignment shouldn't be needed.
102    if labels.ancestor.is_none() {
103        labels.ancestor = Some(ancestor_name.as_ref().into());
104    }
105
106    let our_tree_id = objects.find_commit(&our_commit, &mut state.buf1)?.tree();
107    let their_tree_id = objects.find_commit(&their_commit, &mut state.buf1)?.tree();
108
109    let outcome = crate::tree(
110        &merge_base_tree_id,
111        &our_tree_id,
112        &their_tree_id,
113        labels,
114        objects,
115        |buf| objects.write_buf(gix_object::Kind::Blob, buf),
116        &mut state,
117        diff_resource_cache,
118        blob_merge,
119        options.tree_merge,
120    )?;
121
122    Ok(super::Outcome {
123        tree_merge: outcome,
124        merge_bases,
125        merge_base_tree_id,
126        virtual_merge_bases,
127    })
128}