summaryrefslogtreecommitdiffstats
path: root/vendor/gix-protocol/src/handshake/refs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-protocol/src/handshake/refs')
-rw-r--r--vendor/gix-protocol/src/handshake/refs/async_io.rs43
-rw-r--r--vendor/gix-protocol/src/handshake/refs/blocking_io.rs31
-rw-r--r--vendor/gix-protocol/src/handshake/refs/mod.rs72
-rw-r--r--vendor/gix-protocol/src/handshake/refs/shared.rs237
-rw-r--r--vendor/gix-protocol/src/handshake/refs/tests.rs223
5 files changed, 606 insertions, 0 deletions
diff --git a/vendor/gix-protocol/src/handshake/refs/async_io.rs b/vendor/gix-protocol/src/handshake/refs/async_io.rs
new file mode 100644
index 000000000..19ea543c7
--- /dev/null
+++ b/vendor/gix-protocol/src/handshake/refs/async_io.rs
@@ -0,0 +1,43 @@
+use crate::handshake::{refs, refs::parse::Error, Ref};
+
+/// Parse refs from the given input line by line. Protocol V2 is required for this to succeed.
+pub async fn from_v2_refs(in_refs: &mut dyn gix_transport::client::ReadlineBufRead) -> Result<Vec<Ref>, Error> {
+ let mut out_refs = Vec::new();
+ while let Some(line) = in_refs
+ .readline()
+ .await
+ .transpose()?
+ .transpose()?
+ .and_then(|l| l.as_bstr())
+ {
+ out_refs.push(refs::shared::parse_v2(line)?);
+ }
+ Ok(out_refs)
+}
+
+/// Parse refs from the return stream of the handshake as well as the server capabilities, also received as part of the
+/// handshake.
+/// Together they form a complete set of refs.
+///
+/// # Note
+///
+/// Symbolic refs are shoe-horned into server capabilities whereas refs (without symbolic ones) are sent automatically as
+/// part of the handshake. Both symbolic and peeled refs need to be combined to fit into the [`Ref`] type provided here.
+pub async fn from_v1_refs_received_as_part_of_handshake_and_capabilities<'a>(
+ in_refs: &mut dyn gix_transport::client::ReadlineBufRead,
+ capabilities: impl Iterator<Item = gix_transport::client::capabilities::Capability<'a>>,
+) -> Result<Vec<Ref>, refs::parse::Error> {
+ let mut out_refs = refs::shared::from_capabilities(capabilities)?;
+ let number_of_possible_symbolic_refs_for_lookup = out_refs.len();
+
+ while let Some(line) = in_refs
+ .readline()
+ .await
+ .transpose()?
+ .transpose()?
+ .and_then(|l| l.as_bstr())
+ {
+ refs::shared::parse_v1(number_of_possible_symbolic_refs_for_lookup, &mut out_refs, line)?;
+ }
+ Ok(out_refs.into_iter().map(Into::into).collect())
+}
diff --git a/vendor/gix-protocol/src/handshake/refs/blocking_io.rs b/vendor/gix-protocol/src/handshake/refs/blocking_io.rs
new file mode 100644
index 000000000..7ad695b77
--- /dev/null
+++ b/vendor/gix-protocol/src/handshake/refs/blocking_io.rs
@@ -0,0 +1,31 @@
+use crate::handshake::{refs, refs::parse::Error, Ref};
+
+/// Parse refs from the given input line by line. Protocol V2 is required for this to succeed.
+pub fn from_v2_refs(in_refs: &mut dyn gix_transport::client::ReadlineBufRead) -> Result<Vec<Ref>, Error> {
+ let mut out_refs = Vec::new();
+ while let Some(line) = in_refs.readline().transpose()?.transpose()?.and_then(|l| l.as_bstr()) {
+ out_refs.push(refs::shared::parse_v2(line)?);
+ }
+ Ok(out_refs)
+}
+
+/// Parse refs from the return stream of the handshake as well as the server capabilities, also received as part of the
+/// handshake.
+/// Together they form a complete set of refs.
+///
+/// # Note
+///
+/// Symbolic refs are shoe-horned into server capabilities whereas refs (without symbolic ones) are sent automatically as
+/// part of the handshake. Both symbolic and peeled refs need to be combined to fit into the [`Ref`] type provided here.
+pub fn from_v1_refs_received_as_part_of_handshake_and_capabilities<'a>(
+ in_refs: &mut dyn gix_transport::client::ReadlineBufRead,
+ capabilities: impl Iterator<Item = gix_transport::client::capabilities::Capability<'a>>,
+) -> Result<Vec<Ref>, Error> {
+ let mut out_refs = refs::shared::from_capabilities(capabilities)?;
+ let number_of_possible_symbolic_refs_for_lookup = out_refs.len();
+
+ while let Some(line) = in_refs.readline().transpose()?.transpose()?.and_then(|l| l.as_bstr()) {
+ refs::shared::parse_v1(number_of_possible_symbolic_refs_for_lookup, &mut out_refs, line)?;
+ }
+ Ok(out_refs.into_iter().map(Into::into).collect())
+}
diff --git a/vendor/gix-protocol/src/handshake/refs/mod.rs b/vendor/gix-protocol/src/handshake/refs/mod.rs
new file mode 100644
index 000000000..889842e4c
--- /dev/null
+++ b/vendor/gix-protocol/src/handshake/refs/mod.rs
@@ -0,0 +1,72 @@
+use bstr::BStr;
+
+use super::Ref;
+
+///
+pub mod parse {
+ use bstr::BString;
+
+ /// The error returned when parsing References/refs from the server response.
+ #[derive(Debug, thiserror::Error)]
+ #[allow(missing_docs)]
+ pub enum Error {
+ #[error(transparent)]
+ Io(#[from] std::io::Error),
+ #[error(transparent)]
+ DecodePacketline(#[from] gix_transport::packetline::decode::Error),
+ #[error(transparent)]
+ Id(#[from] gix_hash::decode::Error),
+ #[error("{symref:?} could not be parsed. A symref is expected to look like <NAME>:<target>.")]
+ MalformedSymref { symref: BString },
+ #[error("{0:?} could not be parsed. A V1 ref line should be '<hex-hash> <path>'.")]
+ MalformedV1RefLine(BString),
+ #[error(
+ "{0:?} could not be parsed. A V2 ref line should be '<hex-hash> <path>[ (peeled|symref-target):<value>'."
+ )]
+ MalformedV2RefLine(BString),
+ #[error("The ref attribute {attribute:?} is unknown. Found in line {line:?}")]
+ UnknownAttribute { attribute: BString, line: BString },
+ #[error("{message}")]
+ InvariantViolation { message: &'static str },
+ }
+}
+
+impl Ref {
+ /// Provide shared fields referring to the ref itself, namely `(name, target, [peeled])`.
+ /// In case of peeled refs, the tag object itself is returned as it is what the ref directly refers to, and target of the tag is returned
+ /// as `peeled`.
+ /// If `unborn`, the first object id will be the null oid.
+ pub fn unpack(&self) -> (&BStr, Option<&gix_hash::oid>, Option<&gix_hash::oid>) {
+ match self {
+ Ref::Direct { full_ref_name, object }
+ | Ref::Symbolic {
+ full_ref_name, object, ..
+ } => (full_ref_name.as_ref(), Some(object), None),
+ Ref::Peeled {
+ full_ref_name,
+ tag: object,
+ object: peeled,
+ } => (full_ref_name.as_ref(), Some(object), Some(peeled)),
+ Ref::Unborn {
+ full_ref_name,
+ target: _,
+ } => (full_ref_name.as_ref(), None, None),
+ }
+ }
+}
+
+#[cfg(any(feature = "blocking-client", feature = "async-client"))]
+pub(crate) mod shared;
+
+#[cfg(feature = "async-client")]
+mod async_io;
+#[cfg(feature = "async-client")]
+pub use async_io::{from_v1_refs_received_as_part_of_handshake_and_capabilities, from_v2_refs};
+
+#[cfg(feature = "blocking-client")]
+mod blocking_io;
+#[cfg(feature = "blocking-client")]
+pub use blocking_io::{from_v1_refs_received_as_part_of_handshake_and_capabilities, from_v2_refs};
+
+#[cfg(test)]
+mod tests;
diff --git a/vendor/gix-protocol/src/handshake/refs/shared.rs b/vendor/gix-protocol/src/handshake/refs/shared.rs
new file mode 100644
index 000000000..1d0dfc256
--- /dev/null
+++ b/vendor/gix-protocol/src/handshake/refs/shared.rs
@@ -0,0 +1,237 @@
+use bstr::{BStr, BString, ByteSlice};
+
+use crate::handshake::{refs::parse::Error, Ref};
+
+impl From<InternalRef> for Ref {
+ fn from(v: InternalRef) -> Self {
+ match v {
+ InternalRef::Symbolic {
+ path,
+ target: Some(target),
+ object,
+ } => Ref::Symbolic {
+ full_ref_name: path,
+ target,
+ object,
+ },
+ InternalRef::Symbolic {
+ path,
+ target: None,
+ object,
+ } => Ref::Direct {
+ full_ref_name: path,
+ object,
+ },
+ InternalRef::Peeled { path, tag, object } => Ref::Peeled {
+ full_ref_name: path,
+ tag,
+ object,
+ },
+ InternalRef::Direct { path, object } => Ref::Direct {
+ full_ref_name: path,
+ object,
+ },
+ InternalRef::SymbolicForLookup { .. } => {
+ unreachable!("this case should have been removed during processing")
+ }
+ }
+ }
+}
+
+#[cfg_attr(test, derive(PartialEq, Eq, Debug, Clone))]
+pub(crate) enum InternalRef {
+ /// A ref pointing to a `tag` object, which in turns points to an `object`, usually a commit
+ Peeled {
+ path: BString,
+ tag: gix_hash::ObjectId,
+ object: gix_hash::ObjectId,
+ },
+ /// A ref pointing to a commit object
+ Direct { path: BString, object: gix_hash::ObjectId },
+ /// A symbolic ref pointing to `target` ref, which in turn points to an `object`
+ Symbolic {
+ path: BString,
+ /// It is `None` if the target is unreachable as it points to another namespace than the one is currently set
+ /// on the server (i.e. based on the repository at hand or the user performing the operation).
+ ///
+ /// The latter is more of an edge case, please [this issue][#205] for details.
+ target: Option<BString>,
+ object: gix_hash::ObjectId,
+ },
+ /// extracted from V1 capabilities, which contain some important symbolic refs along with their targets
+ /// These don't contain the Id
+ SymbolicForLookup { path: BString, target: Option<BString> },
+}
+
+impl InternalRef {
+ fn unpack_direct(self) -> Option<(BString, gix_hash::ObjectId)> {
+ match self {
+ InternalRef::Direct { path, object } => Some((path, object)),
+ _ => None,
+ }
+ }
+ fn lookup_symbol_has_path(&self, predicate_path: &BStr) -> bool {
+ matches!(self, InternalRef::SymbolicForLookup { path, .. } if path == predicate_path)
+ }
+}
+
+pub(crate) fn from_capabilities<'a>(
+ capabilities: impl Iterator<Item = gix_transport::client::capabilities::Capability<'a>>,
+) -> Result<Vec<InternalRef>, Error> {
+ let mut out_refs = Vec::new();
+ let symref_values = capabilities.filter_map(|c| {
+ if c.name() == b"symref".as_bstr() {
+ c.value().map(ToOwned::to_owned)
+ } else {
+ None
+ }
+ });
+ for symref in symref_values {
+ let (left, right) = symref.split_at(symref.find_byte(b':').ok_or_else(|| Error::MalformedSymref {
+ symref: symref.to_owned(),
+ })?);
+ if left.is_empty() || right.is_empty() {
+ return Err(Error::MalformedSymref {
+ symref: symref.to_owned(),
+ });
+ }
+ out_refs.push(InternalRef::SymbolicForLookup {
+ path: left.into(),
+ target: match &right[1..] {
+ b"(null)" => None,
+ name => Some(name.into()),
+ },
+ })
+ }
+ Ok(out_refs)
+}
+
+pub(in crate::handshake::refs) fn parse_v1(
+ num_initial_out_refs: usize,
+ out_refs: &mut Vec<InternalRef>,
+ line: &BStr,
+) -> Result<(), Error> {
+ let trimmed = line.trim_end();
+ let (hex_hash, path) = trimmed.split_at(
+ trimmed
+ .find(b" ")
+ .ok_or_else(|| Error::MalformedV1RefLine(trimmed.to_owned().into()))?,
+ );
+ let path = &path[1..];
+ if path.is_empty() {
+ return Err(Error::MalformedV1RefLine(trimmed.to_owned().into()));
+ }
+ match path.strip_suffix(b"^{}") {
+ Some(stripped) => {
+ let (previous_path, tag) =
+ out_refs
+ .pop()
+ .and_then(InternalRef::unpack_direct)
+ .ok_or(Error::InvariantViolation {
+ message: "Expecting peeled refs to be preceded by direct refs",
+ })?;
+ if previous_path != stripped {
+ return Err(Error::InvariantViolation {
+ message: "Expecting peeled refs to have the same base path as the previous, unpeeled one",
+ });
+ }
+ out_refs.push(InternalRef::Peeled {
+ path: previous_path,
+ tag,
+ object: gix_hash::ObjectId::from_hex(hex_hash.as_bytes())?,
+ });
+ }
+ None => {
+ let object = gix_hash::ObjectId::from_hex(hex_hash.as_bytes())?;
+ match out_refs
+ .iter()
+ .take(num_initial_out_refs)
+ .position(|r| r.lookup_symbol_has_path(path.into()))
+ {
+ Some(position) => match out_refs.swap_remove(position) {
+ InternalRef::SymbolicForLookup { path: _, target } => out_refs.push(InternalRef::Symbolic {
+ path: path.into(),
+ object,
+ target,
+ }),
+ _ => unreachable!("Bug in lookup_symbol_has_path - must return lookup symbols"),
+ },
+ None => out_refs.push(InternalRef::Direct {
+ object,
+ path: path.into(),
+ }),
+ };
+ }
+ }
+ Ok(())
+}
+
+pub(in crate::handshake::refs) fn parse_v2(line: &BStr) -> Result<Ref, Error> {
+ let trimmed = line.trim_end();
+ let mut tokens = trimmed.splitn(3, |b| *b == b' ');
+ match (tokens.next(), tokens.next()) {
+ (Some(hex_hash), Some(path)) => {
+ let id = if hex_hash == b"unborn" {
+ None
+ } else {
+ Some(gix_hash::ObjectId::from_hex(hex_hash.as_bytes())?)
+ };
+ if path.is_empty() {
+ return Err(Error::MalformedV2RefLine(trimmed.to_owned().into()));
+ }
+ Ok(if let Some(attribute) = tokens.next() {
+ let mut tokens = attribute.splitn(2, |b| *b == b':');
+ match (tokens.next(), tokens.next()) {
+ (Some(attribute), Some(value)) => {
+ if value.is_empty() {
+ return Err(Error::MalformedV2RefLine(trimmed.to_owned().into()));
+ }
+ match attribute {
+ b"peeled" => Ref::Peeled {
+ full_ref_name: path.into(),
+ object: gix_hash::ObjectId::from_hex(value.as_bytes())?,
+ tag: id.ok_or(Error::InvariantViolation {
+ message: "got 'unborn' as tag target",
+ })?,
+ },
+ b"symref-target" => match value {
+ b"(null)" => Ref::Direct {
+ full_ref_name: path.into(),
+ object: id.ok_or(Error::InvariantViolation {
+ message: "got 'unborn' while (null) was a symref target",
+ })?,
+ },
+ name => match id {
+ Some(id) => Ref::Symbolic {
+ full_ref_name: path.into(),
+ object: id,
+ target: name.into(),
+ },
+ None => Ref::Unborn {
+ full_ref_name: path.into(),
+ target: name.into(),
+ },
+ },
+ },
+ _ => {
+ return Err(Error::UnknownAttribute {
+ attribute: attribute.to_owned().into(),
+ line: trimmed.to_owned().into(),
+ })
+ }
+ }
+ }
+ _ => return Err(Error::MalformedV2RefLine(trimmed.to_owned().into())),
+ }
+ } else {
+ Ref::Direct {
+ object: id.ok_or(Error::InvariantViolation {
+ message: "got 'unborn' as object name of direct reference",
+ })?,
+ full_ref_name: path.into(),
+ }
+ })
+ }
+ _ => Err(Error::MalformedV2RefLine(trimmed.to_owned().into())),
+ }
+}
diff --git a/vendor/gix-protocol/src/handshake/refs/tests.rs b/vendor/gix-protocol/src/handshake/refs/tests.rs
new file mode 100644
index 000000000..a7c9171a5
--- /dev/null
+++ b/vendor/gix-protocol/src/handshake/refs/tests.rs
@@ -0,0 +1,223 @@
+use gix_transport::{client, client::Capabilities};
+
+/// Convert a hexadecimal hash into its corresponding `ObjectId` or _panic_.
+fn oid(hex: &str) -> gix_hash::ObjectId {
+ gix_hash::ObjectId::from_hex(hex.as_bytes()).expect("40 bytes hex")
+}
+
+use crate::handshake::{refs, refs::shared::InternalRef, Ref};
+
+#[maybe_async::test(feature = "blocking-client", async(feature = "async-client", async_std::test))]
+async fn extract_references_from_v2_refs() {
+ let input = &mut Fixture(
+ "808e50d724f604f69ab93c6da2919c014667bedb HEAD symref-target:refs/heads/main
+808e50d724f604f69ab93c6da2919c014667bedb MISSING_NAMESPACE_TARGET symref-target:(null)
+unborn HEAD symref-target:refs/heads/main
+unborn refs/heads/symbolic symref-target:refs/heads/target
+808e50d724f604f69ab93c6da2919c014667bedb refs/heads/main
+7fe1b98b39423b71e14217aa299a03b7c937d656 refs/tags/foo peeled:808e50d724f604f69ab93c6da2919c014667bedb
+7fe1b98b39423b71e14217aa299a03b7c937d6ff refs/tags/blaz
+"
+ .as_bytes(),
+ );
+
+ let out = refs::from_v2_refs(input).await.expect("no failure on valid input");
+
+ assert_eq!(
+ out,
+ vec![
+ Ref::Symbolic {
+ full_ref_name: "HEAD".into(),
+ target: "refs/heads/main".into(),
+ object: oid("808e50d724f604f69ab93c6da2919c014667bedb")
+ },
+ Ref::Direct {
+ full_ref_name: "MISSING_NAMESPACE_TARGET".into(),
+ object: oid("808e50d724f604f69ab93c6da2919c014667bedb")
+ },
+ Ref::Unborn {
+ full_ref_name: "HEAD".into(),
+ target: "refs/heads/main".into(),
+ },
+ Ref::Unborn {
+ full_ref_name: "refs/heads/symbolic".into(),
+ target: "refs/heads/target".into(),
+ },
+ Ref::Direct {
+ full_ref_name: "refs/heads/main".into(),
+ object: oid("808e50d724f604f69ab93c6da2919c014667bedb")
+ },
+ Ref::Peeled {
+ full_ref_name: "refs/tags/foo".into(),
+ tag: oid("7fe1b98b39423b71e14217aa299a03b7c937d656"),
+ object: oid("808e50d724f604f69ab93c6da2919c014667bedb")
+ },
+ Ref::Direct {
+ full_ref_name: "refs/tags/blaz".into(),
+ object: oid("7fe1b98b39423b71e14217aa299a03b7c937d6ff")
+ },
+ ]
+ )
+}
+
+#[maybe_async::test(feature = "blocking-client", async(feature = "async-client", async_std::test))]
+async fn extract_references_from_v1_refs() {
+ let input = &mut Fixture(
+ "73a6868963993a3328e7d8fe94e5a6ac5078a944 HEAD
+21c9b7500cb144b3169a6537961ec2b9e865be81 MISSING_NAMESPACE_TARGET
+73a6868963993a3328e7d8fe94e5a6ac5078a944 refs/heads/main
+8e472f9ccc7d745927426cbb2d9d077de545aa4e refs/pull/13/head
+dce0ea858eef7ff61ad345cc5cdac62203fb3c10 refs/tags/gix-commitgraph-v0.0.0
+21c9b7500cb144b3169a6537961ec2b9e865be81 refs/tags/gix-commitgraph-v0.0.0^{}"
+ .as_bytes(),
+ );
+ let out = refs::from_v1_refs_received_as_part_of_handshake_and_capabilities(
+ input,
+ Capabilities::from_bytes(b"\0symref=HEAD:refs/heads/main symref=MISSING_NAMESPACE_TARGET:(null)")
+ .expect("valid capabilities")
+ .0
+ .iter(),
+ )
+ .await
+ .expect("no failure from valid input");
+ assert_eq!(
+ out,
+ vec![
+ Ref::Symbolic {
+ full_ref_name: "HEAD".into(),
+ target: "refs/heads/main".into(),
+ object: oid("73a6868963993a3328e7d8fe94e5a6ac5078a944")
+ },
+ Ref::Direct {
+ full_ref_name: "MISSING_NAMESPACE_TARGET".into(),
+ object: oid("21c9b7500cb144b3169a6537961ec2b9e865be81")
+ },
+ Ref::Direct {
+ full_ref_name: "refs/heads/main".into(),
+ object: oid("73a6868963993a3328e7d8fe94e5a6ac5078a944")
+ },
+ Ref::Direct {
+ full_ref_name: "refs/pull/13/head".into(),
+ object: oid("8e472f9ccc7d745927426cbb2d9d077de545aa4e")
+ },
+ Ref::Peeled {
+ full_ref_name: "refs/tags/gix-commitgraph-v0.0.0".into(),
+ tag: oid("dce0ea858eef7ff61ad345cc5cdac62203fb3c10"),
+ object: oid("21c9b7500cb144b3169a6537961ec2b9e865be81")
+ },
+ ]
+ )
+}
+
+#[test]
+fn extract_symbolic_references_from_capabilities() -> Result<(), client::Error> {
+ let caps = client::Capabilities::from_bytes(
+ b"\0unrelated symref=HEAD:refs/heads/main symref=ANOTHER:refs/heads/foo symref=MISSING_NAMESPACE_TARGET:(null) agent=git/2.28.0",
+ )?
+ .0;
+ let out = refs::shared::from_capabilities(caps.iter()).expect("a working example");
+
+ assert_eq!(
+ out,
+ vec![
+ InternalRef::SymbolicForLookup {
+ path: "HEAD".into(),
+ target: Some("refs/heads/main".into())
+ },
+ InternalRef::SymbolicForLookup {
+ path: "ANOTHER".into(),
+ target: Some("refs/heads/foo".into())
+ },
+ InternalRef::SymbolicForLookup {
+ path: "MISSING_NAMESPACE_TARGET".into(),
+ target: None
+ }
+ ]
+ );
+ Ok(())
+}
+
+#[cfg(any(feature = "async-client", feature = "blocking-client"))]
+struct Fixture<'a>(&'a [u8]);
+
+#[cfg(feature = "blocking-client")]
+impl<'a> std::io::Read for Fixture<'a> {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ self.0.read(buf)
+ }
+}
+
+#[cfg(feature = "blocking-client")]
+impl<'a> std::io::BufRead for Fixture<'a> {
+ fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
+ self.0.fill_buf()
+ }
+
+ fn consume(&mut self, amt: usize) {
+ self.0.consume(amt)
+ }
+}
+
+#[cfg(feature = "blocking-client")]
+impl<'a> gix_transport::client::ReadlineBufRead for Fixture<'a> {
+ fn readline(
+ &mut self,
+ ) -> Option<std::io::Result<Result<gix_packetline::PacketLineRef<'_>, gix_packetline::decode::Error>>> {
+ use bstr::{BStr, ByteSlice};
+ let bytes: &BStr = self.0.into();
+ let mut lines = bytes.lines();
+ let res = lines.next()?;
+ self.0 = lines.as_bytes();
+ Some(Ok(Ok(gix_packetline::PacketLineRef::Data(res))))
+ }
+}
+
+#[cfg(feature = "async-client")]
+impl<'a> Fixture<'a> {
+ fn project_inner(self: std::pin::Pin<&mut Self>) -> std::pin::Pin<&mut &'a [u8]> {
+ #[allow(unsafe_code)]
+ unsafe {
+ std::pin::Pin::new(&mut self.get_unchecked_mut().0)
+ }
+ }
+}
+
+#[cfg(feature = "async-client")]
+impl<'a> futures_io::AsyncRead for Fixture<'a> {
+ fn poll_read(
+ self: std::pin::Pin<&mut Self>,
+ cx: &mut std::task::Context<'_>,
+ buf: &mut [u8],
+ ) -> std::task::Poll<std::io::Result<usize>> {
+ self.project_inner().poll_read(cx, buf)
+ }
+}
+
+#[cfg(feature = "async-client")]
+impl<'a> futures_io::AsyncBufRead for Fixture<'a> {
+ fn poll_fill_buf(
+ self: std::pin::Pin<&mut Self>,
+ cx: &mut std::task::Context<'_>,
+ ) -> std::task::Poll<std::io::Result<&[u8]>> {
+ self.project_inner().poll_fill_buf(cx)
+ }
+
+ fn consume(self: std::pin::Pin<&mut Self>, amt: usize) {
+ self.project_inner().consume(amt)
+ }
+}
+
+#[cfg(feature = "async-client")]
+#[async_trait::async_trait(?Send)]
+impl<'a> gix_transport::client::ReadlineBufRead for Fixture<'a> {
+ async fn readline(
+ &mut self,
+ ) -> Option<std::io::Result<Result<gix_packetline::PacketLineRef<'_>, gix_packetline::decode::Error>>> {
+ use bstr::{BStr, ByteSlice};
+ let bytes: &BStr = self.0.into();
+ let mut lines = bytes.lines();
+ let res = lines.next()?;
+ self.0 = lines.as_bytes();
+ Some(Ok(Ok(gix_packetline::PacketLineRef::Data(res))))
+ }
+}