| use gix_date::SecondsSinceUnixEpoch; |
| use gix_hash::ObjectId; |
| |
| use crate::{Error, Flags, Metadata, 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 commit = graph.try_lookup_or_insert_commit(id, |entry| { |
| entry.flags |= mark | Flags::SEEN; |
| })?; |
| if let Some(timestamp) = commit.map(|c| c.commit_time) { |
| self.revs.insert(timestamp, id); |
| if !mark.contains(Flags::COMMON) { |
| self.non_common_revs += 1; |
| } |
| } |
| Ok(()) |
| } |
| |
| fn mark_common(&mut self, id: ObjectId, graph: &mut crate::Graph<'_>) -> Result<(), Error> { |
| let mut is_common = false; |
| if let Some(commit) = graph |
| .try_lookup_or_insert_commit(id, |entry| { |
| is_common = entry.flags.contains(Flags::COMMON); |
| entry.flags |= Flags::COMMON; |
| })? |
| .filter(|_| !is_common) |
| { |
| let mut queue = gix_revwalk::PriorityQueue::from_iter(Some((commit.commit_time, id))); |
| while let Some(id) = queue.pop_value() { |
| if let Some(commit) = graph.try_lookup_or_insert_commit(id, |entry| { |
| if !entry.flags.contains(Flags::POPPED) { |
| self.non_common_revs -= 1; |
| } |
| })? { |
| for parent_id in commit.parents.clone() { |
| // This is a bit of a problem as there is no representation of the `parsed` based skip. However, |
| // We assume that parents that aren't in the graph yet haven't been seen, and that's all we need. |
| if !graph.contains(&parent_id) { |
| continue; |
| } |
| let mut was_unseen_or_common = false; |
| if let Some(parent) = graph |
| .try_lookup_or_insert_commit(parent_id, |entry| { |
| was_unseen_or_common = |
| !entry.flags.contains(Flags::SEEN) || entry.flags.contains(Flags::COMMON); |
| entry.flags |= Flags::COMMON |
| })? |
| .filter(|_| !was_unseen_or_common) |
| { |
| queue.insert(parent.commit_time, parent_id); |
| } |
| } |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| fn push_parent( |
| &mut self, |
| entry: Metadata, |
| parent_id: ObjectId, |
| graph: &mut crate::Graph<'_>, |
| ) -> Result<bool, Error> { |
| let mut was_seen = false; |
| if let Some(parent) = graph |
| .get(&parent_id) |
| .map(|parent| { |
| was_seen = parent.data.flags.contains(Flags::SEEN); |
| parent |
| }) |
| .filter(|_| was_seen) |
| { |
| if parent.data.flags.contains(Flags::POPPED) { |
| return Ok(false); |
| } |
| } else { |
| self.add_to_queue(parent_id, Flags::default(), graph)?; |
| } |
| if entry.flags.intersects(Flags::COMMON | Flags::ADVERTISED) { |
| self.mark_common(parent_id, graph)?; |
| } else { |
| let new_original_ttl = if entry.ttl > 0 { |
| entry.original_ttl |
| } else { |
| entry.original_ttl * 3 / 2 + 1 |
| }; |
| let new_ttl = if entry.ttl > 0 { entry.ttl - 1 } else { new_original_ttl }; |
| let parent = graph.get_mut(&parent_id).expect("present or inserted"); |
| if parent.data.original_ttl < new_original_ttl { |
| parent.data.original_ttl = new_original_ttl; |
| parent.data.ttl = new_ttl; |
| } |
| } |
| Ok(true) |
| } |
| } |
| |
| impl Negotiator for Algorithm { |
| fn known_common(&mut self, id: ObjectId, graph: &mut crate::Graph<'_>) -> Result<(), Error> { |
| if graph |
| .get(&id) |
| .map_or(false, |commit| commit.data.flags.contains(Flags::SEEN)) |
| { |
| return Ok(()); |
| } |
| self.add_to_queue(id, Flags::ADVERTISED, graph) |
| } |
| |
| fn add_tip(&mut self, id: ObjectId, graph: &mut crate::Graph<'_>) -> Result<(), Error> { |
| if graph |
| .get(&id) |
| .map_or(false, |commit| commit.data.flags.contains(Flags::SEEN)) |
| { |
| return Ok(()); |
| } |
| self.add_to_queue(id, Flags::default(), 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"); |
| commit.data.flags |= Flags::POPPED; |
| |
| if !commit.data.flags.contains(Flags::COMMON) { |
| self.non_common_revs -= 1; |
| } |
| let mut to_send = None; |
| if !commit.data.flags.contains(Flags::COMMON) && commit.data.ttl == 0 { |
| to_send = Some(id); |
| } |
| |
| let data = commit.data; |
| let mut parent_pushed = false; |
| for parent_id in commit.parents.clone() { |
| parent_pushed |= match self.push_parent(data, parent_id, graph) { |
| Ok(r) => r, |
| Err(err) => return Some(Err(err)), |
| } |
| } |
| |
| if !data.flags.contains(Flags::COMMON) && !parent_pushed { |
| to_send = Some(id); |
| } |
| |
| if let Some(to_send) = to_send { |
| return Some(Ok(to_send)); |
| } |
| } |
| } |
| |
| fn in_common_with_remote(&mut self, id: ObjectId, graph: &mut crate::Graph<'_>) -> Result<bool, Error> { |
| let mut was_seen = false; |
| let known_to_be_common = graph.get(&id).map_or(false, |commit| { |
| was_seen = commit.data.flags.contains(Flags::SEEN); |
| commit.data.flags.contains(Flags::COMMON) |
| }); |
| assert!( |
| was_seen, |
| "Cannot receive ACK for commit we didn't send a HAVE for: {id}" |
| ); |
| self.mark_common(id, graph)?; |
| Ok(known_to_be_common) |
| } |
| } |