gix_protocol/fetch/response/
mod.rs1use bstr::BString;
2use gix_transport::{client, Protocol};
3
4use crate::{command::Feature, fetch::Response};
5
6#[derive(Debug, thiserror::Error)]
8#[allow(missing_docs)]
9pub enum Error {
10 #[error("Failed to read from line reader")]
11 Io(#[source] std::io::Error),
12 #[error(transparent)]
13 UploadPack(#[from] gix_transport::packetline::read::Error),
14 #[error(transparent)]
15 Transport(#[from] client::Error),
16 #[error("Currently we require feature {feature:?}, which is not supported by the server")]
17 MissingServerCapability { feature: &'static str },
18 #[error("Encountered an unknown line prefix in {line:?}")]
19 UnknownLineType { line: String },
20 #[error("Unknown or unsupported header: {header:?}")]
21 UnknownSectionHeader { header: String },
22}
23
24impl From<std::io::Error> for Error {
25 fn from(err: std::io::Error) -> Self {
26 if err.kind() == std::io::ErrorKind::Other {
27 match err.into_inner() {
28 Some(err) => match err.downcast::<gix_transport::packetline::read::Error>() {
29 Ok(err) => Error::UploadPack(*err),
30 Err(err) => Error::Io(std::io::Error::new(std::io::ErrorKind::Other, err)),
31 },
32 None => Error::Io(std::io::ErrorKind::Other.into()),
33 }
34 } else {
35 Error::Io(err)
36 }
37 }
38}
39
40impl gix_transport::IsSpuriousError for Error {
41 fn is_spurious(&self) -> bool {
42 match self {
43 Error::Io(err) => err.is_spurious(),
44 Error::Transport(err) => err.is_spurious(),
45 _ => false,
46 }
47 }
48}
49
50#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
53pub enum Acknowledgement {
54 Common(gix_hash::ObjectId),
56 Ready,
58 Nak,
60}
61
62pub use gix_shallow::Update as ShallowUpdate;
63
64#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
67pub struct WantedRef {
68 pub id: gix_hash::ObjectId,
70 pub path: BString,
72}
73
74pub fn shallow_update_from_line(line: &str) -> Result<ShallowUpdate, Error> {
76 match line.trim_end().split_once(' ') {
77 Some((prefix, id)) => {
78 let id = gix_hash::ObjectId::from_hex(id.as_bytes())
79 .map_err(|_| Error::UnknownLineType { line: line.to_owned() })?;
80 Ok(match prefix {
81 "shallow" => ShallowUpdate::Shallow(id),
82 "unshallow" => ShallowUpdate::Unshallow(id),
83 _ => return Err(Error::UnknownLineType { line: line.to_owned() }),
84 })
85 }
86 None => Err(Error::UnknownLineType { line: line.to_owned() }),
87 }
88}
89
90impl Acknowledgement {
91 pub fn from_line(line: &str) -> Result<Acknowledgement, Error> {
93 let mut tokens = line.trim_end().splitn(3, ' ');
94 match (tokens.next(), tokens.next(), tokens.next()) {
95 (Some(first), id, description) => Ok(match first {
96 "ready" => Acknowledgement::Ready, "NAK" => Acknowledgement::Nak, "ACK" => {
99 let id = match id {
100 Some(id) => gix_hash::ObjectId::from_hex(id.as_bytes())
101 .map_err(|_| Error::UnknownLineType { line: line.to_owned() })?,
102 None => return Err(Error::UnknownLineType { line: line.to_owned() }),
103 };
104 if let Some(description) = description {
105 match description {
106 "common" => {}
107 "ready" => return Ok(Acknowledgement::Ready),
108 _ => return Err(Error::UnknownLineType { line: line.to_owned() }),
109 }
110 }
111 Acknowledgement::Common(id)
112 }
113 _ => return Err(Error::UnknownLineType { line: line.to_owned() }),
114 }),
115 (None, _, _) => Err(Error::UnknownLineType { line: line.to_owned() }),
116 }
117 }
118 pub fn id(&self) -> Option<&gix_hash::ObjectId> {
120 match self {
121 Acknowledgement::Common(id) => Some(id),
122 _ => None,
123 }
124 }
125}
126
127impl WantedRef {
128 pub fn from_line(line: &str) -> Result<WantedRef, Error> {
130 match line.trim_end().split_once(' ') {
131 Some((id, path)) => {
132 let id = gix_hash::ObjectId::from_hex(id.as_bytes())
133 .map_err(|_| Error::UnknownLineType { line: line.to_owned() })?;
134 Ok(WantedRef { id, path: path.into() })
135 }
136 None => Err(Error::UnknownLineType { line: line.to_owned() }),
137 }
138 }
139}
140
141impl Response {
142 pub fn has_pack(&self) -> bool {
144 self.has_pack
145 }
146
147 pub fn check_required_features(version: Protocol, features: &[Feature]) -> Result<(), Error> {
153 match version {
154 Protocol::V0 | Protocol::V1 => {
155 let has = |name: &str| features.iter().any(|f| f.0 == name);
156 if !has("multi_ack_detailed") {
158 return Err(Error::MissingServerCapability {
159 feature: "multi_ack_detailed",
160 });
161 }
162 if !has("side-band") && !has("side-band-64k") {
167 return Err(Error::MissingServerCapability {
168 feature: "side-band OR side-band-64k",
169 });
170 }
171 }
172 Protocol::V2 => {}
173 }
174 Ok(())
175 }
176
177 pub fn acknowledgements(&self) -> &[Acknowledgement] {
179 &self.acks
180 }
181
182 pub fn shallow_updates(&self) -> &[ShallowUpdate] {
184 &self.shallows
185 }
186
187 pub fn append_v1_shallow_updates(&mut self, updates: Option<Vec<ShallowUpdate>>) {
193 self.shallows.extend(updates.into_iter().flatten());
194 }
195
196 pub fn wanted_refs(&self) -> &[WantedRef] {
198 &self.wanted_refs
199 }
200}
201
202#[cfg(any(feature = "async-client", feature = "blocking-client"))]
203impl Response {
204 fn parse_v1_ack_or_shallow_or_assume_pack(
207 acks: &mut Vec<Acknowledgement>,
208 shallows: &mut Vec<ShallowUpdate>,
209 peeked_line: &str,
210 ) -> bool {
211 match Acknowledgement::from_line(peeked_line) {
212 Ok(ack) => match ack.id() {
213 Some(id) => {
214 if !acks.iter().any(|a| a.id() == Some(id)) {
215 acks.push(ack);
216 }
217 }
218 None => acks.push(ack),
219 },
220 Err(_) => match shallow_update_from_line(peeked_line) {
221 Ok(shallow) => {
222 shallows.push(shallow);
223 }
224 Err(_) => return true,
225 },
226 }
227 false
228 }
229}
230
231#[cfg(feature = "async-client")]
232mod async_io;
233#[cfg(feature = "blocking-client")]
234mod blocking_io;