Skip to content

Commit 6a3c021

Browse files
committed
fix!: make V1 stateless negotations work.
This is done by working around another V1 negotiation oddity by exploiting client-side knowledge. For very old servers, we probably wouldn't be able to do multi-rounds without dead-locking, but with recent-enough (probably 10 years or so) old git servers all should work fine. All this to not actually have to implement the V1 strangeness, allowing our code to work smoothly with all permutations of stateless/stateful connections and V1/V2 interactions, with a single high-level implementation essentially.
1 parent c5dc7b4 commit 6a3c021

File tree

4 files changed

+44
-19
lines changed

4 files changed

+44
-19
lines changed

gix-protocol/src/fetch/response/async_io.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,15 @@ async fn parse_v2_section<T>(
3232

3333
impl Response {
3434
/// Parse a response of the given `version` of the protocol from `reader`.
35+
///
36+
/// `client_expects_pack` is only relevant for V1 stateful connections, and if `false`, causes us to stop parsing when seeing `NAK`,
37+
/// and if `true` we will keep parsing until we get a pack as the client already signalled to the server that it's done.
38+
/// This way of doing things allows us to exploit knowledge about more recent versions of the protocol, which keeps code easier
39+
/// and more localized without having to support all the cruft that there is.
3540
pub async fn from_line_reader(
3641
version: Protocol,
3742
reader: &mut (impl client::ExtendedBufRead + Unpin),
43+
client_expects_pack: bool,
3844
) -> Result<Response, response::Error> {
3945
match version {
4046
Protocol::V0 | Protocol::V1 => {
@@ -48,8 +54,8 @@ impl Response {
4854
// This special case (hang/block forever) deals with a single NAK being a legitimate EOF sometimes
4955
// Note that this might block forever in stateful connections as there it's not really clear
5056
// if something will be following or not by just looking at the response. Instead you have to know
51-
// the arguments sent to the server and count response lines based on intricate knowledge on how the
52-
// server works.
57+
// [a lot](https://github.com/git/git/blob/9e49351c3060e1fa6e0d2de64505b7becf157f28/fetch-pack.c#L583-L594)
58+
// to deal with this correctly.
5359
// For now this is acceptable, as V2 can be used as a workaround, which also is the default.
5460
Some(Err(err)) if err.kind() == io::ErrorKind::UnexpectedEof => break 'lines false,
5561
Some(Err(err)) => return Err(err.into()),
@@ -75,6 +81,9 @@ impl Response {
7581
if Response::parse_v1_ack_or_shallow_or_assume_pack(&mut acks, &mut shallows, &peeked_line) {
7682
break 'lines true;
7783
}
84+
if let Some(Acknowledgement::Nak) = acks.last().filter(|_| !client_expects_pack) {
85+
break 'lines false;
86+
}
7887
assert_ne!(
7988
reader.readline_str(&mut line).await?,
8089
0,

gix-protocol/src/fetch/response/blocking_io.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,15 @@ fn parse_v2_section<T>(
3232

3333
impl Response {
3434
/// Parse a response of the given `version` of the protocol from `reader`.
35+
///
36+
/// `client_expects_pack` is only relevant for V1 stateful connections, and if `false`, causes us to stop parsing when seeing `NAK`,
37+
/// and if `true` we will keep parsing until we get a pack as the client already signalled to the server that it's done.
38+
/// This way of doing things allows us to exploit knowledge about more recent versions of the protocol, which keeps code easier
39+
/// and more localized without having to support all the cruft that there is.
3540
pub fn from_line_reader(
3641
version: Protocol,
3742
reader: &mut impl client::ExtendedBufRead,
43+
client_expects_pack: bool,
3844
) -> Result<Response, response::Error> {
3945
match version {
4046
Protocol::V0 | Protocol::V1 => {
@@ -48,8 +54,8 @@ impl Response {
4854
// This special case (hang/block forever) deals with a single NAK being a legitimate EOF sometimes
4955
// Note that this might block forever in stateful connections as there it's not really clear
5056
// if something will be following or not by just looking at the response. Instead you have to know
51-
// the arguments sent to the server and count response lines based on intricate knowledge on how the
52-
// server works.
57+
// [a lot](https://github.com/git/git/blob/9e49351c3060e1fa6e0d2de64505b7becf157f28/fetch-pack.c#L583-L594)
58+
// to deal with this correctly.
5359
// For now this is acceptable, as V2 can be used as a workaround, which also is the default.
5460
Some(Err(err)) if err.kind() == io::ErrorKind::UnexpectedEof => break 'lines false,
5561
Some(Err(err)) => return Err(err.into()),
@@ -75,6 +81,9 @@ impl Response {
7581
if Response::parse_v1_ack_or_shallow_or_assume_pack(&mut acks, &mut shallows, &peeked_line) {
7682
break 'lines true;
7783
}
84+
if let Some(Acknowledgement::Nak) = acks.last().filter(|_| !client_expects_pack) {
85+
break 'lines false;
86+
}
7887
assert_ne!(reader.readline_str(&mut line)?, 0, "consuming a peeked line works");
7988
};
8089
Ok(Response {

gix-protocol/src/fetch_fn.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,12 @@ where
128128
if sideband_all {
129129
setup_remote_progress(&mut progress, &mut reader);
130130
}
131-
let response = Response::from_line_reader(protocol_version, &mut reader).await?;
131+
let response = Response::from_line_reader(
132+
protocol_version,
133+
&mut reader,
134+
true, /* hack, telling us we don't want this delegate approach anymore */
135+
)
136+
.await?;
132137
previous_response = if response.has_pack() {
133138
progress.step();
134139
progress.set_name("receiving pack");

gix-protocol/tests/fetch/response.rs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ mod v1 {
2929
async fn clone() -> crate::Result {
3030
let mut provider = mock_reader("v1/clone-only.response");
3131
let mut reader = provider.as_read_without_sidebands();
32-
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader).await?;
32+
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true).await?;
3333
assert_eq!(r.acknowledgements(), &[Acknowledgement::Nak]);
3434
assert!(r.has_pack());
3535
let mut buf = Vec::new();
@@ -42,7 +42,7 @@ mod v1 {
4242
async fn shallow_clone() -> crate::Result {
4343
let mut provider = mock_reader("v1/clone-deepen-1.response");
4444
let mut reader = provider.as_read_without_sidebands();
45-
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader).await?;
45+
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true).await?;
4646
assert_eq!(
4747
r.shallow_updates(),
4848
&[ShallowUpdate::Shallow(id("808e50d724f604f69ab93c6da2919c014667bedb"))]
@@ -59,7 +59,7 @@ mod v1 {
5959
async fn empty_shallow_clone_due_to_depth_being_too_high() -> crate::Result {
6060
let mut provider = mock_reader("v1/clone-deepen-5.response");
6161
let mut reader = provider.as_read_without_sidebands();
62-
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader).await?;
62+
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true).await?;
6363
assert!(r.shallow_updates().is_empty());
6464
assert_eq!(r.acknowledgements(), &[Acknowledgement::Nak]);
6565
assert!(r.has_pack());
@@ -73,7 +73,7 @@ mod v1 {
7373
async fn unshallow_fetch() -> crate::Result {
7474
let mut provider = mock_reader("v1/fetch-unshallow.response");
7575
let mut reader = provider.as_read_without_sidebands();
76-
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader).await?;
76+
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true).await?;
7777
assert_eq!(
7878
r.acknowledgements(),
7979
&[
@@ -103,7 +103,8 @@ mod v1 {
103103
#[maybe_async::test(feature = "blocking-client", async(feature = "async-client", async_std::test))]
104104
async fn fetch_acks_without_pack() -> crate::Result {
105105
let mut provider = mock_reader("v1/fetch-no-pack.response");
106-
let r = fetch::Response::from_line_reader(Protocol::V1, &mut provider.as_read_without_sidebands()).await?;
106+
let r = fetch::Response::from_line_reader(Protocol::V1, &mut provider.as_read_without_sidebands(), true)
107+
.await?;
107108
assert_eq!(
108109
r.acknowledgements(),
109110
&[
@@ -119,7 +120,7 @@ mod v1 {
119120
async fn fetch_acks_and_pack() -> crate::Result {
120121
let mut provider = mock_reader("v1/fetch.response");
121122
let mut reader = provider.as_read_without_sidebands();
122-
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader).await?;
123+
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true).await?;
123124
assert_eq!(
124125
r.acknowledgements(),
125126
&[
@@ -218,7 +219,7 @@ mod v2 {
218219
);
219220
let mut provider = mock_reader(&fixture);
220221
let mut reader = provider.as_read_without_sidebands();
221-
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader).await?;
222+
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true).await?;
222223
assert!(r.acknowledgements().is_empty(), "it should go straight to the packfile");
223224
assert!(r.has_pack());
224225
reader.set_progress_handler(Some(Box::new(|_is_err, _text| {
@@ -235,7 +236,7 @@ mod v2 {
235236
async fn shallow_clone() -> crate::Result {
236237
let mut provider = mock_reader("v2/clone-deepen-1.response");
237238
let mut reader = provider.as_read_without_sidebands();
238-
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader).await?;
239+
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true).await?;
239240
assert!(r.acknowledgements().is_empty(), "it should go straight to the packfile");
240241
assert_eq!(
241242
r.shallow_updates(),
@@ -252,7 +253,7 @@ mod v2 {
252253
async fn unshallow_fetch() -> crate::Result {
253254
let mut provider = mock_reader("v2/fetch-unshallow.response");
254255
let mut reader = provider.as_read_without_sidebands();
255-
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader).await?;
256+
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true).await?;
256257
assert_eq!(
257258
r.acknowledgements(),
258259
&[
@@ -284,7 +285,7 @@ mod v2 {
284285
async fn empty_shallow_clone() -> crate::Result {
285286
let mut provider = mock_reader("v2/clone-deepen-5.response");
286287
let mut reader = provider.as_read_without_sidebands();
287-
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader).await?;
288+
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true).await?;
288289
assert!(r.acknowledgements().is_empty(), "it should go straight to the packfile");
289290
assert!(r.shallow_updates().is_empty(), "it should go straight to the packfile");
290291
assert!(r.has_pack());
@@ -298,7 +299,7 @@ mod v2 {
298299
async fn clone_with_sidebands() -> crate::Result {
299300
let mut provider = mock_reader("v2/clone-only-2.response");
300301
let mut reader = provider.as_read_without_sidebands();
301-
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader).await?;
302+
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true).await?;
302303
assert!(r.acknowledgements().is_empty(), "it should go straight to the packfile");
303304
assert!(r.has_pack());
304305

@@ -320,7 +321,8 @@ mod v2 {
320321
#[maybe_async::test(feature = "blocking-client", async(feature = "async-client", async_std::test))]
321322
async fn fetch_acks_without_pack() -> crate::Result {
322323
let mut provider = mock_reader("v2/fetch-no-pack.response");
323-
let r = fetch::Response::from_line_reader(Protocol::V2, &mut provider.as_read_without_sidebands()).await?;
324+
let r = fetch::Response::from_line_reader(Protocol::V2, &mut provider.as_read_without_sidebands(), true)
325+
.await?;
324326
assert_eq!(r.acknowledgements(), &[Acknowledgement::Nak]);
325327
Ok(())
326328
}
@@ -330,7 +332,7 @@ mod v2 {
330332
let mut provider = mock_reader("v2/fetch-err-line.response");
331333
provider.fail_on_err_lines(true);
332334
let mut sidebands = provider.as_read_without_sidebands();
333-
match fetch::Response::from_line_reader(Protocol::V2, &mut sidebands).await {
335+
match fetch::Response::from_line_reader(Protocol::V2, &mut sidebands, true).await {
334336
Ok(_) => panic!("need error response"),
335337
Err(err) => match err {
336338
fetch::response::Error::UploadPack(err) => {
@@ -345,7 +347,7 @@ mod v2 {
345347
async fn fetch_acks_and_pack() -> crate::Result {
346348
let mut provider = mock_reader("v2/fetch.response");
347349
let mut reader = provider.as_read_without_sidebands();
348-
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader).await?;
350+
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true).await?;
349351
assert_eq!(
350352
r.acknowledgements(),
351353
&[

0 commit comments

Comments
 (0)