History

[git-protocol] refactor

Sebastian Thiel committed 5 years ago

Commit d623cf71 parent c4985574 files · +443 −446Browse files

Showing the changes to git-protocol/src/fetch (4 of 4 files). Show all

Unified · Split

4 files
  1. git-protocol/src/fetch/response.rs +0 −446
  2. git-protocol/src/fetch/response/async_io.rs +126 −0
  3. git-protocol/src/fetch/response/blocking_io.rs +125 −0
  4. git-protocol/src/fetch/response/mod.rs +192 −0
git-protocol/src/fetch/response.rs+0−446
@@ -1,446 +0,0 @@
1use crate::fetch::command::Feature;
2use git_transport::{client, Protocol};
3use quick_error::quick_error;
4use std::{borrow::Cow, io};
5
6quick_error! {
7 /// The error used in the [response module][crate::fetch::response].
8 #[derive(Debug)]
9 #[allow(missing_docs)]
10 pub enum Error {
11 Io(err: io::Error) {
12 display("Failed to read from line reader")
13 from()
14 source(err)
15 }
16 Transport(err: client::Error) {
17 display("An error occurred when decoding a line")
18 from()
19 source(err)
20 }
21 MissingServerCapability(feature: &'static str) {
22 display("Currently we require feature '{}', which is not supported by the server", feature)
23 }
24 UnknownLineType(line: String) {
25 display("Encountered an unknown line prefix in '{}'", line)
26 }
27 UnknownSectionHeader(header: String) {
28 display("Unknown or unsupported header: '{}'", header)
29 }
30 }
31}
32
33/// An 'ACK' line received from the server.
34#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
35#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
36pub enum Acknowledgement {
37 /// The contained `id` is in common.
38 Common(git_hash::ObjectId),
39 /// The server is ready to receive more lines.
40 Ready,
41 /// The server isn't ready yet.
42 Nak,
43}
44
45/// A shallow line received from the server.
46#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
47#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
48pub enum ShallowUpdate {
49 /// Shallow the given `id`.
50 Shallow(git_hash::ObjectId),
51 /// Don't shallow the given `id` anymore.
52 Unshallow(git_hash::ObjectId),
53}
54
55impl ShallowUpdate {
56 /// Parse a `ShallowUpdate` from a `line` as received to the server.
57 fn from_line(line: &str) -> Result<ShallowUpdate, Error> {
58 let mut tokens = line.trim_end().splitn(2, ' ');
59 match (tokens.next(), tokens.next()) {
60 (Some(prefix), Some(id)) => {
61 let id =
62 git_hash::ObjectId::from_hex(id.as_bytes()).map_err(|_| Error::UnknownLineType(line.to_owned()))?;
63 Ok(match prefix {
64 "shallow" => ShallowUpdate::Shallow(id),
65 "unshallow" => ShallowUpdate::Unshallow(id),
66 _ => return Err(Error::UnknownLineType(line.to_owned())),
67 })
68 }
69 _ => unreachable!("cannot have an entirely empty line"),
70 }
71 }
72}
73
74impl Acknowledgement {
75 /// Parse an `Acknowledgement` from a `line` as received to the server.
76 fn from_line(line: &str) -> Result<Acknowledgement, Error> {
77 let mut tokens = line.trim_end().splitn(3, ' ');
78 Ok(match (tokens.next(), tokens.next(), tokens.next()) {
79 (Some(first), id, description) => match first {
80 "ready" => Acknowledgement::Ready, // V2
81 "NAK" => Acknowledgement::Nak, // V1
82 "ACK" => {
83 let id = match id {
84 Some(id) => git_hash::ObjectId::from_hex(id.as_bytes())
85 .map_err(|_| Error::UnknownLineType(line.to_owned()))?,
86 None => return Err(Error::UnknownLineType(line.to_owned())),
87 };
88 if let Some(description) = description {
89 match description {
90 "common" => {}
91 "ready" => return Ok(Acknowledgement::Ready),
92 _ => return Err(Error::UnknownLineType(line.to_owned())),
93 }
94 }
95 Acknowledgement::Common(id)
96 }
97 _ => return Err(Error::UnknownLineType(line.to_owned())),
98 },
99 (None, _, _) => unreachable!("cannot have an entirely empty line"),
100 })
101 }
102 /// Returns the hash of the acknowledged object if this instance acknowledges a common one.
103 pub fn id(&self) -> Option<&git_hash::ObjectId> {
104 match self {
105 Acknowledgement::Common(id) => Some(id),
106 _ => None,
107 }
108 }
109}
110
111/// A representation of a complete fetch response
112pub struct Response {
113 acks: Vec<Acknowledgement>,
114 shallows: Vec<ShallowUpdate>,
115 has_pack: bool,
116}
117
118impl Response {
119 /// Return true if the response has a pack which can be read next.
120 pub fn has_pack(&self) -> bool {
121 self.has_pack
122 }
123
124 /// Return an error if the given `features` don't contain the required ones for the given `version` of the protocol.
125 ///
126 /// Even though technically any set of features supported by the server could work, we only implement the ones that
127 /// make it easy to maintain all versions with a single code base that aims to be and remain maintainable.
128 pub fn check_required_features(version: Protocol, features: &[Feature]) -> Result<(), Error> {
129 match version {
130 Protocol::V1 => {
131 let has = |name: &str| features.iter().any(|f| f.0 == name);
132 // Let's focus on V2 standards, and simply not support old servers to keep our code simpler
133 if !has("multi_ack_detailed") {
134 return Err(Error::MissingServerCapability("multi_ack_detailed"));
135 }
136 // It's easy to NOT do sideband for us, but then again, everyone supports it.
137 // CORRECTION: If side-band is off, it would send the packfile without packet line encoding,
138 // which is nothing we ever want to deal with (despite it being more efficient). In V2, this
139 // is not even an option anymore, sidebands are always present.
140 if !has("side-band") && !has("side-band-64k") {
141 return Err(Error::MissingServerCapability("side-band OR side-band-64k"));
142 }
143 }
144 Protocol::V2 => {}
145 }
146 Ok(())
147 }
148
149 /// Return all acknowledgements [parsed previously][Response::from_line_reader()].
150 pub fn acknowledgements(&self) -> &[Acknowledgement] {
151 &self.acks
152 }
153
154 /// Return all shallow update lines [parsed previously][Response::from_line_reader()].
155 pub fn shallow_updates(&self) -> &[ShallowUpdate] {
156 &self.shallows
157 }
158}
159
160#[cfg(feature = "blocking-client")]
161mod blocking_io {
162 use crate::fetch::{
163 response,
164 response::{Acknowledgement, ShallowUpdate},
165 Response,
166 };
167 use git_transport::{client, Protocol};
168 use std::io;
169
170 fn parse_v2_section<T>(
171 line: &mut String,
172 reader: &mut impl client::ExtendedBufRead,
173 res: &mut Vec<T>,
174 parse: impl Fn(&str) -> Result<T, response::Error>,
175 ) -> Result<bool, response::Error> {
176 line.clear();
177 while reader.read_line(line)? != 0 {
178 res.push(parse(line)?);
179 line.clear();
180 }
181 // End of message, or end of section?
182 Ok(if reader.stopped_at() == Some(client::MessageKind::Delimiter) {
183 // try reading more sections
184 reader.reset(Protocol::V2);
185 false
186 } else {
187 // we are done, there is no pack
188 true
189 })
190 }
191
192 impl Response {
193 /// Parse a response of the given `version` of the protocol from `reader`.
194 pub fn from_line_reader(
195 version: Protocol,
196 reader: &mut impl client::ExtendedBufRead,
197 ) -> Result<Response, response::Error> {
198 match version {
199 Protocol::V1 => {
200 let mut line = String::new();
201 let mut acks = Vec::<Acknowledgement>::new();
202 let mut shallows = Vec::<ShallowUpdate>::new();
203 let has_pack = 'lines: loop {
204 line.clear();
205 let peeked_line = match reader.peek_data_line() {
206 Some(Ok(Ok(line))) => String::from_utf8_lossy(line),
207 // This special case (hang/block forver) deals with a single NAK being a legitimate EOF sometimes
208 // Note that this might block forever in stateful connections as there it's not really clear
209 // if something will be following or not by just looking at the response. Instead you have to know
210 // the arguments sent to the server and count response lines based on intricate knowledge on how the
211 // server works.
212 // For now this is acceptable, as V2 can be used as a workaround, which also is the default.
213 Some(Err(err)) if err.kind() == io::ErrorKind::UnexpectedEof => break 'lines false,
214 Some(Err(err)) => return Err(err.into()),
215 Some(Ok(Err(err))) => return Err(err.into()),
216 None => {
217 // maybe we saw a shallow flush packet, let's reset and retry
218 debug_assert_eq!(
219 reader.stopped_at(),
220 Some(client::MessageKind::Flush),
221 "If this isn't a flush packet, we don't know what's going on"
222 );
223 reader.read_line(&mut line)?;
224 reader.reset(Protocol::V1);
225 match reader.peek_data_line() {
226 Some(Ok(Ok(line))) => String::from_utf8_lossy(line),
227 Some(Err(err)) => return Err(err.into()),
228 Some(Ok(Err(err))) => return Err(err.into()),
229 None => break 'lines false, // EOF
230 }
231 }
232 };
233
234 if Response::parse_v1_ack_or_shallow_or_assume_pack(&mut acks, &mut shallows, &peeked_line) {
235 break 'lines true;
236 }
237 assert_ne!(reader.read_line(&mut line)?, 0, "consuming a peeked line works");
238 };
239 Ok(Response {
240 acks,
241 shallows,
242 has_pack,
243 })
244 }
245 Protocol::V2 => {
246 // NOTE: We only read acknowledgements and scrub to the pack file, until we have use for the other features
247 let mut line = String::new();
248 reader.reset(Protocol::V2);
249 let mut acks = Vec::<Acknowledgement>::new();
250 let mut shallows = Vec::<ShallowUpdate>::new();
251 let has_pack = 'section: loop {
252 line.clear();
253 if reader.read_line(&mut line)? == 0 {
254 return Err(response::Error::Io(io::Error::new(
255 io::ErrorKind::UnexpectedEof,
256 "Could not read message headline",
257 )));
258 };
259
260 match line.trim_end() {
261 "acknowledgments" => {
262 if parse_v2_section(&mut line, reader, &mut acks, Acknowledgement::from_line)? {
263 break 'section false;
264 }
265 }
266 "shallow-info" => {
267 if parse_v2_section(&mut line, reader, &mut shallows, ShallowUpdate::from_line)? {
268 break 'section false;
269 }
270 }
271 "packfile" => {
272 // what follows is the packfile itself, which can be read with a sideband enabled reader
273 break 'section true;
274 }
275 _ => return Err(response::Error::UnknownSectionHeader(line)),
276 }
277 };
278 Ok(Response {
279 acks,
280 shallows,
281 has_pack,
282 })
283 }
284 }
285 }
286 }
287}
288
289#[cfg(all(not(feature = "blocking-client"), feature = "async-client"))]
290mod async_io {
291 use crate::fetch::{
292 response,
293 response::{Acknowledgement, ShallowUpdate},
294 Response,
295 };
296 use futures_lite::AsyncBufReadExt;
297 use git_transport::{client, Protocol};
298 use std::io;
299
300 async fn parse_v2_section<T>(
301 line: &mut String,
302 reader: &mut (impl client::ExtendedBufRead + Unpin),
303 res: &mut Vec<T>,
304 parse: impl Fn(&str) -> Result<T, response::Error>,
305 ) -> Result<bool, response::Error> {
306 line.clear();
307 while reader.read_line(line).await? != 0 {
308 res.push(parse(line)?);
309 line.clear();
310 }
311 // End of message, or end of section?
312 Ok(if reader.stopped_at() == Some(client::MessageKind::Delimiter) {
313 // try reading more sections
314 reader.reset(Protocol::V2);
315 false
316 } else {
317 // we are done, there is no pack
318 true
319 })
320 }
321
322 impl Response {
323 /// Parse a response of the given `version` of the protocol from `reader`.
324 pub async fn from_line_reader(
325 version: Protocol,
326 reader: &mut (impl client::ExtendedBufRead + Unpin),
327 ) -> Result<Response, response::Error> {
328 match version {
329 Protocol::V1 => {
330 let mut line = String::new();
331 let mut acks = Vec::<Acknowledgement>::new();
332 let mut shallows = Vec::<ShallowUpdate>::new();
333 let has_pack = 'lines: loop {
334 line.clear();
335 let peeked_line = match reader.peek_data_line().await {
336 Some(Ok(Ok(line))) => String::from_utf8_lossy(line),
337 // This special case (hang/block forver) deals with a single NAK being a legitimate EOF sometimes
338 // Note that this might block forever in stateful connections as there it's not really clear
339 // if something will be following or not by just looking at the response. Instead you have to know
340 // the arguments sent to the server and count response lines based on intricate knowledge on how the
341 // server works.
342 // For now this is acceptable, as V2 can be used as a workaround, which also is the default.
343 Some(Err(err)) if err.kind() == io::ErrorKind::UnexpectedEof => break 'lines false,
344 Some(Err(err)) => return Err(err.into()),
345 Some(Ok(Err(err))) => return Err(err.into()),
346 None => {
347 // maybe we saw a shallow flush packet, let's reset and retry
348 debug_assert_eq!(
349 reader.stopped_at(),
350 Some(client::MessageKind::Flush),
351 "If this isn't a flush packet, we don't know what's going on"
352 );
353 reader.read_line(&mut line).await?;
354 reader.reset(Protocol::V1);
355 match reader.peek_data_line().await {
356 Some(Ok(Ok(line))) => String::from_utf8_lossy(line),
357 Some(Err(err)) => return Err(err.into()),
358 Some(Ok(Err(err))) => return Err(err.into()),
359 None => break 'lines false, // EOF
360 }
361 }
362 };
363
364 if Response::parse_v1_ack_or_shallow_or_assume_pack(&mut acks, &mut shallows, &peeked_line) {
365 break 'lines true;
366 }
367 assert_ne!(reader.read_line(&mut line).await?, 0, "consuming a peeked line works");
368 };
369 Ok(Response {
370 acks,
371 shallows,
372 has_pack,
373 })
374 }
375 Protocol::V2 => {
376 // NOTE: We only read acknowledgements and scrub to the pack file, until we have use for the other features
377 let mut line = String::new();
378 reader.reset(Protocol::V2);
379 let mut acks = Vec::<Acknowledgement>::new();
380 let mut shallows = Vec::<ShallowUpdate>::new();
381 let has_pack = 'section: loop {
382 line.clear();
383 if reader.read_line(&mut line).await? == 0 {
384 return Err(response::Error::Io(io::Error::new(
385 io::ErrorKind::UnexpectedEof,
386 "Could not read message headline",
387 )));
388 };
389
390 match line.trim_end() {
391 "acknowledgments" => {
392 if parse_v2_section(&mut line, reader, &mut acks, Acknowledgement::from_line).await? {
393 break 'section false;
394 }
395 }
396 "shallow-info" => {
397 if parse_v2_section(&mut line, reader, &mut shallows, ShallowUpdate::from_line).await? {
398 break 'section false;
399 }
400 }
401 "packfile" => {
402 // what follows is the packfile itself, which can be read with a sideband enabled reader
403 break 'section true;
404 }
405 _ => return Err(response::Error::UnknownSectionHeader(line)),
406 }
407 };
408 Ok(Response {
409 acks,
410 shallows,
411 has_pack,
412 })
413 }
414 }
415 }
416 }
417}
418
419#[cfg(any(feature = "async-client", feature = "blocking-client"))]
420impl Response {
421 /// with a friendly server, we just assume that a non-ack line is a pack line
422 /// which is our hint to stop here.
423 fn parse_v1_ack_or_shallow_or_assume_pack(
424 acks: &mut Vec<Acknowledgement>,
425 shallows: &mut Vec<ShallowUpdate>,
426 peeked_line: &Cow<'_, str>,
427 ) -> bool {
428 match Acknowledgement::from_line(&peeked_line) {
429 Ok(ack) => match ack.id() {
430 Some(id) => {
431 if !acks.iter().any(|a| a.id() == Some(id)) {
432 acks.push(ack);
433 }
434 }
435 None => acks.push(ack),
436 },
437 Err(_) => match ShallowUpdate::from_line(&peeked_line) {
438 Ok(shallow) => {
439 shallows.push(shallow);
440 }
441 Err(_) => return true,
442 },
443 };
444 false
445 }
446}
git-protocol/src/fetch/response/async_io.rs+126−0
@@ -0,0 +1,126 @@
1use crate::fetch::{
2 response,
3 response::{Acknowledgement, ShallowUpdate},
4 Response,
5};
6use futures_lite::AsyncBufReadExt;
7use git_transport::{client, Protocol};
8use std::io;
9
10async fn parse_v2_section<T>(
11 line: &mut String,
12 reader: &mut (impl client::ExtendedBufRead + Unpin),
13 res: &mut Vec<T>,
14 parse: impl Fn(&str) -> Result<T, response::Error>,
15) -> Result<bool, response::Error> {
16 line.clear();
17 while reader.read_line(line).await? != 0 {
18 res.push(parse(line)?);
19 line.clear();
20 }
21 // End of message, or end of section?
22 Ok(if reader.stopped_at() == Some(client::MessageKind::Delimiter) {
23 // try reading more sections
24 reader.reset(Protocol::V2);
25 false
26 } else {
27 // we are done, there is no pack
28 true
29 })
30}
31
32impl Response {
33 /// Parse a response of the given `version` of the protocol from `reader`.
34 pub async fn from_line_reader(
35 version: Protocol,
36 reader: &mut (impl client::ExtendedBufRead + Unpin),
37 ) -> Result<Response, response::Error> {
38 match version {
39 Protocol::V1 => {
40 let mut line = String::new();
41 let mut acks = Vec::<Acknowledgement>::new();
42 let mut shallows = Vec::<ShallowUpdate>::new();
43 let has_pack = 'lines: loop {
44 line.clear();
45 let peeked_line = match reader.peek_data_line().await {
46 Some(Ok(Ok(line))) => String::from_utf8_lossy(line),
47 // This special case (hang/block forver) deals with a single NAK being a legitimate EOF sometimes
48 // Note that this might block forever in stateful connections as there it's not really clear
49 // if something will be following or not by just looking at the response. Instead you have to know
50 // the arguments sent to the server and count response lines based on intricate knowledge on how the
51 // server works.
52 // For now this is acceptable, as V2 can be used as a workaround, which also is the default.
53 Some(Err(err)) if err.kind() == io::ErrorKind::UnexpectedEof => break 'lines false,
54 Some(Err(err)) => return Err(err.into()),
55 Some(Ok(Err(err))) => return Err(err.into()),
56 None => {
57 // maybe we saw a shallow flush packet, let's reset and retry
58 debug_assert_eq!(
59 reader.stopped_at(),
60 Some(client::MessageKind::Flush),
61 "If this isn't a flush packet, we don't know what's going on"
62 );
63 reader.read_line(&mut line).await?;
64 reader.reset(Protocol::V1);
65 match reader.peek_data_line().await {
66 Some(Ok(Ok(line))) => String::from_utf8_lossy(line),
67 Some(Err(err)) => return Err(err.into()),
68 Some(Ok(Err(err))) => return Err(err.into()),
69 None => break 'lines false, // EOF
70 }
71 }
72 };
73
74 if Response::parse_v1_ack_or_shallow_or_assume_pack(&mut acks, &mut shallows, &peeked_line) {
75 break 'lines true;
76 }
77 assert_ne!(reader.read_line(&mut line).await?, 0, "consuming a peeked line works");
78 };
79 Ok(Response {
80 acks,
81 shallows,
82 has_pack,
83 })
84 }
85 Protocol::V2 => {
86 // NOTE: We only read acknowledgements and scrub to the pack file, until we have use for the other features
87 let mut line = String::new();
88 reader.reset(Protocol::V2);
89 let mut acks = Vec::<Acknowledgement>::new();
90 let mut shallows = Vec::<ShallowUpdate>::new();
91 let has_pack = 'section: loop {
92 line.clear();
93 if reader.read_line(&mut line).await? == 0 {
94 return Err(response::Error::Io(io::Error::new(
95 io::ErrorKind::UnexpectedEof,
96 "Could not read message headline",
97 )));
98 };
99
100 match line.trim_end() {
101 "acknowledgments" => {
102 if parse_v2_section(&mut line, reader, &mut acks, Acknowledgement::from_line).await? {
103 break 'section false;
104 }
105 }
106 "shallow-info" => {
107 if parse_v2_section(&mut line, reader, &mut shallows, ShallowUpdate::from_line).await? {
108 break 'section false;
109 }
110 }
111 "packfile" => {
112 // what follows is the packfile itself, which can be read with a sideband enabled reader
113 break 'section true;
114 }
115 _ => return Err(response::Error::UnknownSectionHeader(line)),
116 }
117 };
118 Ok(Response {
119 acks,
120 shallows,
121 has_pack,
122 })
123 }
124 }
125 }
126}
git-protocol/src/fetch/response/blocking_io.rs+125−0
@@ -0,0 +1,125 @@
1use crate::fetch::{
2 response,
3 response::{Acknowledgement, ShallowUpdate},
4 Response,
5};
6use git_transport::{client, Protocol};
7use std::io;
8
9fn parse_v2_section<T>(
10 line: &mut String,
11 reader: &mut impl client::ExtendedBufRead,
12 res: &mut Vec<T>,
13 parse: impl Fn(&str) -> Result<T, response::Error>,
14) -> Result<bool, response::Error> {
15 line.clear();
16 while reader.read_line(line)? != 0 {
17 res.push(parse(line)?);
18 line.clear();
19 }
20 // End of message, or end of section?
21 Ok(if reader.stopped_at() == Some(client::MessageKind::Delimiter) {
22 // try reading more sections
23 reader.reset(Protocol::V2);
24 false
25 } else {
26 // we are done, there is no pack
27 true
28 })
29}
30
31impl Response {
32 /// Parse a response of the given `version` of the protocol from `reader`.
33 pub fn from_line_reader(
34 version: Protocol,
35 reader: &mut impl client::ExtendedBufRead,
36 ) -> Result<Response, response::Error> {
37 match version {
38 Protocol::V1 => {
39 let mut line = String::new();
40 let mut acks = Vec::<Acknowledgement>::new();
41 let mut shallows = Vec::<ShallowUpdate>::new();
42 let has_pack = 'lines: loop {
43 line.clear();
44 let peeked_line = match reader.peek_data_line() {
45 Some(Ok(Ok(line))) => String::from_utf8_lossy(line),
46 // This special case (hang/block forver) deals with a single NAK being a legitimate EOF sometimes
47 // Note that this might block forever in stateful connections as there it's not really clear
48 // if something will be following or not by just looking at the response. Instead you have to know
49 // the arguments sent to the server and count response lines based on intricate knowledge on how the
50 // server works.
51 // For now this is acceptable, as V2 can be used as a workaround, which also is the default.
52 Some(Err(err)) if err.kind() == io::ErrorKind::UnexpectedEof => break 'lines false,
53 Some(Err(err)) => return Err(err.into()),
54 Some(Ok(Err(err))) => return Err(err.into()),
55 None => {
56 // maybe we saw a shallow flush packet, let's reset and retry
57 debug_assert_eq!(
58 reader.stopped_at(),
59 Some(client::MessageKind::Flush),
60 "If this isn't a flush packet, we don't know what's going on"
61 );
62 reader.read_line(&mut line)?;
63 reader.reset(Protocol::V1);
64 match reader.peek_data_line() {
65 Some(Ok(Ok(line))) => String::from_utf8_lossy(line),
66 Some(Err(err)) => return Err(err.into()),
67 Some(Ok(Err(err))) => return Err(err.into()),
68 None => break 'lines false, // EOF
69 }
70 }
71 };
72
73 if Response::parse_v1_ack_or_shallow_or_assume_pack(&mut acks, &mut shallows, &peeked_line) {
74 break 'lines true;
75 }
76 assert_ne!(reader.read_line(&mut line)?, 0, "consuming a peeked line works");
77 };
78 Ok(Response {
79 acks,
80 shallows,
81 has_pack,
82 })
83 }
84 Protocol::V2 => {
85 // NOTE: We only read acknowledgements and scrub to the pack file, until we have use for the other features
86 let mut line = String::new();
87 reader.reset(Protocol::V2);
88 let mut acks = Vec::<Acknowledgement>::new();
89 let mut shallows = Vec::<ShallowUpdate>::new();
90 let has_pack = 'section: loop {
91 line.clear();
92 if reader.read_line(&mut line)? == 0 {
93 return Err(response::Error::Io(io::Error::new(
94 io::ErrorKind::UnexpectedEof,
95 "Could not read message headline",
96 )));
97 };
98
99 match line.trim_end() {
100 "acknowledgments" => {
101 if parse_v2_section(&mut line, reader, &mut acks, Acknowledgement::from_line)? {
102 break 'section false;
103 }
104 }
105 "shallow-info" => {
106 if parse_v2_section(&mut line, reader, &mut shallows, ShallowUpdate::from_line)? {
107 break 'section false;
108 }
109 }
110 "packfile" => {
111 // what follows is the packfile itself, which can be read with a sideband enabled reader
112 break 'section true;
113 }
114 _ => return Err(response::Error::UnknownSectionHeader(line)),
115 }
116 };
117 Ok(Response {
118 acks,
119 shallows,
120 has_pack,
121 })
122 }
123 }
124 }
125}
git-protocol/src/fetch/response/mod.rs+192−0
@@ -0,0 +1,192 @@
1use crate::fetch::command::Feature;
2use git_transport::{client, Protocol};
3use quick_error::quick_error;
4use std::{borrow::Cow, io};
5
6quick_error! {
7 /// The error used in the [response module][crate::fetch::response].
8 #[derive(Debug)]
9 #[allow(missing_docs)]
10 pub enum Error {
11 Io(err: io::Error) {
12 display("Failed to read from line reader")
13 from()
14 source(err)
15 }
16 Transport(err: client::Error) {
17 display("An error occurred when decoding a line")
18 from()
19 source(err)
20 }
21 MissingServerCapability(feature: &'static str) {
22 display("Currently we require feature '{}', which is not supported by the server", feature)
23 }
24 UnknownLineType(line: String) {
25 display("Encountered an unknown line prefix in '{}'", line)
26 }
27 UnknownSectionHeader(header: String) {
28 display("Unknown or unsupported header: '{}'", header)
29 }
30 }
31}
32
33/// An 'ACK' line received from the server.
34#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
35#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
36pub enum Acknowledgement {
37 /// The contained `id` is in common.
38 Common(git_hash::ObjectId),
39 /// The server is ready to receive more lines.
40 Ready,
41 /// The server isn't ready yet.
42 Nak,
43}
44
45/// A shallow line received from the server.
46#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
47#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
48pub enum ShallowUpdate {
49 /// Shallow the given `id`.
50 Shallow(git_hash::ObjectId),
51 /// Don't shallow the given `id` anymore.
52 Unshallow(git_hash::ObjectId),
53}
54
55impl ShallowUpdate {
56 /// Parse a `ShallowUpdate` from a `line` as received to the server.
57 fn from_line(line: &str) -> Result<ShallowUpdate, Error> {
58 let mut tokens = line.trim_end().splitn(2, ' ');
59 match (tokens.next(), tokens.next()) {
60 (Some(prefix), Some(id)) => {
61 let id =
62 git_hash::ObjectId::from_hex(id.as_bytes()).map_err(|_| Error::UnknownLineType(line.to_owned()))?;
63 Ok(match prefix {
64 "shallow" => ShallowUpdate::Shallow(id),
65 "unshallow" => ShallowUpdate::Unshallow(id),
66 _ => return Err(Error::UnknownLineType(line.to_owned())),
67 })
68 }
69 _ => unreachable!("cannot have an entirely empty line"),
70 }
71 }
72}
73
74impl Acknowledgement {
75 /// Parse an `Acknowledgement` from a `line` as received to the server.
76 fn from_line(line: &str) -> Result<Acknowledgement, Error> {
77 let mut tokens = line.trim_end().splitn(3, ' ');
78 Ok(match (tokens.next(), tokens.next(), tokens.next()) {
79 (Some(first), id, description) => match first {
80 "ready" => Acknowledgement::Ready, // V2
81 "NAK" => Acknowledgement::Nak, // V1
82 "ACK" => {
83 let id = match id {
84 Some(id) => git_hash::ObjectId::from_hex(id.as_bytes())
85 .map_err(|_| Error::UnknownLineType(line.to_owned()))?,
86 None => return Err(Error::UnknownLineType(line.to_owned())),
87 };
88 if let Some(description) = description {
89 match description {
90 "common" => {}
91 "ready" => return Ok(Acknowledgement::Ready),
92 _ => return Err(Error::UnknownLineType(line.to_owned())),
93 }
94 }
95 Acknowledgement::Common(id)
96 }
97 _ => return Err(Error::UnknownLineType(line.to_owned())),
98 },
99 (None, _, _) => unreachable!("cannot have an entirely empty line"),
100 })
101 }
102 /// Returns the hash of the acknowledged object if this instance acknowledges a common one.
103 pub fn id(&self) -> Option<&git_hash::ObjectId> {
104 match self {
105 Acknowledgement::Common(id) => Some(id),
106 _ => None,
107 }
108 }
109}
110
111/// A representation of a complete fetch response
112pub struct Response {
113 acks: Vec<Acknowledgement>,
114 shallows: Vec<ShallowUpdate>,
115 has_pack: bool,
116}
117
118impl Response {
119 /// Return true if the response has a pack which can be read next.
120 pub fn has_pack(&self) -> bool {
121 self.has_pack
122 }
123
124 /// Return an error if the given `features` don't contain the required ones for the given `version` of the protocol.
125 ///
126 /// Even though technically any set of features supported by the server could work, we only implement the ones that
127 /// make it easy to maintain all versions with a single code base that aims to be and remain maintainable.
128 pub fn check_required_features(version: Protocol, features: &[Feature]) -> Result<(), Error> {
129 match version {
130 Protocol::V1 => {
131 let has = |name: &str| features.iter().any(|f| f.0 == name);
132 // Let's focus on V2 standards, and simply not support old servers to keep our code simpler
133 if !has("multi_ack_detailed") {
134 return Err(Error::MissingServerCapability("multi_ack_detailed"));
135 }
136 // It's easy to NOT do sideband for us, but then again, everyone supports it.
137 // CORRECTION: If side-band is off, it would send the packfile without packet line encoding,
138 // which is nothing we ever want to deal with (despite it being more efficient). In V2, this
139 // is not even an option anymore, sidebands are always present.
140 if !has("side-band") && !has("side-band-64k") {
141 return Err(Error::MissingServerCapability("side-band OR side-band-64k"));
142 }
143 }
144 Protocol::V2 => {}
145 }
146 Ok(())
147 }
148
149 /// Return all acknowledgements [parsed previously][Response::from_line_reader()].
150 pub fn acknowledgements(&self) -> &[Acknowledgement] {
151 &self.acks
152 }
153
154 /// Return all shallow update lines [parsed previously][Response::from_line_reader()].
155 pub fn shallow_updates(&self) -> &[ShallowUpdate] {
156 &self.shallows
157 }
158}
159
160#[cfg(any(feature = "async-client", feature = "blocking-client"))]
161impl Response {
162 /// with a friendly server, we just assume that a non-ack line is a pack line
163 /// which is our hint to stop here.
164 fn parse_v1_ack_or_shallow_or_assume_pack(
165 acks: &mut Vec<Acknowledgement>,
166 shallows: &mut Vec<ShallowUpdate>,
167 peeked_line: &Cow<'_, str>,
168 ) -> bool {
169 match Acknowledgement::from_line(&peeked_line) {
170 Ok(ack) => match ack.id() {
171 Some(id) => {
172 if !acks.iter().any(|a| a.id() == Some(id)) {
173 acks.push(ack);
174 }
175 }
176 None => acks.push(ack),
177 },
178 Err(_) => match ShallowUpdate::from_line(&peeked_line) {
179 Ok(shallow) => {
180 shallows.push(shallow);
181 }
182 Err(_) => return true,
183 },
184 };
185 false
186 }
187}
188
189#[cfg(all(not(feature = "blocking-client"), feature = "async-client"))]
190mod async_io;
191#[cfg(feature = "blocking-client")]
192mod blocking_io;

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t