…into async-io, as usual.
Showing the changes to git-protocol/src/fetch/mod.rs (1 of 12 files). Show all
Unified · Split
| @@ -1,237 +1,18 @@ | ||
| 1 | −use crate::credentials; | |
| 2 | −use git_features::{progress, progress::Progress}; | |
| 3 | −use git_transport::{ | |
| 4 | − client::{self, SetServiceResponse, TransportV2Ext}, | |
| 5 | − Service, | |
| 1 | +#[cfg(feature = "blocking-client")] | |
| 2 | +mod blocking_io; | |
| 3 | +#[cfg(feature = "blocking-client")] | |
| 4 | +pub use blocking_io::{ | |
| 5 | + arguments::Arguments, | |
| 6 | + command::Command, | |
| 7 | + delegate::{Action, Delegate}, | |
| 8 | + fetch, refs, | |
| 9 | + refs::Ref, | |
| 10 | + response, | |
| 11 | + response::Response, | |
| 12 | + Error, | |
| 6 | 13 | }; |
| 7 | −use quick_error::quick_error; | |
| 8 | −use std::io; | |
| 9 | − | |
| 10 | −mod refs; | |
| 11 | −use refs::InternalRef; | |
| 12 | −pub use refs::Ref; | |
| 13 | − | |
| 14 | −mod command; | |
| 15 | −pub use command::Command; | |
| 16 | − | |
| 17 | −mod arguments; | |
| 18 | −pub use arguments::Arguments; | |
| 19 | − | |
| 20 | −/// | |
| 21 | −pub mod response; | |
| 22 | −pub use response::Response; | |
| 23 | − | |
| 24 | −mod delegate; | |
| 25 | −pub use delegate::{Action, Delegate}; | |
| 26 | − | |
| 27 | −#[cfg(test)] | |
| 28 | −mod tests; | |
| 29 | − | |
| 30 | −quick_error! { | |
| 31 | − /// The error used in [`fetch()`]. | |
| 32 | − #[derive(Debug)] | |
| 33 | − #[allow(missing_docs)] | |
| 34 | − pub enum Error { | |
| 35 | − PackIo(err: io::Error) { | |
| 36 | − display("Could not read streaming pack file") | |
| 37 | − from() | |
| 38 | − source(err) | |
| 39 | − } | |
| 40 | − Credentials(err: credentials::Error) { | |
| 41 | − display("Failed to obtain, approve or reject credentials") | |
| 42 | − from() | |
| 43 | − source(err) | |
| 44 | − } | |
| 45 | − Transport(err: client::Error) { | |
| 46 | − display("An error occurred on the transport layer while fetching data") | |
| 47 | − from() | |
| 48 | − source(err) | |
| 49 | − } | |
| 50 | − SymrefWithoutValue { | |
| 51 | − display("A symref 'capability' is expected to have a value") | |
| 52 | − } | |
| 53 | − Ref(err: refs::Error) { | |
| 54 | − display("A reference could not be parsed or invariants were not met") | |
| 55 | − from() | |
| 56 | − source(err) | |
| 57 | − } | |
| 58 | − Response(err: response::Error) { | |
| 59 | − display("The server response could not be parsed") | |
| 60 | − from() | |
| 61 | − source(err) | |
| 62 | − } | |
| 63 | − } | |
| 64 | −} | |
| 65 | 14 | |
| 66 | 15 | /// Returns the name of the agent as key-value pair, commonly used in HTTP headers. |
| 67 | 16 | pub fn agent() -> (&'static str, Option<&'static str>) { |
| 68 | 17 | ("agent", Some(concat!("git/oxide-", env!("CARGO_PKG_VERSION")))) |
| 69 | 18 | } |
| 70 | − | |
| 71 | −/// Perform a 'fetch' operation with the server using `transport`, with `delegate` handling all server interactions. | |
| 72 | −/// | |
| 73 | −/// * `authenticate(operation_to_perform)` is used to receive credentials for the connection and potentially store it | |
| 74 | −/// if the server indicates 'permission denied'. Note that not all transport support authentication or authorization. | |
| 75 | −/// * `progress` is used to emit progress messages. | |
| 76 | −/// | |
| 77 | −/// _Note_ that depending on the `delegate`, the actual action performed can be `ls-refs`, `clone` or `fetch`. | |
| 78 | −pub fn fetch<F>( | |
| 79 | − mut transport: impl client::Transport, | |
| 80 | − delegate: &mut impl Delegate, | |
| 81 | − mut authenticate: F, | |
| 82 | − mut progress: impl Progress, | |
| 83 | −) -> Result<(), Error> | |
| 84 | −where | |
| 85 | − F: FnMut(credentials::Action<'_>) -> credentials::Result, | |
| 86 | −{ | |
| 87 | − let (protocol_version, mut parsed_refs, capabilities, call_ls_refs) = { | |
| 88 | − progress.init(None, progress::steps()); | |
| 89 | − progress.set_name("handshake"); | |
| 90 | − progress.step(); | |
| 91 | − let result = transport.handshake(Service::UploadPack); | |
| 92 | − let SetServiceResponse { | |
| 93 | − actual_protocol, | |
| 94 | − capabilities, | |
| 95 | − refs, | |
| 96 | − } = match result { | |
| 97 | − Ok(v) => Ok(v), | |
| 98 | − Err(client::Error::Io { ref err }) if err.kind() == io::ErrorKind::PermissionDenied => { | |
| 99 | − drop(result); // needed to workaround this: https://github.com/rust-lang/rust/issues/76149 | |
| 100 | − let url = transport.to_url(); | |
| 101 | − progress.set_name("authentication"); | |
| 102 | − let credentials::Outcome { identity, next } = | |
| 103 | − authenticate(credentials::Action::Fill(&url))?.expect("FILL provides an identity"); | |
| 104 | − transport.set_identity(identity)?; | |
| 105 | − progress.step(); | |
| 106 | − progress.set_name("handshake (authenticated)"); | |
| 107 | − match transport.handshake(Service::UploadPack) { | |
| 108 | − Ok(v) => { | |
| 109 | − authenticate(next.approve())?; | |
| 110 | − Ok(v) | |
| 111 | − } | |
| 112 | − // Still no permission? Reject the credentials. | |
| 113 | − Err(client::Error::Io { err }) if err.kind() == io::ErrorKind::PermissionDenied => { | |
| 114 | − authenticate(next.reject())?; | |
| 115 | − Err(client::Error::Io { err }) | |
| 116 | − } | |
| 117 | − // Otherwise, do nothing, as we don't know if it actually got to try the credentials. | |
| 118 | − // If they were previously stored, they remain. In the worst case, the user has to enter them again | |
| 119 | − // next time they try. | |
| 120 | − Err(err) => Err(err), | |
| 121 | − } | |
| 122 | − } | |
| 123 | − Err(err) => Err(err), | |
| 124 | − }?; | |
| 125 | − | |
| 126 | − let mut parsed_refs = Vec::<InternalRef>::new(); | |
| 127 | − refs::from_capabilities(&mut parsed_refs, capabilities.iter())?; | |
| 128 | − | |
| 129 | − let call_ls_refs = match refs { | |
| 130 | − Some(mut refs) => { | |
| 131 | − assert_eq!( | |
| 132 | − actual_protocol, | |
| 133 | − git_transport::Protocol::V1, | |
| 134 | − "Only V1 auto-responds with refs" | |
| 135 | − ); | |
| 136 | − refs::from_v1_refs_received_as_part_of_handshake(&mut parsed_refs, &mut refs)?; | |
| 137 | − false | |
| 138 | − } | |
| 139 | − None => true, | |
| 140 | − }; | |
| 141 | − ( | |
| 142 | − actual_protocol, | |
| 143 | − parsed_refs.into_iter().map(Into::into).collect::<Vec<Ref>>(), | |
| 144 | − capabilities, | |
| 145 | − call_ls_refs, | |
| 146 | − ) | |
| 147 | − }; // this scope is needed, see https://github.com/rust-lang/rust/issues/76149 | |
| 148 | − | |
| 149 | − if transport.desired_protocol_version() != protocol_version { | |
| 150 | − progress.info(format!( | |
| 151 | − "server did not support protocol {} and downgraded to {}", | |
| 152 | − transport.desired_protocol_version() as usize, | |
| 153 | − protocol_version as usize, | |
| 154 | − )); | |
| 155 | − } | |
| 156 | − | |
| 157 | − if call_ls_refs { | |
| 158 | − assert_eq!( | |
| 159 | − protocol_version, | |
| 160 | − git_transport::Protocol::V2, | |
| 161 | − "Only V2 needs a separate request to get specific refs" | |
| 162 | − ); | |
| 163 | − | |
| 164 | − let ls_refs = Command::LsRefs; | |
| 165 | − let mut ls_features = ls_refs.default_features(protocol_version, &capabilities); | |
| 166 | − let mut ls_args = ls_refs.initial_arguments(&ls_features); | |
| 167 | − delegate.prepare_ls_refs(&capabilities, &mut ls_args, &mut ls_features); | |
| 168 | − ls_refs.validate_argument_prefixes_or_panic(protocol_version, &capabilities, &ls_args, &ls_features); | |
| 169 | − | |
| 170 | − progress.step(); | |
| 171 | − progress.set_name("list refs"); | |
| 172 | − let mut remote_refs = transport.invoke( | |
| 173 | − ls_refs.as_str(), | |
| 174 | − ls_features.into_iter(), | |
| 175 | − if ls_args.is_empty() { | |
| 176 | − None | |
| 177 | − } else { | |
| 178 | − Some(ls_args.into_iter()) | |
| 179 | − }, | |
| 180 | − )?; | |
| 181 | − refs::from_v2_refs(&mut parsed_refs, &mut remote_refs)?; | |
| 182 | − } | |
| 183 | − | |
| 184 | − let fetch = Command::Fetch; | |
| 185 | − let mut fetch_features = fetch.default_features(protocol_version, &capabilities); | |
| 186 | − let next = delegate.prepare_fetch(protocol_version, &capabilities, &mut fetch_features, &parsed_refs); | |
| 187 | − fetch.validate_argument_prefixes_or_panic(protocol_version, &capabilities, &[], &fetch_features); | |
| 188 | − | |
| 189 | − if next == Action::Close { | |
| 190 | − transport.close()?; | |
| 191 | − return Ok(()); | |
| 192 | − } | |
| 193 | − | |
| 194 | − Response::check_required_features(protocol_version, &fetch_features)?; | |
| 195 | − let sideband_all = fetch_features.iter().any(|(n, _)| *n == "sideband-all"); | |
| 196 | − let mut arguments = Arguments::new(protocol_version, fetch_features); | |
| 197 | − let mut previous_response = None::<Response>; | |
| 198 | − let mut round = 1; | |
| 199 | − 'negotiation: loop { | |
| 200 | − progress.step(); | |
| 201 | − progress.set_name(format!("negotiate (round {})", round)); | |
| 202 | − round += 1; | |
| 203 | − let action = delegate.negotiate(&parsed_refs, &mut arguments, previous_response.as_ref()); | |
| 204 | − let mut reader = arguments.send(&mut transport, action == Action::Close)?; | |
| 205 | − if sideband_all { | |
| 206 | − setup_remote_progress(&mut progress, &mut reader); | |
| 207 | − } | |
| 208 | − let response = Response::from_line_reader(protocol_version, &mut reader)?; | |
| 209 | − previous_response = if response.has_pack() { | |
| 210 | − progress.step(); | |
| 211 | − progress.set_name("receiving pack"); | |
| 212 | − if !sideband_all { | |
| 213 | − setup_remote_progress(&mut progress, &mut reader); | |
| 214 | − } | |
| 215 | − delegate.receive_pack(reader, progress, &parsed_refs, &response)?; | |
| 216 | − break 'negotiation; | |
| 217 | − } else { | |
| 218 | − match action { | |
| 219 | − Action::Close => break 'negotiation, | |
| 220 | − Action::Continue => Some(response), | |
| 221 | − } | |
| 222 | − } | |
| 223 | − } | |
| 224 | − Ok(()) | |
| 225 | −} | |
| 226 | − | |
| 227 | −fn setup_remote_progress( | |
| 228 | − progress: &mut impl Progress, | |
| 229 | − reader: &mut Box<dyn git_transport::client::ExtendedBufRead + '_>, | |
| 230 | −) { | |
| 231 | − reader.set_progress_handler(Some(Box::new({ | |
| 232 | − let mut remote_progress = progress.add_child("remote"); | |
| 233 | − move |is_err: bool, data: &[u8]| { | |
| 234 | − crate::RemoteProgress::translate_to_progress(is_err, data, &mut remote_progress) | |
| 235 | − } | |
| 236 | − }) as git_transport::client::HandleProgress)); | |
| 237 | −} | |