gix_protocol/fetch/delegate.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
use std::{
borrow::Cow,
io,
ops::{Deref, DerefMut},
};
use bstr::BString;
use gix_transport::client::Capabilities;
use crate::{
fetch::{Arguments, Response},
handshake::Ref,
};
/// Defines what to do next after certain [`Delegate`] operations.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
pub enum Action {
/// Continue the typical flow of operations in this flow.
Continue,
/// Return at the next possible opportunity without making further requests, possibly after closing the connection.
Cancel,
}
/// The non-IO protocol delegate is the bare minimal interface needed to fully control the [`fetch`][crate::fetch()] operation, sparing
/// the IO parts.
/// Async implementations must treat it as blocking and unblock it by evaluating it elsewhere.
///
/// See [Delegate] for the complete trait.
pub trait DelegateBlocking {
/// Return extra parameters to be provided during the handshake.
///
/// Note that this method is only called once and the result is reused during subsequent handshakes which may happen
/// if there is an authentication failure.
fn handshake_extra_parameters(&self) -> Vec<(String, Option<String>)> {
Vec::new()
}
/// Called before invoking 'ls-refs' on the server to allow providing it with additional `arguments` and to enable `features`.
/// If the server `capabilities` don't match the requirements abort with an error to abort the entire fetch operation.
///
/// Note that some arguments are preset based on typical use, and `features` are preset to maximize options.
/// The `server` capabilities can be used to see which additional capabilities the server supports as per the handshake which happened prior.
///
/// If the delegate returns [`ls_refs::Action::Skip`], no `ls-refs` command is sent to the server.
///
/// Note that this is called only if we are using protocol version 2.
fn prepare_ls_refs(
&mut self,
_server: &Capabilities,
_arguments: &mut Vec<BString>,
_features: &mut Vec<(&str, Option<Cow<'_, str>>)>,
) -> std::io::Result<ls_refs::Action> {
Ok(ls_refs::Action::Continue)
}
/// Called before invoking the 'fetch' interaction with `features` pre-filled for typical use
/// and to maximize capabilities to allow aborting an interaction early.
///
/// `refs` is a list of known references on the remote based on the handshake or a prior call to `ls_refs`.
/// These can be used to abort early in case the refs are already known here.
///
/// As there will be another call allowing to post arguments conveniently in the correct format, i.e. `want hex-oid`,
/// there is no way to set arguments at this time.
///
/// `version` is the actually supported version as reported by the server, which is relevant in case the server requested a downgrade.
/// `server` capabilities is a list of features the server supports for your information, along with enabled `features` that the server knows about.
fn prepare_fetch(
&mut self,
_version: gix_transport::Protocol,
_server: &Capabilities,
_features: &mut Vec<(&str, Option<Cow<'_, str>>)>,
_refs: &[Ref],
) -> std::io::Result<Action> {
Ok(Action::Continue)
}
/// A method called repeatedly to negotiate the objects to receive in [`receive_pack(…)`][Delegate::receive_pack()].
///
/// The first call has `previous_response` set to `None` as there was no previous response. Every call that follows `previous_response`
/// will be set to `Some`.
///
/// ### If `previous_response` is `None`…
///
/// Given a list of `arguments` to populate with wants, want-refs, shallows, filters and other contextual information to be
/// sent to the server. This method is called once.
/// Send the objects you `have` have afterwards based on the tips of your refs, in preparation to walk down their parents
/// with each call to `negotiate` to find the common base(s).
///
/// Note that you should not `want` and object that you already have.
/// `refs` are the tips of on the server side, effectively the latest objects _they_ have.
///
/// Return `Action::Close` if you know that there are no `haves` on your end to allow the server to send all of its objects
/// as is the case during initial clones.
///
/// ### If `previous_response` is `Some`…
///
/// Populate `arguments` with the objects you `have` starting from the tips of _your_ refs, taking into consideration
/// the `previous_response` response of the server to see which objects they acknowledged to have. You have to maintain
/// enough state to be able to walk down from your tips on each call, if they are not in common, and keep setting `have`
/// for those which are in common if that helps teaching the server about our state and to acknowledge their existence on _their_ end.
/// This method is called until the other side signals they are ready to send a pack.
/// Return `Action::Close` if you want to give up before finding a common base. This can happen if the remote repository
/// has radically changed so there are no bases, or they are very far in the past, causing all objects to be sent.
fn negotiate(
&mut self,
refs: &[Ref],
arguments: &mut Arguments,
previous_response: Option<&Response>,
) -> io::Result<Action>;
}
impl<T: DelegateBlocking> DelegateBlocking for Box<T> {
fn handshake_extra_parameters(&self) -> Vec<(String, Option<String>)> {
self.deref().handshake_extra_parameters()
}
fn prepare_ls_refs(
&mut self,
_server: &Capabilities,
_arguments: &mut Vec<BString>,
_features: &mut Vec<(&str, Option<Cow<'_, str>>)>,
) -> io::Result<ls_refs::Action> {
self.deref_mut().prepare_ls_refs(_server, _arguments, _features)
}
fn prepare_fetch(
&mut self,
_version: gix_transport::Protocol,
_server: &Capabilities,
_features: &mut Vec<(&str, Option<Cow<'_, str>>)>,
_refs: &[Ref],
) -> io::Result<Action> {
self.deref_mut().prepare_fetch(_version, _server, _features, _refs)
}
fn negotiate(
&mut self,
refs: &[Ref],
arguments: &mut Arguments,
previous_response: Option<&Response>,
) -> io::Result<Action> {
self.deref_mut().negotiate(refs, arguments, previous_response)
}
}
impl<T: DelegateBlocking> DelegateBlocking for &mut T {
fn handshake_extra_parameters(&self) -> Vec<(String, Option<String>)> {
self.deref().handshake_extra_parameters()
}
fn prepare_ls_refs(
&mut self,
_server: &Capabilities,
_arguments: &mut Vec<BString>,
_features: &mut Vec<(&str, Option<Cow<'_, str>>)>,
) -> io::Result<ls_refs::Action> {
self.deref_mut().prepare_ls_refs(_server, _arguments, _features)
}
fn prepare_fetch(
&mut self,
_version: gix_transport::Protocol,
_server: &Capabilities,
_features: &mut Vec<(&str, Option<Cow<'_, str>>)>,
_refs: &[Ref],
) -> io::Result<Action> {
self.deref_mut().prepare_fetch(_version, _server, _features, _refs)
}
fn negotiate(
&mut self,
refs: &[Ref],
arguments: &mut Arguments,
previous_response: Option<&Response>,
) -> io::Result<Action> {
self.deref_mut().negotiate(refs, arguments, previous_response)
}
}
#[cfg(feature = "blocking-client")]
mod blocking_io {
use std::{
io::{self, BufRead},
ops::DerefMut,
};
use gix_features::progress::NestedProgress;
use crate::{
fetch::{DelegateBlocking, Response},
handshake::Ref,
};
/// The protocol delegate is the bare minimal interface needed to fully control the [`fetch`][crate::fetch()] operation.
///
/// Implementations of this trait are controlled by code with intricate knowledge about how fetching works in protocol version V1 and V2,
/// so you don't have to.
/// Everything is tucked away behind type-safety so 'nothing can go wrong'©. Runtime assertions assure invalid
/// features or arguments don't make it to the server in the first place.
/// Please note that this trait mostly corresponds to what V2 would look like, even though V1 is supported as well.
pub trait Delegate: DelegateBlocking {
/// Receive a pack provided from the given `input`.
///
/// Use `progress` to emit your own progress messages when decoding the pack.
///
/// `refs` of the remote side are provided for convenience, along with the parsed `previous_response` response in case you want
/// to check additional acks.
fn receive_pack(
&mut self,
input: impl io::BufRead,
progress: impl NestedProgress + 'static,
refs: &[Ref],
previous_response: &Response,
) -> io::Result<()>;
}
impl<T: Delegate> Delegate for Box<T> {
fn receive_pack(
&mut self,
input: impl BufRead,
progress: impl NestedProgress + 'static,
refs: &[Ref],
previous_response: &Response,
) -> io::Result<()> {
self.deref_mut().receive_pack(input, progress, refs, previous_response)
}
}
impl<T: Delegate> Delegate for &mut T {
fn receive_pack(
&mut self,
input: impl BufRead,
progress: impl NestedProgress + 'static,
refs: &[Ref],
previous_response: &Response,
) -> io::Result<()> {
self.deref_mut().receive_pack(input, progress, refs, previous_response)
}
}
}
#[cfg(feature = "blocking-client")]
pub use blocking_io::Delegate;
#[cfg(feature = "async-client")]
mod async_io {
use std::{io, ops::DerefMut};
use async_trait::async_trait;
use futures_io::AsyncBufRead;
use gix_features::progress::NestedProgress;
use crate::{
fetch::{DelegateBlocking, Response},
handshake::Ref,
};
/// The protocol delegate is the bare minimal interface needed to fully control the [`fetch`][crate::fetch()] operation.
///
/// Implementations of this trait are controlled by code with intricate knowledge about how fetching works in protocol version V1 and V2,
/// so you don't have to.
/// Everything is tucked away behind type-safety so 'nothing can go wrong'©. Runtime assertions assure invalid
/// features or arguments don't make it to the server in the first place.
/// Please note that this trait mostly corresponds to what V2 would look like, even though V1 is supported as well.
#[async_trait(?Send)]
pub trait Delegate: DelegateBlocking {
/// Receive a pack provided from the given `input`, and the caller should consider it to be blocking as
/// most operations on the received pack are implemented in a blocking fashion.
///
/// Use `progress` to emit your own progress messages when decoding the pack.
///
/// `refs` of the remote side are provided for convenience, along with the parsed `previous_response` response in case you want
/// to check additional acks.
async fn receive_pack(
&mut self,
input: impl AsyncBufRead + Unpin + 'async_trait,
progress: impl NestedProgress + 'static,
refs: &[Ref],
previous_response: &Response,
) -> io::Result<()>;
}
#[async_trait(?Send)]
impl<T: Delegate> Delegate for Box<T> {
async fn receive_pack(
&mut self,
input: impl AsyncBufRead + Unpin + 'async_trait,
progress: impl NestedProgress + 'static,
refs: &[Ref],
previous_response: &Response,
) -> io::Result<()> {
self.deref_mut()
.receive_pack(input, progress, refs, previous_response)
.await
}
}
#[async_trait(?Send)]
impl<T: Delegate> Delegate for &mut T {
async fn receive_pack(
&mut self,
input: impl AsyncBufRead + Unpin + 'async_trait,
progress: impl NestedProgress + 'static,
refs: &[Ref],
previous_response: &Response,
) -> io::Result<()> {
self.deref_mut()
.receive_pack(input, progress, refs, previous_response)
.await
}
}
}
#[cfg(feature = "async-client")]
pub use async_io::Delegate;
use crate::ls_refs;