use std::sync::atomic::AtomicBool;
use super::{PreparedPush, PushAdvertisement, PushError, PushFailure, PushReport, protocol};
use crate::packet::Wire;
use crate::transport::TransportControl;
use crate::transport::http::{HttpRemote, RequestBody};
#[derive(Debug)]
pub enum HttpPushOutcome {
Declined,
Sent(PushReport),
}
pub async fn send_http(
remote: &HttpRemote,
prepared: PreparedPush,
control: TransportControl<'_>,
) -> Result<PushReport, PushError> {
match send_http_checked(remote, prepared, control, |_| true).await? {
HttpPushOutcome::Sent(report) => Ok(report),
HttpPushOutcome::Declined => unreachable!("unconditional HTTP push cannot decline"),
}
}
pub async fn send_http_checked(
remote: &HttpRemote,
mut prepared: PreparedPush,
control: TransportControl<'_>,
should_send: impl FnOnce(&PushAdvertisement) -> bool,
) -> Result<HttpPushOutcome, PushError> {
#[cfg(feature = "tracing")]
let span = tracing::debug_span!(
target: "girt",
"push.http",
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 preflight = async {
control.check()?;
let (bytes, _) = remote
.discover(
"git-receive-pack",
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, advertisement) = protocol::advertise_with_refs(&mut wire, &prepared)?;
wire.end()?;
control.check()?;
Ok::<_, PushFailure>((caps, advertisement))
};
let (caps, advertisement) = preflight.await.map_err(PushError::NotSent)?;
if !should_send(&advertisement) {
return Ok(HttpPushOutcome::Declined);
}
if caps.report_v2 || caps.sideband {
prepared.request = prepared
.request_for(caps.report_v2, caps.sideband)
.map_err(PushError::NotSent)?
.into_owned();
}
let mut report = PushReport::pending(&prepared.commands);
if prepared.commands.is_empty() {
return Ok(HttpPushOutcome::Sent(report));
}
report.mark_attempted(&prepared.request, prepared.request.len());
let body = RequestBody::new(prepared.request, prepared.pack);
control.check().map_err(|e| PushError::NotSent(e.into()))?;
let response = remote
.exchange(
"git-receive-pack",
Some(body),
prepared.limits.max_status_bytes,
control,
)
.await;
let retain = AtomicBool::new(false);
let mut reader = response.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));
let result = response.result.map_err(PushFailure::from).and(parsed);
match result {
Ok(()) => Ok(HttpPushOutcome::Sent(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(HttpPushOutcome::Sent(report)) = &result {
crate::trace::push_report(&span, report);
}
result
}