| use gix_date::SecondsSinceUnixEpoch; |
| use gix_hash::ObjectId; |
| |
| use crate::{Error, Flags, Negotiator}; |
| |
| pub(crate) struct Algorithm { |
| revs: gix_revwalk::PriorityQueue<SecondsSinceUnixEpoch, ObjectId>, |
| 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<Result<ObjectId, Error>> { |
| 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<bool, Error> { |
| 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, |
| } |