use gix_date::SecondsSinceUnixEpoch; use gix_hash::ObjectId; use crate::{Error, Flags, Negotiator}; pub(crate) struct Algorithm { revs: gix_revwalk::PriorityQueue, non_common_revs: usize, } impl Default for Algorithm { fn default() -> Self { Self { revs: gix_revwalk::PriorityQueue::new(), non_common_revs: 0, } } } impl Algorithm { /// Add `id` to our priority queue and *add* `flags` to it. fn add_to_queue(&mut self, id: ObjectId, mark: Flags, graph: &mut crate::Graph<'_>) -> Result<(), Error> { let mut is_common = false; let mut has_mark = false; if let Some(commit) = graph .try_lookup_or_insert_commit(id, |data| { has_mark = data.flags.intersects(mark); data.flags |= mark; is_common = data.flags.contains(Flags::COMMON); })? .filter(|_| !has_mark) { self.revs.insert(commit.commit_time, id); if !is_common { self.non_common_revs += 1; } } Ok(()) } fn mark_common( &mut self, id: ObjectId, mode: Mark, ancestors: Ancestors, graph: &mut crate::Graph<'_>, ) -> Result<(), Error> { let mut is_common = false; if let Some(commit) = graph .try_lookup_or_insert_commit(id, |data| is_common = data.flags.contains(Flags::COMMON))? .filter(|_| !is_common) { let mut queue = gix_revwalk::PriorityQueue::from_iter(Some((commit.commit_time, (id, 0_usize)))); if let Mark::ThisCommitAndAncestors = mode { commit.data.flags |= Flags::COMMON; if commit.data.flags.contains(Flags::SEEN) && !commit.data.flags.contains(Flags::POPPED) { self.non_common_revs -= 1; } } while let Some((id, generation)) = queue.pop_value() { if graph .get(&id) .map_or(true, |commit| !commit.data.flags.contains(Flags::SEEN)) { self.add_to_queue(id, Flags::SEEN, graph)?; } else if matches!(ancestors, Ancestors::AllUnseen) || generation < 2 { if let Some(commit) = graph.try_lookup_or_insert_commit(id, |_| {})? { for parent_id in commit.parents.clone() { let mut prev_flags = Flags::default(); if let Some(parent) = graph .try_lookup_or_insert_commit(parent_id, |data| { prev_flags = data.flags; data.flags |= Flags::COMMON; })? .filter(|_| !prev_flags.contains(Flags::COMMON)) { if prev_flags.contains(Flags::SEEN) && !prev_flags.contains(Flags::POPPED) { self.non_common_revs -= 1; } queue.insert(parent.commit_time, (parent_id, generation + 1)) } } } } } } Ok(()) } } impl Negotiator for Algorithm { fn known_common(&mut self, id: ObjectId, graph: &mut crate::Graph<'_>) -> Result<(), Error> { if graph .get(&id) .map_or(true, |commit| !commit.data.flags.contains(Flags::SEEN)) { self.add_to_queue(id, Flags::COMMON_REF | Flags::SEEN, graph)?; self.mark_common(id, Mark::AncestorsOnly, Ancestors::DirectUnseen, graph)?; } Ok(()) } fn add_tip(&mut self, id: ObjectId, graph: &mut crate::Graph<'_>) -> Result<(), Error> { self.add_to_queue(id, Flags::SEEN, graph) } fn next_have(&mut self, graph: &mut crate::Graph<'_>) -> Option> { loop { let id = self.revs.pop_value().filter(|_| self.non_common_revs != 0)?; let commit = graph.get_mut(&id).expect("it was added to the graph by now"); let flags = &mut commit.data.flags; *flags |= Flags::POPPED; if !flags.contains(Flags::COMMON) { self.non_common_revs -= 1; } let (res, mark) = if flags.contains(Flags::COMMON) { (None, Flags::COMMON | Flags::SEEN) } else if flags.contains(Flags::COMMON_REF) { (Some(id), Flags::COMMON | Flags::SEEN) } else { (Some(id), Flags::SEEN) }; for parent_id in commit.parents.clone() { if graph .get(&parent_id) .map_or(true, |commit| !commit.data.flags.contains(Flags::SEEN)) { if let Err(err) = self.add_to_queue(parent_id, mark, graph) { return Some(Err(err)); } } if mark.contains(Flags::COMMON) { if let Err(err) = self.mark_common(parent_id, Mark::AncestorsOnly, Ancestors::AllUnseen, graph) { return Some(Err(err)); } } } if let Some(id) = res { return Some(Ok(id)); } } } fn in_common_with_remote(&mut self, id: ObjectId, graph: &mut crate::Graph<'_>) -> Result { let known_to_be_common = graph .get(&id) .map_or(false, |commit| commit.data.flags.contains(Flags::COMMON)); self.mark_common(id, Mark::ThisCommitAndAncestors, Ancestors::DirectUnseen, graph)?; Ok(known_to_be_common) } } enum Mark { AncestorsOnly, ThisCommitAndAncestors, } enum Ancestors { /// Traverse only the parents of a commit. DirectUnseen, /// Traverse all ancestors that weren't yet seen. AllUnseen, }