…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 | use bstr::{BStr, BString, ByteSlice}; | 3 | use bstr::{BStr, BString, ByteSlice}; |
| 4 | |||
| 3 | use git_transport::{ | 5 | use git_transport::{ |
| 4 | client::{self, TransportV2Ext}, | 6 | client::{self, TransportV2Ext}, |
| 5 | Protocol, | 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 | /// The arguments passed to a server command. | 13 | /// The arguments passed to a server command. |
| 10 | pub struct Arguments { | 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 | use std::io; | 3 | use std::io; |
| 4 | 4 | ||
| 5 | quick_error! { | 5 | quick_error! { |
| 6 | /// The error returned when parsing References/refs from the server response. | ||
| 6 | #[derive(Debug)] | 7 | #[derive(Debug)] |
| 8 | #[allow(missing_docs)] | ||
| 7 | pub enum Error { | 9 | pub enum Error { |
| 8 | Io(err: io::Error) { | 10 | Io(err: io::Error) { |
| 9 | display("An IO error occurred while reading refs from the server") | 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 | use git_transport::{client, Protocol}; | 2 | use git_transport::{client, Protocol}; |
| 3 | use quick_error::quick_error; | 3 | use quick_error::quick_error; |
| 4 | use std::io; | 4 | use std::io; |
| @@ -72,7 +72,7 @@ fn id(hex: &str) -> git_hash::ObjectId { · 71 unmodified lines | |||
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | mod v1 { | 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 | use bstr::ByteSlice; | 76 | use bstr::ByteSlice; |
| 77 | 77 | ||
| 78 | #[test] | 78 | #[test] |
| @@ -157,7 +157,7 @@ mod v1 { · 78 unmodified lines | |||
| 157 | } | 157 | } |
| 158 | 158 | ||
| 159 | mod v2 { | 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 | use bstr::ByteSlice; | 161 | use bstr::ByteSlice; |
| 162 | 162 | ||
| 163 | #[test] | 163 | #[test] |
| @@ -8,11 +8,8 @@ mod v1 { · 7 unmodified lines | |||
| 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"; | 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 | mod fetch { | 9 | mod fetch { |
| 10 | mod default_features { | 10 | mod default_features { |
| 11 | use crate::fetch::{ | 11 | use crate::fetch::blocking_io::tests::command::v1::{capabilities, GITHUB_CAPABILITIES}; |
| 12 | self, | 12 | use crate::fetch::{self, Command}; |
| 13 | tests::command::v1::{capabilities, GITHUB_CAPABILITIES}, | ||
| 14 | Command, | ||
| 15 | }; | ||
| 16 | 13 | ||
| 17 | #[test] | 14 | #[test] |
| 18 | fn it_chooses_the_best_multi_ack_and_sideband() { | 15 | fn it_chooses_the_best_multi_ack_and_sideband() { |
| @@ -61,7 +58,8 @@ mod v2 { · 42 unmodified lines | |||
| 61 | 58 | ||
| 62 | mod fetch { | 59 | mod fetch { |
| 63 | mod default_features { | 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 | #[test] | 64 | #[test] |
| 67 | fn all_features() { | 65 | fn all_features() { |
| @@ -80,9 +78,11 @@ mod v2 { · 12 unmodified lines | |||
| 80 | } | 78 | } |
| 81 | 79 | ||
| 82 | mod initial_arguments { | 80 | mod initial_arguments { |
| 83 | use crate::fetch::{tests::command::v2::capabilities, Command}; | ||
| 84 | use bstr::ByteSlice; | 81 | use bstr::ByteSlice; |
| 85 | 82 | ||
| 83 | use crate::fetch::blocking_io::tests::command::v2::capabilities; | ||
| 84 | use crate::fetch::Command; | ||
| 85 | |||
| 86 | #[test] | 86 | #[test] |
| 87 | fn for_all_features() { | 87 | fn for_all_features() { |
| 88 | assert_eq!( | 88 | assert_eq!( |
| @@ -108,7 +108,8 @@ mod v2 { · 19 unmodified lines | |||
| 108 | 108 | ||
| 109 | mod ls_refs { | 109 | mod ls_refs { |
| 110 | mod default_features { | 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 | #[test] | 114 | #[test] |
| 114 | fn default_as_there_are_no_features() { | 115 | fn default_as_there_are_no_features() { |
| @@ -123,9 +124,11 @@ mod v2 { · 8 unmodified lines | |||
| 123 | } | 124 | } |
| 124 | 125 | ||
| 125 | mod validate { | 126 | mod validate { |
| 126 | use crate::fetch::{tests::command::v2::capabilities, Command}; | ||
| 127 | use bstr::ByteSlice; | 127 | use bstr::ByteSlice; |
| 128 | 128 | ||
| 129 | use crate::fetch::blocking_io::tests::command::v2::capabilities; | ||
| 130 | use crate::fetch::Command; | ||
| 131 | |||
| 129 | #[test] | 132 | #[test] |
| 130 | fn ref_prefixes_can_always_be_used() { | 133 | fn ref_prefixes_can_always_be_used() { |
| 131 | Command::LsRefs.validate_argument_prefixes_or_panic( | 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 | use std::io; | 1 | use std::io; |
| 4 | 2 | ||
| 3 | use git_transport::client; | ||
| 4 | |||
| 5 | use crate::fetch::{blocking_io::refs::InternalRef, refs, Ref}; | ||
| 6 | |||
| 5 | fn oid(hex_sha: &str) -> git_hash::ObjectId { | 7 | fn oid(hex_sha: &str) -> git_hash::ObjectId { |
| 6 | git_hash::ObjectId::from_hex(hex_sha.as_bytes()).expect("valid input") | 8 | git_hash::ObjectId::from_hex(hex_sha.as_bytes()).expect("valid input") |
| 7 | } | 9 | } |
| @@ -1,237 +1,18 @@ | |||
| 1 | use crate::credentials; | 1 | #[cfg(feature = "blocking-client")] |
| 2 | use git_features::{progress, progress::Progress}; | 2 | mod blocking_io; |
| 3 | use git_transport::{ | 3 | #[cfg(feature = "blocking-client")] |
| 4 | client::{self, SetServiceResponse, TransportV2Ext}, | 4 | pub use blocking_io::{ |
| 5 | Service, | 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 | /// Returns the name of the agent as key-value pair, commonly used in HTTP headers. | 15 | /// Returns the name of the agent as key-value pair, commonly used in HTTP headers. |
| 67 | pub fn agent() -> (&'static str, Option<&'static str>) { | 16 | pub fn agent() -> (&'static str, Option<&'static str>) { |
| 68 | ("agent", Some(concat!("git/oxide-", env!("CARGO_PKG_VERSION")))) | 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 | } | ||