summaryrefslogtreecommitdiffstats
path: root/vendor/gix-negotiate/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-negotiate/src/lib.rs')
-rw-r--r--vendor/gix-negotiate/src/lib.rs144
1 files changed, 144 insertions, 0 deletions
diff --git a/vendor/gix-negotiate/src/lib.rs b/vendor/gix-negotiate/src/lib.rs
new file mode 100644
index 000000000..b05f944f8
--- /dev/null
+++ b/vendor/gix-negotiate/src/lib.rs
@@ -0,0 +1,144 @@
+//! 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, 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_revision::Graph<'find, gix_revision::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: impl Into<Option<usize>>) -> usize {
+ let current_size = match window_size.into() {
+ 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_revision::graph::lookup::commit::Error;