diff options
Diffstat (limited to 'vendor/gix-protocol/src')
-rw-r--r-- | vendor/gix-protocol/src/command/mod.rs | 6 | ||||
-rw-r--r-- | vendor/gix-protocol/src/fetch/arguments/async_io.rs | 2 | ||||
-rw-r--r-- | vendor/gix-protocol/src/fetch/arguments/blocking_io.rs | 2 | ||||
-rw-r--r-- | vendor/gix-protocol/src/fetch/arguments/mod.rs | 36 | ||||
-rw-r--r-- | vendor/gix-protocol/src/fetch/delegate.rs | 4 | ||||
-rw-r--r-- | vendor/gix-protocol/src/fetch/response/async_io.rs | 18 | ||||
-rw-r--r-- | vendor/gix-protocol/src/fetch/response/blocking_io.rs | 18 | ||||
-rw-r--r-- | vendor/gix-protocol/src/fetch/response/mod.rs | 2 | ||||
-rw-r--r-- | vendor/gix-protocol/src/fetch/tests.rs | 28 | ||||
-rw-r--r-- | vendor/gix-protocol/src/fetch_fn.rs | 7 | ||||
-rw-r--r-- | vendor/gix-protocol/src/handshake/function.rs | 10 | ||||
-rw-r--r-- | vendor/gix-protocol/src/handshake/refs/shared.rs | 4 | ||||
-rw-r--r-- | vendor/gix-protocol/src/ls_refs.rs | 4 |
13 files changed, 111 insertions, 30 deletions
diff --git a/vendor/gix-protocol/src/command/mod.rs b/vendor/gix-protocol/src/command/mod.rs index d560220d0..1216ba625 100644 --- a/vendor/gix-protocol/src/command/mod.rs +++ b/vendor/gix-protocol/src/command/mod.rs @@ -60,7 +60,7 @@ mod with_io { match self { Command::LsRefs => &[], Command::Fetch => match version { - gix_transport::Protocol::V1 => &[ + gix_transport::Protocol::V0 | gix_transport::Protocol::V1 => &[ "multi_ack", "thin-pack", "side-band", @@ -120,7 +120,7 @@ mod with_io { ) -> Vec<Feature> { match self { Command::Fetch => match version { - gix_transport::Protocol::V1 => { + gix_transport::Protocol::V0 | gix_transport::Protocol::V1 => { let has_multi_ack_detailed = server_capabilities.contains("multi_ack_detailed"); let has_sideband_64k = server_capabilities.contains("side-band-64k"); self.all_features(version) @@ -173,7 +173,7 @@ mod with_io { panic!("{}: argument {} is not known or allowed", self.as_str(), arg); } match version { - gix_transport::Protocol::V1 => { + gix_transport::Protocol::V0 | gix_transport::Protocol::V1 => { for (feature, _) in features { if server .iter() diff --git a/vendor/gix-protocol/src/fetch/arguments/async_io.rs b/vendor/gix-protocol/src/fetch/arguments/async_io.rs index 3984ec610..fc876d02c 100644 --- a/vendor/gix-protocol/src/fetch/arguments/async_io.rs +++ b/vendor/gix-protocol/src/fetch/arguments/async_io.rs @@ -14,7 +14,7 @@ impl Arguments { assert!(add_done_argument, "If there are no haves, is_done must be true."); } match self.version { - gix_transport::Protocol::V1 => { + gix_transport::Protocol::V0 | gix_transport::Protocol::V1 => { let (on_into_read, retained_state) = self.prepare_v1( transport.connection_persists_across_multiple_requests(), add_done_argument, diff --git a/vendor/gix-protocol/src/fetch/arguments/blocking_io.rs b/vendor/gix-protocol/src/fetch/arguments/blocking_io.rs index b49d1a1ba..571792148 100644 --- a/vendor/gix-protocol/src/fetch/arguments/blocking_io.rs +++ b/vendor/gix-protocol/src/fetch/arguments/blocking_io.rs @@ -15,7 +15,7 @@ impl Arguments { assert!(add_done_argument, "If there are no haves, is_done must be true."); } match self.version { - gix_transport::Protocol::V1 => { + gix_transport::Protocol::V0 | gix_transport::Protocol::V1 => { let (on_into_read, retained_state) = self.prepare_v1( transport.connection_persists_across_multiple_requests(), add_done_argument, diff --git a/vendor/gix-protocol/src/fetch/arguments/mod.rs b/vendor/gix-protocol/src/fetch/arguments/mod.rs index 39c9eee3a..1adf993bb 100644 --- a/vendor/gix-protocol/src/fetch/arguments/mod.rs +++ b/vendor/gix-protocol/src/fetch/arguments/mod.rs @@ -49,20 +49,20 @@ impl Arguments { pub fn can_use_deepen(&self) -> bool { self.shallow } - /// Return true if the 'deepen_since' capability is supported. + /// Return true if the '`deepen_since`' capability is supported. /// /// This is relevant for partial clones when using `--depth X` and retrieving additional history /// based on a date beyond which all history should be present. pub fn can_use_deepen_since(&self) -> bool { self.deepen_since } - /// Return true if the 'deepen_not' capability is supported. + /// Return true if the '`deepen_not`' capability is supported. /// /// This is relevant for partial clones when using `--depth X`. pub fn can_use_deepen_not(&self) -> bool { self.deepen_not } - /// Return true if the 'deepen_relative' capability is supported. + /// Return true if the '`deepen_relative`' capability is supported. /// /// This is relevant for partial clones when using `--depth X`. pub fn can_use_deepen_relative(&self) -> bool { @@ -78,6 +78,18 @@ impl Arguments { pub fn can_use_include_tag(&self) -> bool { self.supports_include_tag } + /// Return true if we will use a stateless mode of operation, which can be decided in conjunction with `transport_is_stateless`. + /// + /// * we are always stateless if the transport is stateless, i.e. doesn't support multiple interactions with a single connection. + /// * we are always stateless if the protocol version is `2` + /// * otherwise we may be stateful. + pub fn is_stateless(&self, transport_is_stateless: bool) -> bool { + #[cfg(any(feature = "async-client", feature = "blocking-client"))] + let res = transport_is_stateless || self.version == gix_transport::Protocol::V2; + #[cfg(not(any(feature = "async-client", feature = "blocking-client")))] + let res = transport_is_stateless; + res + } /// Add the given `id` pointing to a commit to the 'want' list. /// @@ -153,7 +165,18 @@ impl Arguments { pub fn use_include_tag(&mut self) { debug_assert!(self.supports_include_tag, "'include-tag' feature required"); if self.supports_include_tag { - self.args.push("include-tag".into()); + match self.version { + gix_transport::Protocol::V0 | gix_transport::Protocol::V1 => { + let features = self + .features_for_first_want + .as_mut() + .expect("call use_include_tag before want()"); + features.push("include-tag".into()) + } + gix_transport::Protocol::V2 => { + self.args.push("include-tag".into()); + } + } } } fn prefixed(&mut self, prefix: &str, value: impl fmt::Display) { @@ -173,13 +196,16 @@ impl Arguments { let mut deepen_relative = shallow; let supports_include_tag; let (initial_arguments, features_for_first_want) = match version { - gix_transport::Protocol::V1 => { + gix_transport::Protocol::V0 | gix_transport::Protocol::V1 => { deepen_since = has("deepen-since"); deepen_not = has("deepen-not"); deepen_relative = has("deepen-relative"); supports_include_tag = has("include-tag"); let baked_features = features .iter() + .filter( + |(f, _)| *f != "include-tag", /* not a capability in that sense, needs to be turned on by caller later */ + ) .map(|(n, v)| match v { Some(v) => format!("{n}={v}"), None => n.to_string(), diff --git a/vendor/gix-protocol/src/fetch/delegate.rs b/vendor/gix-protocol/src/fetch/delegate.rs index d4b900f66..e90022d41 100644 --- a/vendor/gix-protocol/src/fetch/delegate.rs +++ b/vendor/gix-protocol/src/fetch/delegate.rs @@ -40,7 +40,7 @@ pub trait DelegateBlocking { /// 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 [`ls_refs::Action::Skip`], no 'ls-refs` command is sent to the server. + /// If the delegate returns [`ls_refs::Action::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( @@ -55,7 +55,7 @@ pub trait DelegateBlocking { /// 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. + /// `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`, diff --git a/vendor/gix-protocol/src/fetch/response/async_io.rs b/vendor/gix-protocol/src/fetch/response/async_io.rs index 550ed46b6..c510a1ad4 100644 --- a/vendor/gix-protocol/src/fetch/response/async_io.rs +++ b/vendor/gix-protocol/src/fetch/response/async_io.rs @@ -32,15 +32,22 @@ async fn parse_v2_section<T>( impl Response { /// Parse a response of the given `version` of the protocol from `reader`. + /// + /// `client_expects_pack` is only relevant for V1 stateful connections, and if `false`, causes us to stop parsing when seeing `NAK`, + /// and if `true` we will keep parsing until we get a pack as the client already signalled to the server that it's done. + /// This way of doing things allows us to exploit knowledge about more recent versions of the protocol, which keeps code easier + /// and more localized without having to support all the cruft that there is. pub async fn from_line_reader( version: Protocol, reader: &mut (impl client::ExtendedBufRead + Unpin), + client_expects_pack: bool, ) -> Result<Response, response::Error> { match version { - Protocol::V1 => { + Protocol::V0 | Protocol::V1 => { let mut line = String::new(); let mut acks = Vec::<Acknowledgement>::new(); let mut shallows = Vec::<ShallowUpdate>::new(); + let mut saw_ready = false; let has_pack = 'lines: loop { line.clear(); let peeked_line = match reader.peek_data_line().await { @@ -48,8 +55,8 @@ impl Response { // This special case (hang/block forever) deals with a single NAK being a legitimate EOF sometimes // Note that this might block forever in stateful connections as there it's not really clear // if something will be following or not by just looking at the response. Instead you have to know - // the arguments sent to the server and count response lines based on intricate knowledge on how the - // server works. + // [a lot](https://github.com/git/git/blob/9e49351c3060e1fa6e0d2de64505b7becf157f28/fetch-pack.c#L583-L594) + // to deal with this correctly. // For now this is acceptable, as V2 can be used as a workaround, which also is the default. Some(Err(err)) if err.kind() == io::ErrorKind::UnexpectedEof => break 'lines false, Some(Err(err)) => return Err(err.into()), @@ -80,6 +87,11 @@ impl Response { 0, "consuming a peeked line works" ); + // When the server sends ready, we know there is going to be a pack so no need to stop early. + saw_ready |= matches!(acks.last(), Some(Acknowledgement::Ready)); + if let Some(Acknowledgement::Nak) = acks.last().filter(|_| !client_expects_pack && !saw_ready) { + break 'lines false; + } }; Ok(Response { acks, diff --git a/vendor/gix-protocol/src/fetch/response/blocking_io.rs b/vendor/gix-protocol/src/fetch/response/blocking_io.rs index 7a3f2deb3..309f5a7c5 100644 --- a/vendor/gix-protocol/src/fetch/response/blocking_io.rs +++ b/vendor/gix-protocol/src/fetch/response/blocking_io.rs @@ -32,15 +32,22 @@ fn parse_v2_section<T>( impl Response { /// Parse a response of the given `version` of the protocol from `reader`. + /// + /// `client_expects_pack` is only relevant for V1 stateful connections, and if `false`, causes us to stop parsing when seeing `NAK`, + /// and if `true` we will keep parsing until we get a pack as the client already signalled to the server that it's done. + /// This way of doing things allows us to exploit knowledge about more recent versions of the protocol, which keeps code easier + /// and more localized without having to support all the cruft that there is. pub fn from_line_reader( version: Protocol, reader: &mut impl client::ExtendedBufRead, + client_expects_pack: bool, ) -> Result<Response, response::Error> { match version { - Protocol::V1 => { + Protocol::V0 | Protocol::V1 => { let mut line = String::new(); let mut acks = Vec::<Acknowledgement>::new(); let mut shallows = Vec::<ShallowUpdate>::new(); + let mut saw_ready = false; let has_pack = 'lines: loop { line.clear(); let peeked_line = match reader.peek_data_line() { @@ -48,8 +55,8 @@ impl Response { // This special case (hang/block forever) deals with a single NAK being a legitimate EOF sometimes // Note that this might block forever in stateful connections as there it's not really clear // if something will be following or not by just looking at the response. Instead you have to know - // the arguments sent to the server and count response lines based on intricate knowledge on how the - // server works. + // [a lot](https://github.com/git/git/blob/9e49351c3060e1fa6e0d2de64505b7becf157f28/fetch-pack.c#L583-L594) + // to deal with this correctly. // For now this is acceptable, as V2 can be used as a workaround, which also is the default. Some(Err(err)) if err.kind() == io::ErrorKind::UnexpectedEof => break 'lines false, Some(Err(err)) => return Err(err.into()), @@ -76,6 +83,11 @@ impl Response { break 'lines true; } assert_ne!(reader.readline_str(&mut line)?, 0, "consuming a peeked line works"); + // When the server sends ready, we know there is going to be a pack so no need to stop early. + saw_ready |= matches!(acks.last(), Some(Acknowledgement::Ready)); + if let Some(Acknowledgement::Nak) = acks.last().filter(|_| !client_expects_pack && !saw_ready) { + break 'lines false; + } }; Ok(Response { acks, diff --git a/vendor/gix-protocol/src/fetch/response/mod.rs b/vendor/gix-protocol/src/fetch/response/mod.rs index bfb4beb83..5f2f7f007 100644 --- a/vendor/gix-protocol/src/fetch/response/mod.rs +++ b/vendor/gix-protocol/src/fetch/response/mod.rs @@ -170,7 +170,7 @@ impl Response { /// make it easy to maintain all versions with a single code base that aims to be and remain maintainable. pub fn check_required_features(version: Protocol, features: &[Feature]) -> Result<(), Error> { match version { - Protocol::V1 => { + Protocol::V0 | Protocol::V1 => { let has = |name: &str| features.iter().any(|f| f.0 == name); // Let's focus on V2 standards, and simply not support old servers to keep our code simpler if !has("multi_ack_detailed") { diff --git a/vendor/gix-protocol/src/fetch/tests.rs b/vendor/gix-protocol/src/fetch/tests.rs index 5a1902ad2..80dc752dd 100644 --- a/vendor/gix-protocol/src/fetch/tests.rs +++ b/vendor/gix-protocol/src/fetch/tests.rs @@ -163,7 +163,7 @@ mod arguments { async fn include_tag() { let mut out = Vec::new(); let mut t = transport(&mut out, true); - let mut arguments = arguments_v1(["include-tag", "feature-b"].iter().cloned()); + let mut arguments = arguments_v1(["include-tag", "feature-b"].iter().copied()); assert!(arguments.can_use_include_tag()); arguments.use_include_tag(); @@ -171,8 +171,7 @@ mod arguments { arguments.send(&mut t, true).await.expect("sending to buffer to work"); assert_eq!( out.as_bstr(), - b"0048want ff333369de1221f9bfbbe03a3a13e9a09bc1ffff include-tag feature-b -0010include-tag + b"0048want ff333369de1221f9bfbbe03a3a13e9a09bc1ffff feature-b include-tag 00000009done " .as_bstr() @@ -180,10 +179,29 @@ mod arguments { } #[maybe_async::test(feature = "blocking-client", async(feature = "async-client", async_std::test))] + async fn no_include_tag() { + let mut out = Vec::new(); + let mut t = transport(&mut out, true); + let mut arguments = arguments_v1(["include-tag", "feature-b"].iter().copied()); + assert!(arguments.can_use_include_tag()); + + arguments.want(id("ff333369de1221f9bfbbe03a3a13e9a09bc1ffff")); + arguments.send(&mut t, true).await.expect("sending to buffer to work"); + assert_eq!( + out.as_bstr(), + b"003cwant ff333369de1221f9bfbbe03a3a13e9a09bc1ffff feature-b +00000009done +" + .as_bstr(), + "it's possible to not have it enabled, even though it's advertised by the server" + ); + } + + #[maybe_async::test(feature = "blocking-client", async(feature = "async-client", async_std::test))] async fn haves_and_wants_for_clone() { let mut out = Vec::new(); let mut t = transport(&mut out, true); - let mut arguments = arguments_v1(["feature-a", "feature-b"].iter().cloned()); + let mut arguments = arguments_v1(["feature-a", "feature-b"].iter().copied()); assert!( !arguments.can_use_include_tag(), "needs to be enabled by features in V1" @@ -298,6 +316,8 @@ mod arguments { let mut out = Vec::new(); let mut t = transport(&mut out, true); let mut arguments = arguments_v2(["feature-a", "shallow"].iter().copied()); + assert!(arguments.is_stateless(true), "V2 is stateless…"); + assert!(arguments.is_stateless(false), "…in all cases"); arguments.deepen(1); arguments.deepen_relative(); diff --git a/vendor/gix-protocol/src/fetch_fn.rs b/vendor/gix-protocol/src/fetch_fn.rs index 5899ed95f..64d457711 100644 --- a/vendor/gix-protocol/src/fetch_fn.rs +++ b/vendor/gix-protocol/src/fetch_fn.rs @@ -128,7 +128,12 @@ where if sideband_all { setup_remote_progress(&mut progress, &mut reader); } - let response = Response::from_line_reader(protocol_version, &mut reader).await?; + let response = Response::from_line_reader( + protocol_version, + &mut reader, + true, /* hack, telling us we don't want this delegate approach anymore */ + ) + .await?; previous_response = if response.has_pack() { progress.step(); progress.set_name("receiving pack"); diff --git a/vendor/gix-protocol/src/handshake/function.rs b/vendor/gix-protocol/src/handshake/function.rs index 1206ee363..6324fb3e1 100644 --- a/vendor/gix-protocol/src/handshake/function.rs +++ b/vendor/gix-protocol/src/handshake/function.rs @@ -77,10 +77,12 @@ where let parsed_refs = match refs { Some(mut refs) => { - assert_eq!( - actual_protocol, - gix_transport::Protocol::V1, - "Only V1 auto-responds with refs" + assert!( + matches!( + actual_protocol, + gix_transport::Protocol::V0 | gix_transport::Protocol::V1 + ), + "Only V(0|1) auto-responds with refs" ); Some( refs::from_v1_refs_received_as_part_of_handshake_and_capabilities(&mut refs, capabilities.iter()) diff --git a/vendor/gix-protocol/src/handshake/refs/shared.rs b/vendor/gix-protocol/src/handshake/refs/shared.rs index 1d0dfc256..046a2a1b1 100644 --- a/vendor/gix-protocol/src/handshake/refs/shared.rs +++ b/vendor/gix-protocol/src/handshake/refs/shared.rs @@ -123,6 +123,10 @@ pub(in crate::handshake::refs) fn parse_v1( } match path.strip_suffix(b"^{}") { Some(stripped) => { + if hex_hash.iter().all(|b| *b == b'0') && stripped == b"capabilities" { + // this is a special dummy-ref just for the sake of getting capabilities across in a repo that is empty. + return Ok(()); + } let (previous_path, tag) = out_refs .pop() diff --git a/vendor/gix-protocol/src/ls_refs.rs b/vendor/gix-protocol/src/ls_refs.rs index a31588894..d0b2e9ba0 100644 --- a/vendor/gix-protocol/src/ls_refs.rs +++ b/vendor/gix-protocol/src/ls_refs.rs @@ -1,7 +1,7 @@ mod error { use crate::handshake::refs::parse; - /// The error returned by [ls_refs()][crate::ls_refs()]. + /// The error returned by [`ls_refs()`][crate::ls_refs()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -25,7 +25,7 @@ mod error { } pub use error::Error; -/// What to do after preparing ls-refs in [ls_refs()][crate::ls_refs()]. +/// What to do after preparing ls-refs in [`ls_refs()`][crate::ls_refs()]. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub enum Action { /// Continue by sending a 'ls-refs' command. |