Skip to content

Commit 3abf043

Browse files
committed
feat: add Repository::merge_base()
A simple method to obtain the merge-base between two commits.
1 parent 76f113a commit 3abf043

File tree

3 files changed

+109
-1
lines changed

3 files changed

+109
-1
lines changed

gix/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ attributes = [
118118
mailmap = ["dep:gix-mailmap", "revision"]
119119

120120
## Make revspec parsing possible, as well describing revision.
121-
revision = ["gix-revision/describe", "index"]
121+
revision = ["gix-revision/describe", "gix-revision/merge_base", "index"]
122122

123123
## If enabled, revspecs now support the regex syntax like `@^{/^.*x}`. Otherwise, only substring search is supported.
124124
## This feature does increase compile time for niche-benefit, but is required for fully git-compatible revspec parsing.

gix/src/repository/mod.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,42 @@ mod submodule;
7272
mod thread_safe;
7373
mod worktree;
7474

75+
///
76+
#[cfg(feature = "revision")]
77+
pub mod merge_base {
78+
/// The error returned by [Repository::merge_base()](crate::Repository::merge_base()).
79+
#[derive(Debug, thiserror::Error)]
80+
#[allow(missing_docs)]
81+
pub enum Error {
82+
#[error(transparent)]
83+
OpenCache(#[from] crate::repository::commit_graph_if_enabled::Error),
84+
#[error(transparent)]
85+
FindMergeBase(#[from] gix_revision::merge_base::Error),
86+
#[error("Could not find a merge-base between commits {first} and {second}")]
87+
NotFound {
88+
first: gix_hash::ObjectId,
89+
second: gix_hash::ObjectId,
90+
},
91+
}
92+
}
93+
94+
///
95+
#[cfg(feature = "revision")]
96+
pub mod merge_base_with_cache {
97+
/// The error returned by [Repository::merge_base_with_cache()](crate::Repository::merge_base_with_cache()).
98+
#[derive(Debug, thiserror::Error)]
99+
#[allow(missing_docs)]
100+
pub enum Error {
101+
#[error(transparent)]
102+
FindMergeBase(#[from] gix_revision::merge_base::Error),
103+
#[error("Could not find a merge-base between commits {first} and {second}")]
104+
NotFound {
105+
first: gix_hash::ObjectId,
106+
second: gix_hash::ObjectId,
107+
},
108+
}
109+
}
110+
75111
///
76112
pub mod commit_graph_if_enabled {
77113
/// The error returned by [Repository::commit_graph_if_enabled()](crate::Repository::commit_graph_if_enabled()).

gix/src/repository/revision.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,78 @@ impl crate::Repository {
3636
.ok_or(revision::spec::parse::single::Error::RangedRev { spec: spec.into() })
3737
}
3838

39+
/// Obtain the best merge-base between commit `one` and `two`, or fail if there is none.
40+
///
41+
/// # Performance
42+
/// For repeated calls, prefer [`merge_base_with_cache()`](crate::Repository::merge_base_with_cache()).
43+
/// Also be sure to [set an object cache](crate::Repository::object_cache_size_if_unset) to accelerate repeated commit lookups.
44+
#[cfg(feature = "revision")]
45+
pub fn merge_base(
46+
&self,
47+
one: impl Into<gix_hash::ObjectId>,
48+
two: impl Into<gix_hash::ObjectId>,
49+
) -> Result<Id<'_>, super::merge_base::Error> {
50+
use crate::prelude::ObjectIdExt;
51+
let one = one.into();
52+
let two = two.into();
53+
let cache = self.commit_graph_if_enabled()?;
54+
let mut graph = self.revision_graph(cache.as_ref());
55+
let bases =
56+
gix_revision::merge_base(one, &vec![two], &mut graph)?.ok_or(super::merge_base::Error::NotFound {
57+
first: one,
58+
second: two,
59+
})?;
60+
Ok(bases[0].attach(self))
61+
}
62+
63+
/// Obtain the best merge-base between commit `one` and `two`, or fail if there is none, providing a
64+
/// commit-graph `cache` to potentially greatly accelerate the operation.
65+
///
66+
/// # Performance
67+
/// Be sure to [set an object cache](crate::Repository::object_cache_size_if_unset) to accelerate repeated commit lookups.
68+
#[cfg(feature = "revision")]
69+
pub fn merge_base_with_cache(
70+
&self,
71+
one: impl Into<gix_hash::ObjectId>,
72+
two: impl Into<gix_hash::ObjectId>,
73+
cache: Option<&gix_commitgraph::Graph>,
74+
) -> Result<Id<'_>, super::merge_base_with_cache::Error> {
75+
use crate::prelude::ObjectIdExt;
76+
let one = one.into();
77+
let two = two.into();
78+
let mut graph = self.revision_graph(cache);
79+
let bases = gix_revision::merge_base(one, &vec![two], &mut graph)?.ok_or(
80+
super::merge_base_with_cache::Error::NotFound {
81+
first: one,
82+
second: two,
83+
},
84+
)?;
85+
Ok(bases[0].attach(self))
86+
}
87+
88+
/// Obtain all merge-bases between commit `one` and `others`, or an empty list if there is none, providing a
89+
/// commit-graph `cache` to potentially greatly accelerate the operation.
90+
///
91+
/// # Performance
92+
/// Be sure to [set an object cache](crate::Repository::object_cache_size_if_unset) to accelerate repeated commit lookups.
93+
#[doc(alias = "merge_bases_many", alias = "git2")]
94+
#[cfg(feature = "revision")]
95+
pub fn merge_bases_many_with_cache(
96+
&self,
97+
one: impl Into<gix_hash::ObjectId>,
98+
others: &[gix_hash::ObjectId],
99+
cache: Option<&gix_commitgraph::Graph>,
100+
) -> Result<Vec<Id<'_>>, gix_revision::merge_base::Error> {
101+
use crate::prelude::ObjectIdExt;
102+
let one = one.into();
103+
let mut graph = self.revision_graph(cache);
104+
Ok(gix_revision::merge_base(one, others, &mut graph)?
105+
.unwrap_or_default()
106+
.into_iter()
107+
.map(|id| id.attach(self))
108+
.collect())
109+
}
110+
39111
/// Create the baseline for a revision walk by initializing it with the `tips` to start iterating on.
40112
///
41113
/// It can be configured further before starting the actual walk.

0 commit comments

Comments
 (0)