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