use crate::Protocol; use bstr::BString; /// The way to connect to a process speaking the `git` protocol. #[derive(PartialEq, Eq, Clone, Copy)] pub enum ConnectMode { /// A git daemon. Daemon, /// A spawned `git` process to upload a pack to the client. Process, } /// A TCP connection to either a `git` daemon or a spawned `git` process. /// /// When connecting to a daemon, additional context information is sent with the first line of the handshake. Otherwise that /// context is passed using command line arguments to a [spawned `git` process][crate::client::file::SpawnProcessOnDemand]. pub struct Connection { pub(in crate::client) writer: W, pub(in crate::client) line_provider: git_packetline::StreamingPeekableIter, pub(in crate::client) path: BString, pub(in crate::client) virtual_host: Option<(String, Option)>, pub(in crate::client) desired_version: Protocol, pub(in crate::client) mode: ConnectMode, } pub(crate) mod message { use bstr::{BString, ByteVec}; use crate::{Protocol, Service}; pub fn connect( service: Service, version: Protocol, path: &[u8], virtual_host: Option<&(String, Option)>, ) -> BString { let mut out = bstr::BString::from(service.as_str()); out.push(b' '); let path = git_url::expand_path::for_shell(path.into()); out.extend_from_slice(&path); out.push(0); if let Some((host, port)) = virtual_host { out.push_str("host="); out.extend_from_slice(host.as_bytes()); if let Some(port) = port { out.push_byte(b':'); out.push_str(&format!("{}", port)); } out.push(0); } // We only send the version when needed, as otherwise a V2 server who is asked for V1 will respond with 'version 1' // as extra lines in the reply, which we don't want to handle. Especially since an old server will not respond with that // line (is what I assume, at least), so it's an optional part in the response to understand and handle. There is no value // in that, so let's help V2 servers to respond in a way that assumes V1. if version != Protocol::V1 { out.push(0); out.push_str(format!("version={}", version as usize)); out.push(0); } out } #[cfg(test)] mod tests { use crate::{client::git, Protocol, Service}; #[test] fn version_1_without_host_and_version() { assert_eq!( git::message::connect(Service::UploadPack, Protocol::V1, b"hello/world", None), "git-upload-pack hello/world\0" ) } #[test] fn version_2_without_host_and_version() { assert_eq!( git::message::connect(Service::UploadPack, Protocol::V2, b"hello\\world", None), "git-upload-pack hello\\world\0\0version=2\0" ) } #[test] fn with_host_without_port() { assert_eq!( git::message::connect( Service::UploadPack, Protocol::V1, b"hello\\world", Some(&("host".into(), None)) ), "git-upload-pack hello\\world\0host=host\0" ) } #[test] fn with_host_with_port() { assert_eq!( git::message::connect( Service::UploadPack, Protocol::V1, b"hello\\world", Some(&("host".into(), Some(404))) ), "git-upload-pack hello\\world\0host=host:404\0" ) } } } #[cfg(all(not(feature = "blocking-client"), feature = "async-client"))] mod async_io; #[cfg(feature = "blocking-client")] mod blocking_io; #[cfg(feature = "blocking-client")] pub use blocking_io::connect;