use crate::fetch::Cursor; #[cfg(all(feature = "async-client", not(feature = "blocking-client")))] use gix_packetline::async_io::StreamingPeekableIter; #[cfg(feature = "blocking-client")] use gix_packetline::blocking_io::StreamingPeekableIter; fn mock_reader(path: &str) -> StreamingPeekableIter { use crate::fixture_bytes; let buf = fixture_bytes(path); StreamingPeekableIter::new(Cursor::new(buf), &[gix_packetline::PacketLineRef::Flush], false) } fn id(hex: &str) -> gix_hash::ObjectId { gix_hash::ObjectId::from_hex(hex.as_bytes()).expect("expect valid hex id") } mod v1 { mod from_line_reader { #[cfg(feature = "blocking-client")] use std::io::Read; #[cfg(all(feature = "async-client", not(feature = "blocking-client")))] use futures_lite::io::AsyncReadExt; use gix_protocol::fetch::{ self, response::{Acknowledgement, ShallowUpdate}, }; use gix_transport::Protocol; use crate::fetch::response::{id, mock_reader}; #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn clone() -> crate::Result { let mut provider = mock_reader("v1/clone-only.response"); let mut reader = provider.as_read_without_sidebands(); let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true, false).await?; assert_eq!(r.acknowledgements(), &[Acknowledgement::Nak]); assert!(r.has_pack()); let mut buf = Vec::new(); let bytes_read = reader.read_to_end(&mut buf).await?; assert_eq!(bytes_read, 1090, "should be able to read the whole pack"); Ok(()) } #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn shallow_clone() -> crate::Result { let mut provider = mock_reader("v1/clone-deepen-1.response"); let mut reader = provider.as_read_without_sidebands(); let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true, false).await?; assert_eq!( r.shallow_updates(), &[ShallowUpdate::Shallow(id("808e50d724f604f69ab93c6da2919c014667bedb"))] ); assert_eq!(r.acknowledgements(), &[Acknowledgement::Nak]); assert!(r.has_pack()); let mut buf = Vec::new(); let bytes_read = reader.read_to_end(&mut buf).await?; assert_eq!(bytes_read, 1989, "should be able to read the whole pack"); Ok(()) } #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn empty_shallow_clone_due_to_depth_being_too_high() -> crate::Result { let mut provider = mock_reader("v1/clone-deepen-5.response"); let mut reader = provider.as_read_without_sidebands(); let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true, false).await?; assert!(r.shallow_updates().is_empty()); assert_eq!(r.acknowledgements(), &[Acknowledgement::Nak]); assert!(r.has_pack()); let mut buf = Vec::new(); let bytes_read = reader.read_to_end(&mut buf).await?; assert_eq!(bytes_read, 1988, "should be able to read the whole pack"); Ok(()) } #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn unshallow_fetch() -> crate::Result { let mut provider = mock_reader("v1/fetch-unshallow.response"); let mut reader = provider.as_read_without_sidebands(); let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true, true).await?; assert_eq!( r.acknowledgements(), &[ Acknowledgement::Common(id("f99771fe6a1b535783af3163eba95a927aae21d5")), Acknowledgement::Common(id("2d9d136fb0765f2e24c44a0f91984318d580d03b")), Acknowledgement::Common(id("dfd0954dabef3b64f458321ef15571cc1a46d552")), ] ); assert_eq!( r.shallow_updates(), &[ ShallowUpdate::Unshallow(id("2d9d136fb0765f2e24c44a0f91984318d580d03b")), ShallowUpdate::Unshallow(id("dfd0954dabef3b64f458321ef15571cc1a46d552")) ] ); assert!(r.has_pack()); let mut pack = Vec::new(); reader.read_to_end(&mut pack).await?; assert_eq!( pack.len(), 2662, "should be able to read the whole pack (and progress info)" ); Ok(()) } #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn fetch_acks_without_pack() -> crate::Result { let mut provider = mock_reader("v1/fetch-no-pack.response"); let r = fetch::Response::from_line_reader(Protocol::V1, &mut provider.as_read_without_sidebands(), true, true) .await?; assert_eq!( r.acknowledgements(), &[ Acknowledgement::Common(id("47ee0b7fe4f3a7d776c78794873e6467e1c47e59")), Acknowledgement::Common(id("3f02c0ad360d96e8dbba92f97b42ebbaa4319db1")), Acknowledgement::Nak, ] ); Ok(()) } #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn fetch_acks_and_pack() -> crate::Result { let mut provider = mock_reader("v1/fetch.response"); let mut reader = provider.as_read_without_sidebands(); let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true, true).await?; assert_eq!( r.acknowledgements(), &[ Acknowledgement::Common(id("6504930888c9c5337e7e065c964f87b60d16a7d7")), Acknowledgement::Common(id("fe17165c392110d1305674c06e4aec35728bfab7")), Acknowledgement::Common(id("f22743895a3024bb0c958335981439f1fa747d57")), Acknowledgement::Ready, Acknowledgement::Nak, ] ); assert!(r.has_pack()); let mut buf = Vec::new(); let bytes_read = reader.read_to_end(&mut buf).await?; assert_eq!(bytes_read, 9703, "should be able to read the whole pack"); Ok(()) } } mod arguments { use bstr::ByteSlice; use gix_protocol::{Command, fetch}; use gix_transport::{Protocol, client::Capabilities}; use crate::fetch::{response::id, transport}; #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn all() -> crate::Result { let (caps, _) = Capabilities::from_bytes(&b"7814e8a05a59c0cf5fb186661d1551c75d1299b5 HEAD\0multi_ack thin-pack filter side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative no-progress include-tag multi_ack_detailed symref=HEAD:refs/heads/master object-format=sha1 agent=git/2.28.0"[..])?; let mut args = fetch::Arguments::new( Protocol::V1, Command::Fetch.default_features(Protocol::V1, &caps), false, ); assert!( args.is_stateless(true /* transport is stateless */), "V1 is stateless if the transport is connection oriented" ); assert!( !args.is_stateless(false /* transport is stateless */), "otherwise V1 is stateful" ); assert!(args.can_use_shallow()); assert!(args.can_use_deepen()); assert!(args.can_use_deepen_not()); assert!(args.can_use_deepen_relative()); assert!(args.can_use_deepen_since()); assert!(args.can_use_filter()); assert!(args.can_use_include_tag()); assert!( !args.can_use_ref_in_want(), "V2 only feature, and we initialize capabilities with V1 for convenience" ); assert!(args.is_empty()); args.shallow(id("97c5a932b3940a09683e924ef6a92b31a6f7c6de")); args.deepen(1); args.deepen_relative(); args.deepen_since(123456); args.deepen_not("tag".into()); args.want(id("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); args.have(id("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")); let mut out = Vec::new(); let mut transport = transport( &mut out, "v1/clone.response", Protocol::V2, gix_transport::client::git::ConnectMode::Daemon, ); let _response = args.send(&mut transport, true).await?; drop(_response); assert_eq!( out.as_slice().as_bstr(), "009ewant aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa thin-pack side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative multi_ack_detailed filter\n000ddeepen 1\n0014deepen-relative\n0018deepen-since 123456\n0013deepen-not tag\n0035shallow 97c5a932b3940a09683e924ef6a92b31a6f7c6de\n00000032have bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n0009done\n" ); Ok(()) } } } mod v2 { mod from_line_reader { #[cfg(feature = "blocking-client")] use std::io::Read; #[cfg(all(feature = "async-client", not(feature = "blocking-client")))] use futures_lite::io::AsyncReadExt; use gix_protocol::fetch::{ self, response::{Acknowledgement, ShallowUpdate}, }; use gix_transport::Protocol; #[cfg(all(feature = "async-client", not(feature = "blocking-client")))] use gix_transport::client::async_io::HandleProgress; #[cfg(feature = "blocking-client")] use gix_transport::client::blocking_io::HandleProgress; use crate::fetch::response::{id, mock_reader}; #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn clone() -> crate::Result { for keepalive in [false, true] { let fixture = format!( "v2/clone-only{}.response", if keepalive { "-with-keepalive" } else { Default::default() } ); let mut provider = mock_reader(&fixture); let mut reader = provider.as_read_without_sidebands(); let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true, true).await?; assert!(r.acknowledgements().is_empty(), "it should go straight to the packfile"); assert!(r.has_pack()); reader.set_progress_handler(Some(Box::new(|_is_err, _text| std::ops::ControlFlow::Continue(())))); let mut buf = Vec::new(); let bytes_read = reader.read_to_end(&mut buf).await?; assert_eq!(bytes_read, 876, "should be able to read the whole pack"); } Ok(()) } #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn shallow_clone() -> crate::Result { let mut provider = mock_reader("v2/clone-deepen-1.response"); let mut reader = provider.as_read_without_sidebands(); let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true, true).await?; assert!(r.acknowledgements().is_empty(), "it should go straight to the packfile"); assert_eq!( r.shallow_updates(), &[ShallowUpdate::Shallow(id("808e50d724f604f69ab93c6da2919c014667bedb"))] ); assert!(r.has_pack()); let mut pack = Vec::new(); reader.read_to_end(&mut pack).await?; assert_eq!(pack.len(), 1991, "should be able to read the whole pack"); Ok(()) } #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn unshallow_fetch() -> crate::Result { let mut provider = mock_reader("v2/fetch-unshallow.response"); let mut reader = provider.as_read_without_sidebands(); let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true, true).await?; assert_eq!( r.acknowledgements(), &[ Acknowledgement::Common(id("f99771fe6a1b535783af3163eba95a927aae21d5")), Acknowledgement::Common(id("2d9d136fb0765f2e24c44a0f91984318d580d03b")), Acknowledgement::Common(id("dfd0954dabef3b64f458321ef15571cc1a46d552")), Acknowledgement::Ready, ] ); assert_eq!( r.shallow_updates(), &[ ShallowUpdate::Unshallow(id("2d9d136fb0765f2e24c44a0f91984318d580d03b")), ShallowUpdate::Unshallow(id("dfd0954dabef3b64f458321ef15571cc1a46d552")) ] ); assert!(r.has_pack()); let mut pack = Vec::new(); reader.read_to_end(&mut pack).await?; assert_eq!( pack.len(), 2664, "should be able to read the whole pack (and progress info)" ); Ok(()) } #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn empty_shallow_clone() -> crate::Result { let mut provider = mock_reader("v2/clone-deepen-5.response"); let mut reader = provider.as_read_without_sidebands(); let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true, true).await?; assert!(r.acknowledgements().is_empty(), "it should go straight to the packfile"); assert!(r.shallow_updates().is_empty(), "it should go straight to the packfile"); assert!(r.has_pack()); let mut pack = Vec::new(); let bytes_read = reader.read_to_end(&mut pack).await?; assert_eq!(bytes_read, 1989, "should be able to read the whole pack"); Ok(()) } #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn clone_with_sidebands() -> crate::Result { let mut provider = mock_reader("v2/clone-only-2.response"); let mut reader = provider.as_read_without_sidebands(); let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true, true).await?; assert!(r.acknowledgements().is_empty(), "it should go straight to the packfile"); assert!(r.has_pack()); let mut buf = Vec::new(); reader.set_progress_handler(Some(Box::new(|is_err: bool, _data: &[u8]| { assert!(!is_err, "fixture does not have an error"); std::ops::ControlFlow::Continue(()) }) as HandleProgress)); let bytes_read = reader.read_to_end(&mut buf).await?; assert_eq!(bytes_read, 1643, "should be able to read the whole pack"); assert_eq!(&buf[..4], b"PACK"); assert_eq!( gix_hash::ObjectId::from_bytes_or_panic(&buf[buf.len() - gix_hash::Kind::Sha1.len_in_bytes()..]) .to_string(), "f34c9be7e0c3ef2c3ed7c62cc7791dbf6dc5ec9a" ); Ok(()) } #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn fetch_acks_without_pack() -> crate::Result { let mut provider = mock_reader("v2/fetch-no-pack.response"); let r = fetch::Response::from_line_reader(Protocol::V2, &mut provider.as_read_without_sidebands(), true, true) .await?; assert_eq!(r.acknowledgements(), &[Acknowledgement::Nak]); Ok(()) } #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn fetch_with_err_response() { let mut provider = mock_reader("v2/fetch-err-line.response"); provider.fail_on_err_lines(true); let mut sidebands = provider.as_read_without_sidebands(); match fetch::Response::from_line_reader(Protocol::V2, &mut sidebands, true, true).await { Ok(_) => panic!("need error response"), Err(err) => match err { fetch::response::Error::UploadPack(err) => { assert_eq!(err.message, "segmentation fault\n"); } err => panic!("we expect upload pack errors, got {err:#?}"), }, } } #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn fetch_acks_and_pack() -> crate::Result { let mut provider = mock_reader("v2/fetch.response"); let mut reader = provider.as_read_without_sidebands(); let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true, true).await?; assert_eq!( r.acknowledgements(), &[ Acknowledgement::Common(id("190c3f6b2319c1f4ec854215533caf8623f8f870")), Acknowledgement::Common(id("97c5a932b3940a09683e924ef6a92b31a6f7c6de")), Acknowledgement::Ready, ] ); assert!(r.has_pack()); let mut buf = Vec::new(); reader.set_progress_handler(Some(Box::new(|a: bool, b: &[u8]| { gix_protocol::RemoteProgress::translate_to_progress(a, b, &mut gix_features::progress::Discard); std::ops::ControlFlow::Continue(()) }) as HandleProgress)); let bytes_read = reader.read_to_end(&mut buf).await?; assert_eq!(bytes_read, 5360, "should be able to read the whole pack"); Ok(()) } } mod arguments { use bstr::ByteSlice; use gix_protocol::{Command, fetch}; use gix_transport::{Protocol, client::Capabilities}; use crate::fetch::{response::id, transport}; #[crate::bisync::bisync] #[cfg_attr(feature = "blocking-client", test)] #[cfg_attr(all(feature = "async-client", not(feature = "blocking-client")), async_std::test)] async fn all() -> crate::Result { let (caps, _) = Capabilities::from_bytes(&b"7814e8a05a59c0cf5fb186661d1551c75d1299b5 HEAD\0multi_ack thin-pack filter side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative no-progress include-tag multi_ack_detailed symref=HEAD:refs/heads/master object-format=sha1 agent=git/2.28.0"[..])?; let mut args = fetch::Arguments::new( Protocol::V2, Command::Fetch.default_features(Protocol::V1, &caps), false, ); assert!(args.can_use_shallow()); assert!(args.can_use_deepen()); assert!(args.can_use_deepen_not()); assert!(args.can_use_deepen_relative()); assert!(args.can_use_deepen_since()); assert!(args.can_use_filter()); assert!(args.can_use_include_tag()); assert!( !args.can_use_ref_in_want(), "V2 only feature, and we initialize capabilities with V1 for convenience" ); assert!(args.is_empty()); args.shallow(id("97c5a932b3940a09683e924ef6a92b31a6f7c6de")); args.deepen(1); args.deepen_relative(); args.deepen_since(123456); args.deepen_not("tag".into()); args.want(id("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); args.have(id("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")); let mut out = Vec::new(); let mut transport = transport( &mut out, "v1/clone.response", Protocol::V2, gix_transport::client::git::ConnectMode::Daemon, ); let _response = args.send(&mut transport, true).await?; drop(_response); assert_eq!(out.as_slice().as_bstr(), "0012command=fetch\n0001000ethin-pack\n000eofs-delta\n0035shallow 97c5a932b3940a09683e924ef6a92b31a6f7c6de\n000ddeepen 1\n0014deepen-relative\n0018deepen-since 123456\n0013deepen-not tag\n0032want aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0032have bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n0009done\n0000"); Ok(()) } } }