use bstr::BString;
use gix_transport::{client, Protocol};
use crate::command::Feature;
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error("Failed to read from line reader")]
Io(#[source] std::io::Error),
#[error(transparent)]
UploadPack(#[from] gix_transport::packetline::read::Error),
#[error(transparent)]
Transport(#[from] client::Error),
#[error("Currently we require feature {feature:?}, which is not supported by the server")]
MissingServerCapability { feature: &'static str },
#[error("Encountered an unknown line prefix in {line:?}")]
UnknownLineType { line: String },
#[error("Unknown or unsupported header: {header:?}")]
UnknownSectionHeader { header: String },
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
if err.kind() == std::io::ErrorKind::Other {
match err.into_inner() {
Some(err) => match err.downcast::<gix_transport::packetline::read::Error>() {
Ok(err) => Error::UploadPack(*err),
Err(err) => Error::Io(std::io::Error::new(std::io::ErrorKind::Other, err)),
},
None => Error::Io(std::io::ErrorKind::Other.into()),
}
} else {
Error::Io(err)
}
}
}
impl gix_transport::IsSpuriousError for Error {
fn is_spurious(&self) -> bool {
match self {
Error::Io(err) => err.is_spurious(),
Error::Transport(err) => err.is_spurious(),
_ => false,
}
}
}
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Acknowledgement {
Common(gix_hash::ObjectId),
Ready,
Nak,
}
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ShallowUpdate {
Shallow(gix_hash::ObjectId),
Unshallow(gix_hash::ObjectId),
}
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct WantedRef {
pub id: gix_hash::ObjectId,
pub path: BString,
}
impl ShallowUpdate {
pub fn from_line(line: &str) -> Result<ShallowUpdate, Error> {
match line.trim_end().split_once(' ') {
Some((prefix, id)) => {
let id = gix_hash::ObjectId::from_hex(id.as_bytes())
.map_err(|_| Error::UnknownLineType { line: line.to_owned() })?;
Ok(match prefix {
"shallow" => ShallowUpdate::Shallow(id),
"unshallow" => ShallowUpdate::Unshallow(id),
_ => return Err(Error::UnknownLineType { line: line.to_owned() }),
})
}
None => Err(Error::UnknownLineType { line: line.to_owned() }),
}
}
}
impl Acknowledgement {
pub fn from_line(line: &str) -> Result<Acknowledgement, Error> {
let mut tokens = line.trim_end().splitn(3, ' ');
match (tokens.next(), tokens.next(), tokens.next()) {
(Some(first), id, description) => Ok(match first {
"ready" => Acknowledgement::Ready, "NAK" => Acknowledgement::Nak, "ACK" => {
let id = match id {
Some(id) => gix_hash::ObjectId::from_hex(id.as_bytes())
.map_err(|_| Error::UnknownLineType { line: line.to_owned() })?,
None => return Err(Error::UnknownLineType { line: line.to_owned() }),
};
if let Some(description) = description {
match description {
"common" => {}
"ready" => return Ok(Acknowledgement::Ready),
_ => return Err(Error::UnknownLineType { line: line.to_owned() }),
}
}
Acknowledgement::Common(id)
}
_ => return Err(Error::UnknownLineType { line: line.to_owned() }),
}),
(None, _, _) => Err(Error::UnknownLineType { line: line.to_owned() }),
}
}
pub fn id(&self) -> Option<&gix_hash::ObjectId> {
match self {
Acknowledgement::Common(id) => Some(id),
_ => None,
}
}
}
impl WantedRef {
pub fn from_line(line: &str) -> Result<WantedRef, Error> {
match line.trim_end().split_once(' ') {
Some((id, path)) => {
let id = gix_hash::ObjectId::from_hex(id.as_bytes())
.map_err(|_| Error::UnknownLineType { line: line.to_owned() })?;
Ok(WantedRef { id, path: path.into() })
}
None => Err(Error::UnknownLineType { line: line.to_owned() }),
}
}
}
#[derive(Debug)]
pub struct Response {
acks: Vec<Acknowledgement>,
shallows: Vec<ShallowUpdate>,
wanted_refs: Vec<WantedRef>,
has_pack: bool,
}
impl Response {
pub fn has_pack(&self) -> bool {
self.has_pack
}
pub fn check_required_features(version: Protocol, features: &[Feature]) -> Result<(), Error> {
match version {
Protocol::V0 | Protocol::V1 => {
let has = |name: &str| features.iter().any(|f| f.0 == name);
if !has("multi_ack_detailed") {
return Err(Error::MissingServerCapability {
feature: "multi_ack_detailed",
});
}
if !has("side-band") && !has("side-band-64k") {
return Err(Error::MissingServerCapability {
feature: "side-band OR side-band-64k",
});
}
}
Protocol::V2 => {}
}
Ok(())
}
pub fn acknowledgements(&self) -> &[Acknowledgement] {
&self.acks
}
pub fn shallow_updates(&self) -> &[ShallowUpdate] {
&self.shallows
}
pub fn wanted_refs(&self) -> &[WantedRef] {
&self.wanted_refs
}
}
#[cfg(any(feature = "async-client", feature = "blocking-client"))]
impl Response {
fn parse_v1_ack_or_shallow_or_assume_pack(
acks: &mut Vec<Acknowledgement>,
shallows: &mut Vec<ShallowUpdate>,
peeked_line: &str,
) -> bool {
match Acknowledgement::from_line(peeked_line) {
Ok(ack) => match ack.id() {
Some(id) => {
if !acks.iter().any(|a| a.id() == Some(id)) {
acks.push(ack);
}
}
None => acks.push(ack),
},
Err(_) => match ShallowUpdate::from_line(peeked_line) {
Ok(shallow) => {
shallows.push(shallow);
}
Err(_) => return true,
},
};
false
}
}
#[cfg(feature = "async-client")]
mod async_io;
#[cfg(feature = "blocking-client")]
mod blocking_io;