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