Skip to content

Commit bdfc717

Browse files
committed
feat: first basic implementation of merge_base().
1 parent 3e2ff9a commit bdfc717

File tree

3 files changed

+78
-3
lines changed

3 files changed

+78
-3
lines changed

gix-revision/src/merge_base.rs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
bitflags::bitflags! {
22
/// The flags used in the graph for finding [merge bases](crate::merge_base()).
3-
#[derive(Debug, Default, Copy, Clone)]
3+
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
44
pub struct Flags: u8 {
55
/// The commit belongs to the graph reachable by the first commit
66
const COMMIT1 = 1 << 0;
@@ -18,6 +18,8 @@ bitflags::bitflags! {
1818
#[derive(Debug, thiserror::Error)]
1919
#[allow(missing_docs)]
2020
pub enum Error {
21+
#[error(transparent)]
22+
IterParents(#[from] gix_revwalk::graph::commit::iter_parents::Error),
2123
#[error("A commit could not be found")]
2224
FindExistingCommit(#[from] gix_object::find::existing_iter::Error),
2325
#[error("A commit could not be decoded during traversal")]
@@ -51,6 +53,24 @@ pub(crate) mod function {
5153
return Ok(Some(vec![first]));
5254
}
5355

56+
let mut bases = paint_down_to_common(first, others, graph)?;
57+
// TODO(ST): remove redundant
58+
Ok(if bases.is_empty() {
59+
None
60+
} else {
61+
let mut out = Vec::with_capacity(bases.len());
62+
while let Some((_info, commit_id)) = bases.pop() {
63+
out.push(commit_id);
64+
}
65+
Some(out)
66+
})
67+
}
68+
69+
fn paint_down_to_common<'name>(
70+
first: ObjectId,
71+
others: &[ObjectId],
72+
graph: &mut Graph<'_, Flags>,
73+
) -> Result<PriorityQueue<GenThenTime, ObjectId>, Error> {
5474
let mut queue = PriorityQueue::<GenThenTime, ObjectId>::new();
5575
graph.insert_data(first, |commit| -> Result<_, Error> {
5676
queue.insert(commit.try_into()?, first);
@@ -63,10 +83,47 @@ pub(crate) mod function {
6383
Ok(Flags::COMMIT2)
6484
})?;
6585
}
66-
Ok(None)
86+
87+
let mut out = PriorityQueue::new();
88+
while queue
89+
.iter_unordered()
90+
.any(|id| graph.get(id).map_or(false, |data| !data.contains(Flags::STALE)))
91+
{
92+
let (info, commit_id) = queue.pop().expect("we have non-stale");
93+
let flags_mut = graph.get_mut(&commit_id).expect("everything queued is in graph");
94+
let mut flags_without_result = *flags_mut & (Flags::COMMIT1 | Flags::COMMIT2 | Flags::STALE);
95+
if flags_without_result == (Flags::COMMIT1 | Flags::COMMIT2) {
96+
if !flags_mut.contains(Flags::RESULT) {
97+
*flags_mut |= Flags::RESULT;
98+
out.insert(info, commit_id);
99+
}
100+
flags_without_result |= Flags::STALE;
101+
}
102+
103+
graph.insert_parents_with_lookup(&commit_id, &mut |parent_id, parent, ex_flags| -> Result<_, Error> {
104+
let queue_info = match ex_flags {
105+
Some(ex_flags) => {
106+
if (*ex_flags & flags_without_result) != flags_without_result {
107+
*ex_flags |= flags_without_result;
108+
Some(GenThenTime::try_from(parent)?)
109+
} else {
110+
None
111+
}
112+
}
113+
None => Some(GenThenTime::try_from(parent)?),
114+
};
115+
if let Some(info) = queue_info {
116+
queue.insert(info, parent_id);
117+
}
118+
Ok(flags_without_result)
119+
})?;
120+
}
121+
122+
Ok(out)
67123
}
68124

69125
// TODO(ST): Should this type be used for `describe` as well?
126+
#[derive(Debug)]
70127
struct GenThenTime {
71128
/// Note that the special [`GENERATION_NUMBER_INFINITY`](gix_commitgraph::GENERATION_NUMBER_INFINITY) is used to indicate
72129
/// that no commitgraph is avaialble.

gix-revision/tests/fixtures/make_merge_base_repos.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ mkcommit 100 DB
4545
baseline DA DA DB
4646
} > 1_disjoint.baseline
4747

48+
# A graph that is purposefully using times that can't be trusted, i.e. the root E
49+
# has a higher time than its future commits, so that it would be preferred
50+
# unless if there was an additional pruning step to deal with this case.
4851
# E---D---C---B---A
4952
# \"-_ \ \
5053
# \ `---------G \

gix-revwalk/src/graph/mod.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,26 @@ impl<'find, T> Graph<'find, T> {
9090
self.map.get_mut(id)
9191
}
9292

93-
/// Insert `id` into the graph and associate it with `value`, returning the previous value associated with it if it existed.
93+
/// Insert `id` into the graph and associate it with `value`, returning the previous value associated with `id` if it existed.
9494
pub fn insert(&mut self, id: gix_hash::ObjectId, value: T) -> Option<T> {
9595
self.map.insert(id, value)
9696
}
9797

98+
/// Insert `id` into the graph and associate it with the value returned by `make_data`,
99+
/// and returning the previous value associated with `id` if it existed.
100+
/// Fail if `id` doesn't exist in the object database.
101+
pub fn insert_data<E>(
102+
&mut self,
103+
id: gix_hash::ObjectId,
104+
mut make_data: impl FnMut(LazyCommit<'_>) -> Result<T, E>,
105+
) -> Result<Option<T>, E>
106+
where
107+
E: From<gix_object::find::existing_iter::Error>,
108+
{
109+
let value = make_data(self.lookup(&id).map_err(E::from)?)?;
110+
Ok(self.map.insert(id, value))
111+
}
112+
98113
/// Remove all data from the graph to start over.
99114
pub fn clear(&mut self) {
100115
self.map.clear();

0 commit comments

Comments
 (0)