History

[git-protocol] move everything into `blocking_io` for later translation…

…into async-io, as usual.

Sebastian Thiel committed 5 years ago

Commit fa033741 parent 0d39b5d12 files · +295 −254Browse files

Showing the changes to git-protocol/src (11 of 12 files). Show all

Unified · Split

11 files
  1. git-protocol/src/fetch/blocking_io/arguments.rs +6 −2
  2. git-protocol/src/fetch/blocking_io/command.rs +0 −0
  3. git-protocol/src/fetch/blocking_io/delegate.rs +0 −0
  4. git-protocol/src/fetch/blocking_io/mod.rs +244 −0
  5. git-protocol/src/fetch/blocking_io/refs.rs +2 −0
  6. git-protocol/src/fetch/blocking_io/response.rs +1 −1
  7. git-protocol/src/fetch/blocking_io/tests/arguments.rs +2 −2
  8. git-protocol/src/fetch/blocking_io/tests/command.rs +12 −9
  9. git-protocol/src/fetch/blocking_io/tests/mod.rs +0 −0
  10. git-protocol/src/fetch/blocking_io/tests/refs.rs +4 −2
  11. git-protocol/src/fetch/mod.rs +12 −231
git-protocol/src/fetch/blocking_io/arguments.rsrenamed from git-protocol/src/fetch/arguments.rs+6−2
@@ -1,10 +1,14 @@
1−use crate::fetch::{command::Feature, Command};
1+use std::{fmt, io::Write};
2+
23 use bstr::{BStr, BString, ByteSlice};
4+
35 use git_transport::{
46 client::{self, TransportV2Ext},
57 Protocol,
68 };
7−use std::{fmt, io::Write};
9+
10+use crate::fetch::blocking_io::command::Feature;
11+use crate::fetch::Command;
812
913 /// The arguments passed to a server command.
1014 pub struct Arguments {
git-protocol/src/fetch/blocking_io/command.rsrenamed from git-protocol/src/fetch/command.rs+0−0

File renamed without changes.

git-protocol/src/fetch/blocking_io/delegate.rsrenamed from git-protocol/src/fetch/delegate.rs+0−0

File renamed without changes.

git-protocol/src/fetch/blocking_io/mod.rs+244−0
@@ -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;
git-protocol/src/fetch/blocking_io/refs.rsrenamed from git-protocol/src/fetch/refs.rs+2−0
@@ -3,7 +3,9 @@ use quick_error::quick_error; · 2 unmodified lines
33 use std::io;
44
55 quick_error! {
6+ /// The error returned when parsing References/refs from the server response.
67 #[derive(Debug)]
8+ #[allow(missing_docs)]
79 pub enum Error {
810 Io(err: io::Error) {
911 display("An IO error occurred while reading refs from the server")
git-protocol/src/fetch/blocking_io/response.rsrenamed from git-protocol/src/fetch/response.rs+1−1
@@ -1,4 +1,4 @@
1−use crate::fetch::command::Feature;
1+use crate::fetch::blocking_io::command::Feature;
22 use git_transport::{client, Protocol};
33 use quick_error::quick_error;
44 use std::io;
git-protocol/src/fetch/blocking_io/tests/arguments.rsrenamed from git-protocol/src/fetch/tests/arguments.rs+2−2
@@ -72,7 +72,7 @@ fn id(hex: &str) -> git_hash::ObjectId { · 71 unmodified lines
7272 }
7373
7474 mod v1 {
75− use crate::fetch::tests::arguments::{arguments_v1, id, transport};
75+ use crate::fetch::blocking_io::tests::arguments::{arguments_v1, id, transport};
7676 use bstr::ByteSlice;
7777
7878 #[test]
@@ -157,7 +157,7 @@ mod v1 { · 78 unmodified lines
157157 }
158158
159159 mod v2 {
160− use crate::fetch::tests::arguments::{arguments_v2, id, transport};
160+ use crate::fetch::blocking_io::tests::arguments::{arguments_v2, id, transport};
161161 use bstr::ByteSlice;
162162
163163 #[test]
git-protocol/src/fetch/blocking_io/tests/command.rsrenamed from git-protocol/src/fetch/tests/command.rs+12−9
@@ -8,11 +8,8 @@ mod v1 { · 7 unmodified lines
88 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";
99 mod fetch {
1010 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};
1613
1714 #[test]
1815 fn it_chooses_the_best_multi_ack_and_sideband() {
@@ -61,7 +58,8 @@ mod v2 { · 42 unmodified lines
6158
6259 mod fetch {
6360 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};
6563
6664 #[test]
6765 fn all_features() {
@@ -80,9 +78,11 @@ mod v2 { · 12 unmodified lines
8078 }
8179
8280 mod initial_arguments {
83− use crate::fetch::{tests::command::v2::capabilities, Command};
8481 use bstr::ByteSlice;
8582
83+ use crate::fetch::blocking_io::tests::command::v2::capabilities;
84+ use crate::fetch::Command;
85+
8686 #[test]
8787 fn for_all_features() {
8888 assert_eq!(
@@ -108,7 +108,8 @@ mod v2 { · 19 unmodified lines
108108
109109 mod ls_refs {
110110 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};
112113
113114 #[test]
114115 fn default_as_there_are_no_features() {
@@ -123,9 +124,11 @@ mod v2 { · 8 unmodified lines
123124 }
124125
125126 mod validate {
126− use crate::fetch::{tests::command::v2::capabilities, Command};
127127 use bstr::ByteSlice;
128128
129+ use crate::fetch::blocking_io::tests::command::v2::capabilities;
130+ use crate::fetch::Command;
131+
129132 #[test]
130133 fn ref_prefixes_can_always_be_used() {
131134 Command::LsRefs.validate_argument_prefixes_or_panic(
git-protocol/src/fetch/blocking_io/tests/mod.rsrenamed from git-protocol/src/fetch/tests/mod.rs+0−0

File renamed without changes.

git-protocol/src/fetch/blocking_io/tests/refs.rsrenamed from git-protocol/src/fetch/tests/refs.rs+4−2
@@ -1,7 +1,9 @@
1−use super::super::{refs, InternalRef, Ref};
2−use git_transport::client;
31 use std::io;
42
3+use git_transport::client;
4+
5+use crate::fetch::{blocking_io::refs::InternalRef, refs, Ref};
6+
57 fn oid(hex_sha: &str) -> git_hash::ObjectId {
68 git_hash::ObjectId::from_hex(hex_sha.as_bytes()).expect("valid input")
79 }
git-protocol/src/fetch/mod.rs+12−231
@@ -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,
613 };
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−}
6514
6615 /// Returns the name of the agent as key-value pair, commonly used in HTTP headers.
6716 pub fn agent() -> (&'static str, Option<&'static str>) {
6817 ("agent", Some(concat!("git/oxide-", env!("CARGO_PKG_VERSION"))))
6918 }
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−}