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