|
| 1 | +/// The error returned by [`commit()`](crate::commit()). |
| 2 | +#[derive(Debug, thiserror::Error)] |
| 3 | +#[allow(missing_docs)] |
| 4 | +pub enum Error { |
| 5 | + #[error(transparent)] |
| 6 | + MergeBase(#[from] gix_revision::merge_base::Error), |
| 7 | + #[error(transparent)] |
| 8 | + MergeTree(#[from] crate::tree::Error), |
| 9 | + #[error("No common ancestor between {our_commit_id} and {their_commit_id}")] |
| 10 | + NoMergeBase { |
| 11 | + /// The commit on our side that was to be merged. |
| 12 | + our_commit_id: gix_hash::ObjectId, |
| 13 | + /// The commit on their side that was to be merged. |
| 14 | + their_commit_id: gix_hash::ObjectId, |
| 15 | + }, |
| 16 | + #[error("Could not find ancestor, our or their commit to extract tree from")] |
| 17 | + FindCommit(#[from] gix_object::find::existing_object::Error), |
| 18 | +} |
| 19 | + |
| 20 | +/// A way to configure [`commit()`](crate::commit()). |
| 21 | +#[derive(Default, Debug, Copy, Clone)] |
| 22 | +pub struct Options { |
| 23 | + /// If `true`, merging unrelated commits is allowed, with the merge-base being assumed as empty tree. |
| 24 | + pub allow_missing_merge_base: bool, |
| 25 | + /// Options to define how trees should be merged. |
| 26 | + pub tree_merge: crate::tree::Options, |
| 27 | + /// Options to define how to merge blobs. |
| 28 | + /// |
| 29 | + /// Note that these are temporarily overwritten if multiple merge-bases are merged into one. |
| 30 | + pub blob_merge: crate::blob::platform::merge::Options, |
| 31 | +} |
| 32 | + |
| 33 | +pub(super) mod function { |
| 34 | + use crate::commit::{Error, Options}; |
| 35 | + use gix_object::FindExt; |
| 36 | + |
| 37 | + /// Like [`tree()`](crate::tree()), but it takes only two commits, `our_commit` and `their_commit` to automatically |
| 38 | + /// compute the merge-bases among them. |
| 39 | + /// If there are multiple merge bases, these will be auto-merged into one, recursively, if |
| 40 | + /// [`allow_missing_merge_base`](Options::allow_missing_merge_base) is `true`. |
| 41 | + /// |
| 42 | + /// `labels` are names where [`current`](crate::blob::builtin_driver::text::Labels::current) is a name for `our_commit` |
| 43 | + /// and [`other`](crate::blob::builtin_driver::text::Labels::other) is a name for `their_commit`. |
| 44 | + /// If [`ancestor`](crate::blob::builtin_driver::text::Labels::ancestor) is unset, it will be set by us based on the |
| 45 | + /// merge-bases of `our_commit` and `their_commit`. |
| 46 | + /// |
| 47 | + /// The `graph` is used to find the merge-base between `our_commit` and `their_commit`, and can also act as cache |
| 48 | + /// to speed up subsequent merge-base queries. |
| 49 | + /// |
| 50 | + /// ### Performance |
| 51 | + /// |
| 52 | + /// Note that `objects` *should* have an object cache to greatly accelerate tree-retrieval. |
| 53 | + #[allow(clippy::too_many_arguments)] |
| 54 | + pub fn commit<'objects>( |
| 55 | + our_commit: gix_hash::ObjectId, |
| 56 | + their_commit: gix_hash::ObjectId, |
| 57 | + mut labels: crate::blob::builtin_driver::text::Labels<'_>, |
| 58 | + graph: &mut gix_revwalk::Graph<'_, '_, gix_revwalk::graph::Commit<gix_revision::merge_base::Flags>>, |
| 59 | + diff_resource_cache: &mut gix_diff::blob::Platform, |
| 60 | + blob_merge: &mut crate::blob::Platform, |
| 61 | + objects: &'objects impl gix_object::FindObjectOrHeader, |
| 62 | + options: Options, |
| 63 | + ) -> Result<crate::tree::Outcome<'objects>, Error> { |
| 64 | + let merge_bases_commit_ids = gix_revision::merge_base(our_commit, &[their_commit], graph)?; |
| 65 | + let (merge_base_commit_id, ancestor_name) = match merge_bases_commit_ids { |
| 66 | + Some(base_commit) if base_commit.len() == 1 => (base_commit[0], None), |
| 67 | + Some(base_commits) => { |
| 68 | + let virtual_base_tree = *base_commits.first().expect("TODO: merge multiple bases into one"); |
| 69 | + (virtual_base_tree, Some("merged common ancestors".into())) |
| 70 | + } |
| 71 | + None => { |
| 72 | + if options.allow_missing_merge_base { |
| 73 | + ( |
| 74 | + gix_hash::ObjectId::empty_tree(our_commit.kind()), |
| 75 | + Some("empty tree".into()), |
| 76 | + ) |
| 77 | + } else { |
| 78 | + return Err(Error::NoMergeBase { |
| 79 | + our_commit_id: our_commit, |
| 80 | + their_commit_id: their_commit, |
| 81 | + }); |
| 82 | + } |
| 83 | + } |
| 84 | + }; |
| 85 | + if labels.ancestor.is_none() { |
| 86 | + labels.ancestor = ancestor_name; |
| 87 | + } |
| 88 | + |
| 89 | + let mut state = gix_diff::tree::State::default(); |
| 90 | + let merge_base_tree_id = objects.find_commit(&merge_base_commit_id, &mut state.buf1)?.tree(); |
| 91 | + let our_tree_id = objects.find_commit(&our_commit, &mut state.buf1)?.tree(); |
| 92 | + let their_tree_id = objects.find_commit(&their_commit, &mut state.buf1)?.tree(); |
| 93 | + |
| 94 | + Ok(crate::tree( |
| 95 | + &merge_base_tree_id, |
| 96 | + &our_tree_id, |
| 97 | + &their_tree_id, |
| 98 | + labels, |
| 99 | + objects, |
| 100 | + &mut state, |
| 101 | + diff_resource_cache, |
| 102 | + blob_merge, |
| 103 | + options.tree_merge, |
| 104 | + )?) |
| 105 | + } |
| 106 | +} |
0 commit comments