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::{
    io,
    ops::{Deref, DerefMut},
};

use bstr::BString;
use git_transport::client::Capabilities;

use crate::fetch::{Arguments, Ref, Response};

/// 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,
}

/// What to do after [`DelegateBlocking::prepare_ls_refs`].
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
pub enum LsRefsAction {
    /// Continue by sending a 'ls-refs' command.
    Continue,
    /// Skip 'ls-refs' entirely.
    ///
    /// This is valid if the 'ref-in-want' capability is taken advantage of. The delegate must then send 'want-ref's in
    /// [`DelegateBlocking::negotiate`].
    Skip,
}

/// 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 [`LsRefsAction::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<&str>)>,
    ) -> std::io::Result<LsRefsAction> {
        Ok(LsRefsAction::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: git_transport::Protocol,
        _server: &Capabilities,
        _features: &mut Vec<(&str, Option<&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 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<&str>)>,
    ) -> io::Result<LsRefsAction> {
        self.deref_mut().prepare_ls_refs(_server, _arguments, _features)
    }

    fn prepare_fetch(
        &mut self,
        _version: git_transport::Protocol,
        _server: &Capabilities,
        _features: &mut Vec<(&str, Option<&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<&str>)>,
    ) -> io::Result<LsRefsAction> {
        self.deref_mut().prepare_ls_refs(_server, _arguments, _features)
    }

    fn prepare_fetch(
        &mut self,
        _version: git_transport::Protocol,
        _server: &Capabilities,
        _features: &mut Vec<(&str, Option<&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 git_features::progress::Progress;

    use crate::fetch::{DelegateBlocking, Ref, Response};

    /// 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 Progress,
            refs: &[Ref],
            previous_response: &Response,
        ) -> io::Result<()>;
    }

    impl<T: Delegate> Delegate for Box<T> {
        fn receive_pack(
            &mut self,
            input: impl BufRead,
            progress: impl Progress,
            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 Progress,
            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 git_features::progress::Progress;

    use crate::fetch::{DelegateBlocking, Ref, Response};

    /// 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 Progress,
            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 Progress,
            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 Progress,
            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;