use std::io::{Read, Write};
use super::RequestedProtocol;
use crate::remote::Destination;
pub trait Transport: Send + Sync {
fn connect(&self, request: &ConnectRequest<'_>) -> Result<Box<dyn Connection>, ConnectError>;
}
pub trait Connection: Read + Write + Send {
fn close_write(&mut self) -> std::io::Result<()> {
Ok(())
}
fn finish(self: Box<Self>) -> Result<(), ConnectError> {
Ok(())
}
}
#[derive(Debug)]
pub struct ConnectRequest<'a> {
pub(crate) destination: &'a Destination,
pub(crate) service: Service,
pub(crate) protocol: RequestedProtocol,
}
impl ConnectRequest<'_> {
pub fn url(&self) -> &[u8] {
self.destination.bytes()
}
pub fn address(&self) -> &[u8] {
let url = self.destination.bytes();
let scheme = self.destination.helper_scheme().map_or(0, <[u8]>::len);
let rest = &url[scheme..];
rest.strip_prefix(b"::")
.or_else(|| rest.strip_prefix(b"://"))
.unwrap_or(rest)
}
pub fn scheme(&self) -> &[u8] {
self.destination.helper_scheme().unwrap_or_default()
}
pub fn service(&self) -> Service {
self.service
}
pub fn protocol(&self) -> RequestedProtocol {
self.protocol
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Service {
UploadPack,
ReceivePack,
}
impl Service {
pub fn name(self) -> &'static str {
match self {
Self::UploadPack => "git-upload-pack",
Self::ReceivePack => "git-receive-pack",
}
}
}
#[derive(Debug, thiserror::Error)]
#[error("transport: {0}")]
pub struct ConnectError(#[source] Box<dyn std::error::Error + Send + Sync>);
impl ConnectError {
pub fn new(error: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
Self(error.into())
}
}
#[derive(Debug)]
pub struct ChildConnection {
child: std::process::Child,
input: Option<std::process::ChildStdin>,
output: std::process::ChildStdout,
reaped: bool,
}
impl ChildConnection {
pub fn new(mut child: std::process::Child) -> Self {
let input = child.stdin.take().expect("piped standard input");
let output = child.stdout.take().expect("piped standard output");
Self {
child,
input: Some(input),
output,
reaped: false,
}
}
}
impl Read for ChildConnection {
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
self.output.read(buffer)
}
}
impl Write for ChildConnection {
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
match &mut self.input {
Some(input) => input.write(buffer),
None => Err(std::io::ErrorKind::BrokenPipe.into()),
}
}
fn flush(&mut self) -> std::io::Result<()> {
self.input.as_mut().map_or(Ok(()), Write::flush)
}
}
impl Connection for ChildConnection {
fn close_write(&mut self) -> std::io::Result<()> {
drop(self.input.take());
Ok(())
}
fn finish(mut self: Box<Self>) -> Result<(), ConnectError> {
drop(self.input.take());
let status = self.child.wait().map_err(ConnectError::new)?;
self.reaped = true;
if status.success() {
Ok(())
} else {
Err(ConnectError::new(format!("service exited with {status}")))
}
}
}
impl Drop for ChildConnection {
fn drop(&mut self) {
if !self.reaped {
let _ = self.child.kill();
let _ = self.child.wait();
}
}
}