blob: 1969e05de4d7e0a4b6719e40057301d4e27945cc [file] [log] [blame] [edit]
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)
}
}