gix_protocol/fetch/response/
mod.rs

1use bstr::BString;
2use gix_transport::{client, Protocol};
3
4use crate::command::Feature;
5use crate::fetch::Response;
6
7/// The error returned in the [response module][crate::fetch::response].
8#[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/// An 'ACK' line received from the server.
52#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
54pub enum Acknowledgement {
55    /// The contained `id` is in common.
56    Common(gix_hash::ObjectId),
57    /// The server is ready to receive more lines.
58    Ready,
59    /// The server isn't ready yet.
60    Nak,
61}
62
63pub use gix_shallow::Update as ShallowUpdate;
64
65/// A wanted-ref line received from the server.
66#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
68pub struct WantedRef {
69    /// The object id of the wanted ref, as seen by the server.
70    pub id: gix_hash::ObjectId,
71    /// The name of the ref, as requested by the client as a `want-ref` argument.
72    pub path: BString,
73}
74
75/// Parse a `ShallowUpdate` from a `line` as received to the server.
76pub 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    /// Parse an `Acknowledgement` from a `line` as received to the server.
93    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, // V2
98                "NAK" => Acknowledgement::Nak,     // V1
99                "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    /// Returns the hash of the acknowledged object if this instance acknowledges a common one.
120    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    /// Parse a `WantedRef` from a `line` as received from the server.
130    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    /// Return true if the response has a pack which can be read next.
144    pub fn has_pack(&self) -> bool {
145        self.has_pack
146    }
147
148    /// Return an error if the given `features` don't contain the required ones (the ones this implementation needs)
149    /// for the given `version` of the protocol.
150    ///
151    /// Even though technically any set of features supported by the server could work, we only implement the ones that
152    /// make it easy to maintain all versions with a single code base that aims to be and remain maintainable.
153    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                // Let's focus on V2 standards, and simply not support old servers to keep our code simpler
158                if !has("multi_ack_detailed") {
159                    return Err(Error::MissingServerCapability {
160                        feature: "multi_ack_detailed",
161                    });
162                }
163                // It's easy to NOT do sideband for us, but then again, everyone supports it.
164                // CORRECTION: If side-band is off, it would send the packfile without packet line encoding,
165                // which is nothing we ever want to deal with (despite it being more efficient). In V2, this
166                // is not even an option anymore, sidebands are always present.
167                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    /// Return all acknowledgements [parsed previously][Response::from_line_reader()].
179    pub fn acknowledgements(&self) -> &[Acknowledgement] {
180        &self.acks
181    }
182
183    /// Return all shallow update lines [parsed previously][Response::from_line_reader()].
184    pub fn shallow_updates(&self) -> &[ShallowUpdate] {
185        &self.shallows
186    }
187
188    /// Append the given `updates` which may have been obtained from a
189    /// (handshake::Outcome)[crate::handshake::Outcome::v1_shallow_updates].
190    ///
191    /// In V2, these are received as part of the pack, but V1 sends them early, so we
192    /// offer to re-integrate them here.
193    pub fn append_v1_shallow_updates(&mut self, updates: Option<Vec<ShallowUpdate>>) {
194        self.shallows.extend(updates.into_iter().flatten());
195    }
196
197    /// Return all wanted-refs [parsed previously][Response::from_line_reader()].
198    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    /// with a friendly server, we just assume that a non-ack line is a pack line
206    /// which is our hint to stop here.
207    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;