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 (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 @@
1−use crate::fetch::command::Feature;
2−use git_transport::{client, Protocol};
3−use quick_error::quick_error;
4−use std::{borrow::Cow, io};
5−
6−quick_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))]
36−pub 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))]
48−pub 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−
55−impl 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−
74−impl 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
112−pub struct Response {
113− acks: Vec<Acknowledgement>,
114− shallows: Vec<ShallowUpdate>,
115− has_pack: bool,
116−}
117−
118−impl 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")]
161−mod 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"))]
290−mod 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"))]
420−impl 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 @@
1+use crate::fetch::{
2+ response,
3+ response::{Acknowledgement, ShallowUpdate},
4+ Response,
5+};
6+use futures_lite::AsyncBufReadExt;
7+use git_transport::{client, Protocol};
8+use std::io;
9+
10+async 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+
32+impl 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 @@
1+use crate::fetch::{
2+ response,
3+ response::{Acknowledgement, ShallowUpdate},
4+ Response,
5+};
6+use git_transport::{client, Protocol};
7+use std::io;
8+
9+fn 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+
31+impl 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 @@
1+use crate::fetch::command::Feature;
2+use git_transport::{client, Protocol};
3+use quick_error::quick_error;
4+use std::{borrow::Cow, io};
5+
6+quick_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))]
36+pub 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))]
48+pub 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+
55+impl 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+
74+impl 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
112+pub struct Response {
113+ acks: Vec<Acknowledgement>,
114+ shallows: Vec<ShallowUpdate>,
115+ has_pack: bool,
116+}
117+
118+impl 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"))]
161+impl 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"))]
190+mod async_io;
191+#[cfg(feature = "blocking-client")]
192+mod blocking_io;