use std::sync::atomic::AtomicBool;
use super::{PreparedPush, PushError, PushFailure, PushReport, protocol};
use crate::packet::Wire;
use crate::transport::TransportControl;
use crate::transport::ssh::SshRemote;
pub async fn send_ssh(
remote: &SshRemote,
prepared: &PreparedPush,
control: TransportControl<'_>,
) -> Result<PushReport, PushError> {
#[cfg(feature = "tracing")]
let span = tracing::debug_span!(
target: "girt",
"push.ssh",
outcome = "incomplete",
failure_class = tracing::field::Empty,
effects = tracing::field::Empty,
accepted = tracing::field::Empty,
rejected = tracing::field::Empty,
pending = tracing::field::Empty,
unpack = tracing::field::Empty,
);
let operation = async {
let mut session = remote
.connect("git-receive-pack", control)
.map_err(|e| PushError::NotSent(e.into()))?;
let preflight = async {
let bytes = session
.advertise(prepared.limits.max_advertisement_bytes, control)
.await?;
let mut reader = bytes.as_slice();
let mut wire = Wire {
reader: &mut reader,
remaining: prepared.limits.max_advertisement_bytes,
cancel: control.cancel,
};
let caps = protocol::advertise(&mut wire, prepared)?;
wire.end()?;
control.check()?;
Ok::<_, PushFailure>(caps)
};
let caps = preflight.await.map_err(PushError::NotSent)?;
let negotiated = prepared
.request_for(caps.report_v2, caps.sideband)
.map_err(PushError::NotSent)?;
let mut report = PushReport::pending(&prepared.commands);
let request = if prepared.commands.is_empty() {
b"0000"
} else {
negotiated.as_ref()
};
let (body, result, written) = session
.exchange(
request,
&prepared.pack,
prepared.limits.max_status_bytes,
control,
)
.await;
if prepared.commands.is_empty() {
result.map_err(|e| PushError::NotSent(e.into()))?;
if !body.is_empty() {
return Err(PushError::NotSent(PushFailure::Protocol(
"trailing response bytes",
)));
}
return Ok(report);
}
if written == 0
&& let Err(cause) = result
{
return Err(PushError::NotSent(cause.into()));
}
report.mark_attempted(request, written);
let retain = AtomicBool::new(false);
let mut reader = body.as_slice();
let mut wire = Wire {
reader: &mut reader,
remaining: prepared.limits.max_status_bytes,
cancel: &retain,
};
let parsed = protocol::read_response(
&mut wire,
&mut report,
caps,
prepared.limits.max_status_bytes,
)
.and_then(|()| wire.end().map_err(PushFailure::from));
match result.map_err(PushFailure::from).and(parsed) {
Ok(()) => Ok(report),
Err(cause) => Err(PushError::Uncertain {
cause,
report: Box::new(report),
}),
}
};
#[cfg(feature = "tracing")]
let result = tracing::Instrument::instrument(operation, span.clone()).await;
#[cfg(not(feature = "tracing"))]
let result = operation.await;
#[cfg(feature = "tracing")]
crate::trace::finish(&span, &result, |error| crate::trace::push(error, &span));
#[cfg(feature = "tracing")]
if let Ok(report) = &result {
crate::trace::push_report(&span, report);
}
result
}