509 lines · 22.4 KB · Rust
RawDownload
/// What to do after preparing ls-refs.#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]pub enum RefsAction {    /// Continue by sending a 'ls-refs' command.    Continue,    /// Skip 'ls-refs' entirely.    ///    /// This is useful if the `ref-in-want` capability is taken advantage of. When fetching, one    /// must then send `want-ref`s during the negotiation phase.    Skip,} mod fetch_fn {    use crate::bisync::bisync;    use gix_features::progress::NestedProgress;    use gix_protocol::{        Command, LsRefsCommand, credentials,        fetch::{Arguments, Response},        indicate_end_of_interaction,    };    #[cfg(all(feature = "async-client", not(feature = "blocking-client")))]    use gix_transport::client::async_io::{ExtendedBufRead, HandleProgress, Transport};    #[cfg(feature = "blocking-client")]    use gix_transport::client::blocking_io::{ExtendedBufRead, HandleProgress, Transport};    use std::ops::ControlFlow;     use super::{Action, Delegate, RefsAction};    use crate::fetch::Error;     /// A way to indicate how to treat the connection underlying the transport, potentially allowing to reuse it.    #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]    pub enum FetchConnection {        /// Use this variant if server should be informed that the operation is completed and no further commands will be issued        /// at the end of the fetch operation or after deciding that no fetch operation should happen after references were listed.        ///        /// When indicating the end-of-fetch, this flag is only relevant in protocol V2.        /// Generally it only applies when using persistent transports.        ///        /// In most explicit client side failure modes the end-of-operation' notification will be sent to the server automatically.        #[default]        TerminateOnSuccessfulCompletion,         /// Indicate that persistent transport connections can be reused by _not_ sending an 'end-of-operation' notification to the server.        /// This is useful if multiple `fetch(…)` calls are used in succession.        ///        /// Note that this has no effect in case of non-persistent connections, like the ones over HTTP.        ///        /// As an optimization, callers can use `AllowReuse` here as the server will also know the client is done        /// if the connection is closed.        AllowReuse,    }     /// Perform a 'fetch' operation with the server using `transport`, with `delegate` handling all server interactions.    /// **Note** that `delegate` has blocking operations and thus this entire call should be on an executor which can handle    /// that. This could be the current thread blocking, or another thread.    ///    /// * `authenticate(operation_to_perform)` is used to receive credentials for the connection and potentially store it    ///   if the server indicates 'permission denied'. Note that not all transport support authentication or authorization.    /// * `progress` is used to emit progress messages.    /// * `name` is the name of the git client to present as `agent`, like `"my-app (v2.0)"`".    /// * If `trace` is `true`, all packetlines received or sent will be passed to the facilities of the `gix-trace` crate.    ///    /// _Note_ that depending on the `delegate`, the actual action performed can be `ls-refs`, `clone` or `fetch`.    ///    /// # WARNING - Do not use!    ///    /// As it will hang when having multiple negotiation rounds.    #[bisync]    // TODO: remove this without losing test coverage - we have the same but better in `gix` and it's    //       not really worth it to maintain the delegates here.    pub async fn legacy_fetch<F, D, T, P>(        mut transport: T,        mut delegate: D,        authenticate: F,        mut progress: P,        fetch_mode: FetchConnection,        agent: impl Into<String>,        trace: bool,    ) -> Result<(), Error>    where        F: FnMut(credentials::helper::Action) -> credentials::protocol::Result,        D: Delegate,        T: Transport,        P: NestedProgress + 'static,        P::SubProgress: 'static,    {        let gix_protocol::Handshake {            server_protocol_version: protocol_version,            refs,            v1_shallow_updates: _ignored_shallow_updates_as_it_is_deprecated,            capabilities,        } = gix_protocol::handshake(            &mut transport,            gix_transport::Service::UploadPack,            authenticate,            delegate.handshake_extra_parameters(),            &mut progress,        )        .await?;         let agent = gix_protocol::agent(agent);        let refs = match refs {            Some(refs) => refs,            None => match delegate.action() {                Ok(RefsAction::Skip) => Vec::new(),                Ok(RefsAction::Continue) => {                    #[cfg(all(feature = "async-client", not(feature = "blocking-client")))]                    {                        LsRefsCommand::new(None, &capabilities, ("agent", Some(agent.clone())))                            .invoke_async(&mut transport, &mut progress, trace)                            .await?                    }                    #[cfg(feature = "blocking-client")]                    {                        LsRefsCommand::new(None, &capabilities, ("agent", Some(agent.clone()))).invoke_blocking(                            &mut transport,                            &mut progress,                            trace,                        )?                    }                }                Err(err) => {                    indicate_end_of_interaction(transport, trace).await?;                    return Err(err.into());                }            },        };         let fetch = Command::Fetch;        let mut fetch_features = fetch.default_features(protocol_version, &capabilities);        match delegate.prepare_fetch(protocol_version, &capabilities, &mut fetch_features, &refs) {            Ok(Action::Cancel) => {                return if matches!(protocol_version, gix_transport::Protocol::V1)                    || matches!(fetch_mode, FetchConnection::TerminateOnSuccessfulCompletion)                {                    indicate_end_of_interaction(transport, trace).await.map_err(Into::into)                } else {                    Ok(())                };            }            Ok(Action::Continue) => {                fetch                    .validate_argument_prefixes(protocol_version, &capabilities, &[], &fetch_features)                    .expect("BUG: delegates must always produce valid arguments");            }            Err(err) => {                indicate_end_of_interaction(transport, trace).await?;                return Err(err.into());            }        }         Response::check_required_features(protocol_version, &fetch_features)?;        let sideband_all = fetch_features.iter().any(|(n, _)| *n == "sideband-all");        fetch_features.push(("agent", Some(agent)));        let mut arguments = Arguments::new(protocol_version, fetch_features, trace);        let mut previous_response = None::<Response>;        let mut round = 1;        'negotiation: loop {            progress.step();            progress.set_name(format!("negotiate (round {round})"));            round += 1;            let action = delegate.negotiate(&refs, &mut arguments, previous_response.as_ref())?;            let mut reader = arguments.send(&mut transport, action == Action::Cancel).await?;            if sideband_all {                setup_remote_progress(&mut progress, &mut reader);            }            let response = Response::from_line_reader(                protocol_version,                &mut reader,                true,  /* hack, telling us we don't want this delegate approach anymore */                false, /* just as much of a hack which causes us to expect a pack immediately */            )            .await?;            previous_response = if response.has_pack() {                progress.step();                progress.set_name("receiving pack".into());                if !sideband_all {                    setup_remote_progress(&mut progress, &mut reader);                }                delegate.receive_pack(reader, progress, &refs, &response).await?;                break 'negotiation;            } else {                match action {                    Action::Cancel => break 'negotiation,                    Action::Continue => Some(response),                }            }        }        if matches!(protocol_version, gix_transport::Protocol::V2)            && matches!(fetch_mode, FetchConnection::TerminateOnSuccessfulCompletion)        {            indicate_end_of_interaction(transport, trace).await?;        }        Ok(())    }     fn setup_remote_progress<'a, P>(progress: &mut P, reader: &mut Box<dyn ExtendedBufRead<'a> + Unpin + 'a>)    where        P: NestedProgress,        P::SubProgress: 'static,    {        reader.set_progress_handler(Some(Box::new({            let mut remote_progress = progress.add_child("remote");            move |is_err: bool, data: &[u8]| {                gix_protocol::RemoteProgress::translate_to_progress(is_err, data, &mut remote_progress);                ControlFlow::Continue(())            }        }) as HandleProgress<'a>));    }}pub use fetch_fn::{FetchConnection, legacy_fetch as fetch}; mod delegate {    use std::{        io,        ops::{Deref, DerefMut},    };     use gix_protocol::{        command::Feature,        fetch::{Arguments, Response},        handshake::Ref,    };    use gix_transport::client::Capabilities;     use super::RefsAction;     /// 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`][gix_protocol::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 [`RefsAction::Skip`], no `ls-refs` command is sent to the server.        ///        /// Note that this is called only if we are using protocol version 2.        fn action(&mut self) -> std::io::Result<RefsAction> {            Ok(RefsAction::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<Feature>,            _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 action(&mut self) -> io::Result<RefsAction> {            self.deref_mut().action()        }         fn prepare_fetch(            &mut self,            _version: gix_transport::Protocol,            _server: &Capabilities,            _features: &mut Vec<Feature>,            _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 action(&mut self) -> io::Result<RefsAction> {            self.deref_mut().action()        }         fn prepare_fetch(            &mut self,            _version: gix_transport::Protocol,            _server: &Capabilities,            _features: &mut Vec<Feature>,            _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 gix_protocol::{fetch::Response, handshake::Ref};         use super::DelegateBlocking;         /// The protocol delegate is the bare minimal interface needed to fully control the [`fetch`][gix_protocol::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(all(feature = "async-client", not(feature = "blocking-client")))]    mod async_io {        use std::{io, ops::DerefMut};         use async_trait::async_trait;        use futures_io::AsyncBufRead;        use gix_features::progress::NestedProgress;        use gix_protocol::{fetch::Response, handshake::Ref};         use super::DelegateBlocking;         /// The protocol delegate is the bare minimal interface needed to fully control the [`fetch`][gix_protocol::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

Lines 1–500 of 509.