| //! An implementation of negotiation algorithms to help the server figure out what we have in common so it can optimize |
| //! the pack it sends to only contain what we don't have. |
| #![deny(rust_2018_idioms, missing_docs)] |
| #![forbid(unsafe_code)] |
| mod consecutive; |
| mod noop; |
| mod skipping; |
| |
| bitflags::bitflags! { |
| /// Multi purpose, shared flags that are used by negotiation algorithms and by the caller as well |
| /// |
| /// However, in this crate we can't implement the calling side, so we marry this type to whatever is needed in downstream crates. |
| #[derive(Debug, Default, Copy, Clone)] |
| pub struct Flags: u8 { |
| /// The object is already available locally and doesn't need to be fetched by the remote. |
| const COMPLETE = 1 << 0; |
| /// A commit from an alternate object database. |
| const ALTERNATE = 1 << 1; |
| |
| /// The revision is known to be in common with the remote. |
| /// |
| /// Used by `consecutive` and `skipping`. |
| const COMMON = 1 << 2; |
| /// The revision has entered the priority queue. |
| /// |
| /// Used by `consecutive` and `skipping`. |
| const SEEN = 1 << 3; |
| /// The revision was popped off our primary priority queue, used to avoid double-counting of `non_common_revs` |
| /// |
| /// Used by `consecutive` and `skipping`. |
| const POPPED = 1 << 4; |
| |
| /// The revision is common and was set by merit of a remote tracking ref (e.g. `refs/heads/origin/main`). |
| /// |
| /// Used by `consecutive`. |
| const COMMON_REF = 1 << 5; |
| |
| /// The remote let us know it has the object. We still have to tell the server we have this object or one of its descendants. |
| /// We won't tell the server about its ancestors. |
| /// |
| /// Used by `skipping`. |
| const ADVERTISED = 1 << 6; |
| } |
| } |
| |
| /// Additional data to store with each commit when used by any of our algorithms. |
| /// |
| /// It's shared among those who use the [`Negotiator`] trait, and all implementations of it. |
| #[derive(Default, Debug, Copy, Clone)] |
| pub struct Metadata { |
| /// Used by `skipping`. |
| /// Only used if commit is not COMMON |
| pub original_ttl: u16, |
| /// Used by `skipping`. |
| pub ttl: u16, |
| /// Additional information about each commit |
| pub flags: Flags, |
| } |
| |
| /// The graph our callers use to store traversal information, for (re-)use in the negotiation implementation. |
| pub type Graph<'find> = gix_revwalk::Graph<'find, gix_revwalk::graph::Commit<Metadata>>; |
| |
| /// A map associating an object id with its commit-metadata. |
| pub type IdMap = gix_revwalk::graph::IdMap<gix_revwalk::graph::Commit<Metadata>>; |
| |
| /// The way the negotiation is performed. |
| #[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] |
| pub enum Algorithm { |
| /// Do not send any information at all, which typically leads to complete packs to be sent. |
| Noop, |
| /// Walk over consecutive commits and check each one. This can be costly be assures packs are exactly the size they need to be. |
| #[default] |
| Consecutive, |
| /// Like `Consecutive`, but skips commits to converge faster, at the cost of receiving packs that are larger than they have to be. |
| Skipping, |
| } |
| |
| impl std::fmt::Display for Algorithm { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| match self { |
| Algorithm::Noop => "noop", |
| Algorithm::Consecutive => "consecutive", |
| Algorithm::Skipping => "skipping", |
| } |
| .fmt(f) |
| } |
| } |
| |
| /// Calculate how many `HAVE` lines we may send in one round, with variation depending on whether the `transport_is_stateless` or not. |
| /// `window_size` is the previous (or initial) value of the window size. |
| pub fn window_size(transport_is_stateless: bool, window_size: Option<usize>) -> usize { |
| let current_size = match window_size { |
| None => return 16, |
| Some(cs) => cs, |
| }; |
| const PIPESAFE_FLUSH: usize = 32; |
| const LARGE_FLUSH: usize = 16384; |
| |
| if transport_is_stateless { |
| if current_size < LARGE_FLUSH { |
| current_size * 2 |
| } else { |
| current_size * 11 / 10 |
| } |
| } else if current_size < PIPESAFE_FLUSH { |
| current_size * 2 |
| } else { |
| current_size + PIPESAFE_FLUSH |
| } |
| } |
| |
| impl Algorithm { |
| /// Create an instance of a negotiator which implements this algorithm. |
| pub fn into_negotiator(self) -> Box<dyn Negotiator> { |
| match &self { |
| Algorithm::Noop => Box::new(noop::Noop) as Box<dyn Negotiator>, |
| Algorithm::Consecutive => Box::<consecutive::Algorithm>::default(), |
| Algorithm::Skipping => Box::<skipping::Algorithm>::default(), |
| } |
| } |
| } |
| |
| /// A delegate to implement a negotiation algorithm. |
| pub trait Negotiator { |
| /// Mark `id` as common between the remote and us. |
| /// |
| /// These ids are typically the local tips of remote tracking branches. |
| fn known_common(&mut self, id: gix_hash::ObjectId, graph: &mut Graph<'_>) -> Result<(), Error>; |
| |
| /// Add `id` as starting point of a traversal across commits that aren't necessarily common between the remote and us. |
| /// |
| /// These tips are usually the commits of local references whose tips should lead to objects that we have in common with the remote. |
| fn add_tip(&mut self, id: gix_hash::ObjectId, graph: &mut Graph<'_>) -> Result<(), Error>; |
| |
| /// Produce the next id of an object that we want the server to know we have. It's an object we don't know we have in common or not. |
| /// |
| /// Returns `None` if we have exhausted all options, which might mean we have traversed the entire commit graph. |
| fn next_have(&mut self, graph: &mut Graph<'_>) -> Option<Result<gix_hash::ObjectId, Error>>; |
| |
| /// Mark `id` as being common with the remote (as informed by the remote itself) and return `true` if we knew it was common already. |
| /// |
| /// We can assume to have already seen `id` as we were the one to inform the remote in a prior `have`. |
| fn in_common_with_remote(&mut self, id: gix_hash::ObjectId, graph: &mut Graph<'_>) -> Result<bool, Error>; |
| } |
| |
| /// An error that happened during any of the methods on a [`Negotiator`]. |
| pub type Error = gix_revwalk::graph::try_lookup_or_insert_default::Error; |