From bdfc7177512f9ec6c4108aedcb1acca09eed530f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 25 Aug 2024 16:29:12 +0200 Subject: [PATCH] feat: first basic implementation of `merge_base()`. --- gix-revision/src/merge_base.rs | 61 ++++++++++++++++++- .../tests/fixtures/make_merge_base_repos.sh | 3 + gix-revwalk/src/graph/mod.rs | 17 +++++- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/gix-revision/src/merge_base.rs b/gix-revision/src/merge_base.rs index 46b500efe87..dce2c7f2465 100644 --- a/gix-revision/src/merge_base.rs +++ b/gix-revision/src/merge_base.rs @@ -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; @@ -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")] @@ -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, Error> { let mut queue = PriorityQueue::::new(); graph.insert_data(first, |commit| -> Result<_, Error> { queue.insert(commit.try_into()?, first); @@ -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. diff --git a/gix-revision/tests/fixtures/make_merge_base_repos.sh b/gix-revision/tests/fixtures/make_merge_base_repos.sh index de6a644d4f3..f8ea82dedfa 100644 --- a/gix-revision/tests/fixtures/make_merge_base_repos.sh +++ b/gix-revision/tests/fixtures/make_merge_base_repos.sh @@ -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 \ diff --git a/gix-revwalk/src/graph/mod.rs b/gix-revwalk/src/graph/mod.rs index c7aad0a704d..7e32137281c 100644 --- a/gix-revwalk/src/graph/mod.rs +++ b/gix-revwalk/src/graph/mod.rs @@ -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 { 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( + &mut self, + id: gix_hash::ObjectId, + mut make_data: impl FnMut(LazyCommit<'_>) -> Result, + ) -> Result, E> + where + E: From, + { + 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();