From 7aee32abef13f5bf331249fed38e1e6b3e12876a Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Nov 2024 16:04:47 +0100 Subject: [PATCH] feat: add `Repository::virtual_merge_base()` and `Repository::virtual_merge_base_with_graph()`. --- gix/src/merge.rs | 18 ++++++++++ gix/src/repository/merge.rs | 70 ++++++++++++++++++++++++++++++++++++- gix/src/repository/mod.rs | 36 +++++++++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) diff --git a/gix/src/merge.rs b/gix/src/merge.rs index e87bf4394c3..978ce0a8003 100644 --- a/gix/src/merge.rs +++ b/gix/src/merge.rs @@ -2,6 +2,24 @@ pub use gix_merge as plumbing; pub use gix_merge::blob; +/// +pub mod virtual_merge_base { + use crate::Id; + + /// The outcome produced by [`Repository::virtual_merge_base()`](crate::Repository::virtual_merge_base()). + pub struct Outcome<'repo> { + /// The commit ids of all the virtual merge bases we have produced in the process of recursively merging the merge-bases. + /// As they have been written to the object database, they are still available until they are garbage collected. + /// The last one is the most recently produced and the one returned as `commit_id`. + /// If this list is empty, this means that there was only one merge-base, which itself is already suitable the final merge-base. + pub virtual_merge_bases: Vec>, + /// The id of the commit that was created to hold the merged tree. + pub commit_id: Id<'repo>, + /// The hash of the merged tree. + pub tree_id: Id<'repo>, + } +} + /// pub mod commit { /// The outcome produced by [`Repository::merge_commits()`](crate::Repository::merge_commits()). diff --git a/gix/src/repository/merge.rs b/gix/src/repository/merge.rs index 3e9a4df7be2..72f38795bd1 100644 --- a/gix/src/repository/merge.rs +++ b/gix/src/repository/merge.rs @@ -1,7 +1,10 @@ use crate::config::cache::util::ApplyLeniencyDefault; use crate::config::tree; use crate::prelude::ObjectIdExt; -use crate::repository::{blob_merge_options, merge_commits, merge_resource_cache, merge_trees, tree_merge_options}; +use crate::repository::{ + blob_merge_options, merge_commits, merge_resource_cache, merge_trees, tree_merge_options, virtual_merge_base, + virtual_merge_base_with_graph, +}; use crate::Repository; use gix_merge::blob::builtin_driver::text; use gix_object::Write; @@ -223,4 +226,69 @@ impl Repository { virtual_merge_bases, }) } + + /// Create a single virtual merge-base by merging all `merge_bases` into one. + /// If the list is empty, an error will be returned as the histories are then unrelated. + /// If there is only one commit in the list, it is returned directly with this case clearly marked in the outcome. + /// + /// Note that most of `options` are overwritten to match the requirements of a merge-base merge, but they can be useful + /// to control the diff algorithm or rewrite tracking, for example. + // TODO: test + pub fn virtual_merge_base( + &self, + merge_bases: impl IntoIterator>, + options: crate::merge::tree::Options, + ) -> Result, virtual_merge_base::Error> { + let commit_graph = self.commit_graph_if_enabled()?; + let mut graph = self.revision_graph(commit_graph.as_ref()); + Ok(self.virtual_merge_base_with_graph(merge_bases, &mut graph, options)?) + } + + /// Like [`Self::virtual_merge_base()`], but also allows to reuse a `graph` for faster merge-base calculation, + /// particularly if `graph` was used to find the `merge_bases`. + pub fn virtual_merge_base_with_graph( + &self, + merge_bases: impl IntoIterator>, + graph: &mut gix_revwalk::Graph<'_, '_, gix_revwalk::graph::Commit>, + options: crate::merge::tree::Options, + ) -> Result, virtual_merge_base_with_graph::Error> { + let mut merge_bases: Vec<_> = merge_bases.into_iter().map(Into::into).collect(); + let first = merge_bases + .pop() + .ok_or(virtual_merge_base_with_graph::Error::MissingCommit)?; + let Some(second) = merge_bases.pop() else { + let tree_id = self.find_commit(first)?.tree_id()?; + let commit_id = first.attach(self); + return Ok(crate::merge::virtual_merge_base::Outcome { + virtual_merge_bases: Vec::new(), + commit_id, + tree_id, + }); + }; + + let mut diff_cache = self.diff_resource_cache_for_tree_diff()?; + let mut blob_merge = self.merge_resource_cache(Default::default())?; + + let gix_merge::commit::virtual_merge_base::Outcome { + virtual_merge_bases, + commit_id, + tree_id, + } = gix_merge::commit::virtual_merge_base( + first, + second, + merge_bases, + graph, + &mut diff_cache, + &mut blob_merge, + self, + &mut |id| id.to_owned().attach(self).shorten_or_id().to_string(), + options.into(), + )?; + + Ok(crate::merge::virtual_merge_base::Outcome { + virtual_merge_bases: virtual_merge_bases.into_iter().map(|id| id.attach(self)).collect(), + commit_id: commit_id.attach(self), + tree_id: tree_id.attach(self), + }) + } } diff --git a/gix/src/repository/mod.rs b/gix/src/repository/mod.rs index 0f8d96bcc14..069ce119b07 100644 --- a/gix/src/repository/mod.rs +++ b/gix/src/repository/mod.rs @@ -150,6 +150,42 @@ pub mod merge_commits { } } +/// +#[cfg(feature = "merge")] +pub mod virtual_merge_base { + /// The error returned by [Repository::virtual_merge_base()](crate::Repository::virtual_merge_base()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error(transparent)] + OpenCommitGraph(#[from] super::commit_graph_if_enabled::Error), + #[error(transparent)] + VirtualMergeBase(#[from] super::virtual_merge_base_with_graph::Error), + } +} + +/// +#[cfg(feature = "merge")] +pub mod virtual_merge_base_with_graph { + /// The error returned by [Repository::virtual_merge_base_with_graph()](crate::Repository::virtual_merge_base_with_graph()). + #[derive(Debug, thiserror::Error)] + #[allow(missing_docs)] + pub enum Error { + #[error("Not commit was provided as merge-base")] + MissingCommit, + #[error(transparent)] + MergeResourceCache(#[from] super::merge_resource_cache::Error), + #[error(transparent)] + DiffResourceCache(#[from] super::diff_resource_cache::Error), + #[error(transparent)] + CommitMerge(#[from] gix_merge::commit::Error), + #[error(transparent)] + FindCommit(#[from] crate::object::find::existing::with_conversion::Error), + #[error(transparent)] + DecodeCommit(#[from] gix_object::decode::Error), + } +} + /// #[cfg(feature = "merge")] pub mod tree_merge_options {