Skip to content

Commit

Permalink
feat: first basic implementation of merge_base().
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Aug 25, 2024
1 parent 3e2ff9a commit bdfc717
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 3 deletions.
61 changes: 59 additions & 2 deletions gix-revision/src/merge_base.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
bitflags::bitflags! {
/// The flags used in the graph for finding [merge bases](crate::merge_base()).
#[derive(Debug, Default, Copy, Clone)]
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct Flags: u8 {
/// The commit belongs to the graph reachable by the first commit
const COMMIT1 = 1 << 0;
Expand All @@ -18,6 +18,8 @@ bitflags::bitflags! {
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
IterParents(#[from] gix_revwalk::graph::commit::iter_parents::Error),
#[error("A commit could not be found")]
FindExistingCommit(#[from] gix_object::find::existing_iter::Error),
#[error("A commit could not be decoded during traversal")]
Expand Down Expand Up @@ -51,6 +53,24 @@ pub(crate) mod function {
return Ok(Some(vec![first]));
}

let mut bases = paint_down_to_common(first, others, graph)?;
// TODO(ST): remove redundant
Ok(if bases.is_empty() {
None
} else {
let mut out = Vec::with_capacity(bases.len());
while let Some((_info, commit_id)) = bases.pop() {
out.push(commit_id);
}
Some(out)
})
}

fn paint_down_to_common<'name>(
first: ObjectId,
others: &[ObjectId],
graph: &mut Graph<'_, Flags>,
) -> Result<PriorityQueue<GenThenTime, ObjectId>, Error> {
let mut queue = PriorityQueue::<GenThenTime, ObjectId>::new();
graph.insert_data(first, |commit| -> Result<_, Error> {
queue.insert(commit.try_into()?, first);
Expand All @@ -63,10 +83,47 @@ pub(crate) mod function {
Ok(Flags::COMMIT2)
})?;
}
Ok(None)

let mut out = PriorityQueue::new();
while queue
.iter_unordered()
.any(|id| graph.get(id).map_or(false, |data| !data.contains(Flags::STALE)))
{
let (info, commit_id) = queue.pop().expect("we have non-stale");
let flags_mut = graph.get_mut(&commit_id).expect("everything queued is in graph");
let mut flags_without_result = *flags_mut & (Flags::COMMIT1 | Flags::COMMIT2 | Flags::STALE);
if flags_without_result == (Flags::COMMIT1 | Flags::COMMIT2) {
if !flags_mut.contains(Flags::RESULT) {
*flags_mut |= Flags::RESULT;
out.insert(info, commit_id);
}
flags_without_result |= Flags::STALE;
}

graph.insert_parents_with_lookup(&commit_id, &mut |parent_id, parent, ex_flags| -> Result<_, Error> {
let queue_info = match ex_flags {
Some(ex_flags) => {
if (*ex_flags & flags_without_result) != flags_without_result {
*ex_flags |= flags_without_result;
Some(GenThenTime::try_from(parent)?)
} else {
None
}
}
None => Some(GenThenTime::try_from(parent)?),
};
if let Some(info) = queue_info {
queue.insert(info, parent_id);
}
Ok(flags_without_result)
})?;
}

Ok(out)
}

// TODO(ST): Should this type be used for `describe` as well?
#[derive(Debug)]
struct GenThenTime {
/// Note that the special [`GENERATION_NUMBER_INFINITY`](gix_commitgraph::GENERATION_NUMBER_INFINITY) is used to indicate
/// that no commitgraph is avaialble.
Expand Down
3 changes: 3 additions & 0 deletions gix-revision/tests/fixtures/make_merge_base_repos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ mkcommit 100 DB
baseline DA DA DB
} > 1_disjoint.baseline

# A graph that is purposefully using times that can't be trusted, i.e. the root E
# has a higher time than its future commits, so that it would be preferred
# unless if there was an additional pruning step to deal with this case.
# E---D---C---B---A
# \"-_ \ \
# \ `---------G \
Expand Down
17 changes: 16 additions & 1 deletion gix-revwalk/src/graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,26 @@ impl<'find, T> Graph<'find, T> {
self.map.get_mut(id)
}

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

/// Insert `id` into the graph and associate it with the value returned by `make_data`,
/// and returning the previous value associated with `id` if it existed.
/// Fail if `id` doesn't exist in the object database.
pub fn insert_data<E>(
&mut self,
id: gix_hash::ObjectId,
mut make_data: impl FnMut(LazyCommit<'_>) -> Result<T, E>,
) -> Result<Option<T>, E>
where
E: From<gix_object::find::existing_iter::Error>,
{
let value = make_data(self.lookup(&id).map_err(E::from)?)?;
Ok(self.map.insert(id, value))
}

/// Remove all data from the graph to start over.
pub fn clear(&mut self) {
self.map.clear();
Expand Down

0 comments on commit bdfc717

Please sign in to comment.