Skip to content

Commit eb23338

Browse files
committed
Merge branch 'fix-v1-negotiation'
2 parents e7de4c7 + 6295dec commit eb23338

File tree

5 files changed

+51
-21
lines changed

5 files changed

+51
-21
lines changed

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,16 @@ impl Response {
3737
/// and if `true` we will keep parsing until we get a pack as the client already signalled to the server that it's done.
3838
/// This way of doing things allows us to exploit knowledge about more recent versions of the protocol, which keeps code easier
3939
/// and more localized without having to support all the cruft that there is.
40+
///
41+
/// `wants_to_negotiate` should be `false` for clones which is when we don't have sent any haves. The reason for this flag to exist
42+
/// is to predict how to parse V1 output only, and neither `client_expects_pack` nor `wants_to_negotiate` are relevant for V2.
43+
/// This ugliness is in place to avoid having to resort to an [an even more complex ugliness](https://github.com/git/git/blob/9e49351c3060e1fa6e0d2de64505b7becf157f28/fetch-pack.c#L583-L594)
44+
/// that `git` has to use to predict how many acks are supposed to be read. We also genuinely hope that this covers it all….
4045
pub async fn from_line_reader(
4146
version: Protocol,
4247
reader: &mut (impl client::ExtendedBufRead + Unpin),
4348
client_expects_pack: bool,
49+
wants_to_negotiate: bool,
4450
) -> Result<Response, response::Error> {
4551
match version {
4652
Protocol::V0 | Protocol::V1 => {
@@ -89,7 +95,10 @@ impl Response {
8995
);
9096
// When the server sends ready, we know there is going to be a pack so no need to stop early.
9197
saw_ready |= matches!(acks.last(), Some(Acknowledgement::Ready));
92-
if let Some(Acknowledgement::Nak) = acks.last().filter(|_| !client_expects_pack && !saw_ready) {
98+
if let Some(Acknowledgement::Nak) = acks.last().filter(|_| !client_expects_pack || !saw_ready) {
99+
if !wants_to_negotiate {
100+
continue;
101+
}
93102
break 'lines false;
94103
}
95104
};

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,16 @@ impl Response {
3737
/// and if `true` we will keep parsing until we get a pack as the client already signalled to the server that it's done.
3838
/// This way of doing things allows us to exploit knowledge about more recent versions of the protocol, which keeps code easier
3939
/// and more localized without having to support all the cruft that there is.
40+
///
41+
/// `wants_to_negotiate` should be `false` for clones which is when we don't have sent any haves. The reason for this flag to exist
42+
/// is to predict how to parse V1 output only, and neither `client_expects_pack` nor `wants_to_negotiate` are relevant for V2.
43+
/// This ugliness is in place to avoid having to resort to an [an even more complex ugliness](https://github.com/git/git/blob/9e49351c3060e1fa6e0d2de64505b7becf157f28/fetch-pack.c#L583-L594)
44+
/// that `git` has to use to predict how many acks are supposed to be read. We also genuinely hope that this covers it all….
4045
pub fn from_line_reader(
4146
version: Protocol,
4247
reader: &mut impl client::ExtendedBufRead,
4348
client_expects_pack: bool,
49+
wants_to_negotiate: bool,
4450
) -> Result<Response, response::Error> {
4551
match version {
4652
Protocol::V0 | Protocol::V1 => {
@@ -85,7 +91,10 @@ impl Response {
8591
assert_ne!(reader.readline_str(&mut line)?, 0, "consuming a peeked line works");
8692
// When the server sends ready, we know there is going to be a pack so no need to stop early.
8793
saw_ready |= matches!(acks.last(), Some(Acknowledgement::Ready));
88-
if let Some(Acknowledgement::Nak) = acks.last().filter(|_| !client_expects_pack && !saw_ready) {
94+
if let Some(Acknowledgement::Nak) = acks.last().filter(|_| !client_expects_pack || !saw_ready) {
95+
if !wants_to_negotiate {
96+
continue;
97+
}
8998
break 'lines false;
9099
}
91100
};

gix-protocol/src/fetch_fn.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ pub enum FetchConnection {
4444
/// * If `trace` is `true`, all packetlines received or sent will be passed to the facilities of the `gix-trace` crate.
4545
///
4646
/// _Note_ that depending on the `delegate`, the actual action performed can be `ls-refs`, `clone` or `fetch`.
47+
///
48+
/// # WARNING - Do not use!
49+
///
50+
/// As it will hang when having multiple negotiation rounds.
4751
#[allow(clippy::result_large_err)]
4852
#[maybe_async]
4953
// TODO: remove this without losing test coverage - we have the same but better in `gix` and it's
@@ -134,7 +138,8 @@ where
134138
let response = Response::from_line_reader(
135139
protocol_version,
136140
&mut reader,
137-
true, /* hack, telling us we don't want this delegate approach anymore */
141+
true, /* hack, telling us we don't want this delegate approach anymore */
142+
false, /* just as much of a hack which causes us to expect a pack immediately */
138143
)
139144
.await?;
140145
previous_response = if response.has_pack() {

gix-protocol/tests/fetch/response.rs

Lines changed: 18 additions & 16 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, true).await?;
32+
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true, false).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, true).await?;
45+
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true, false).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, true).await?;
62+
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true, false).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, true).await?;
76+
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true, true).await?;
7777
assert_eq!(
7878
r.acknowledgements(),
7979
&[
@@ -103,8 +103,9 @@ 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(), true)
107-
.await?;
106+
let r =
107+
fetch::Response::from_line_reader(Protocol::V1, &mut provider.as_read_without_sidebands(), true, true)
108+
.await?;
108109
assert_eq!(
109110
r.acknowledgements(),
110111
&[
@@ -120,7 +121,7 @@ mod v1 {
120121
async fn fetch_acks_and_pack() -> crate::Result {
121122
let mut provider = mock_reader("v1/fetch.response");
122123
let mut reader = provider.as_read_without_sidebands();
123-
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true).await?;
124+
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader, true, true).await?;
124125
assert_eq!(
125126
r.acknowledgements(),
126127
&[
@@ -223,7 +224,7 @@ mod v2 {
223224
);
224225
let mut provider = mock_reader(&fixture);
225226
let mut reader = provider.as_read_without_sidebands();
226-
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true).await?;
227+
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true, true).await?;
227228
assert!(r.acknowledgements().is_empty(), "it should go straight to the packfile");
228229
assert!(r.has_pack());
229230
reader.set_progress_handler(Some(Box::new(|_is_err, _text| {
@@ -240,7 +241,7 @@ mod v2 {
240241
async fn shallow_clone() -> crate::Result {
241242
let mut provider = mock_reader("v2/clone-deepen-1.response");
242243
let mut reader = provider.as_read_without_sidebands();
243-
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true).await?;
244+
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true, true).await?;
244245
assert!(r.acknowledgements().is_empty(), "it should go straight to the packfile");
245246
assert_eq!(
246247
r.shallow_updates(),
@@ -257,7 +258,7 @@ mod v2 {
257258
async fn unshallow_fetch() -> crate::Result {
258259
let mut provider = mock_reader("v2/fetch-unshallow.response");
259260
let mut reader = provider.as_read_without_sidebands();
260-
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true).await?;
261+
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true, true).await?;
261262
assert_eq!(
262263
r.acknowledgements(),
263264
&[
@@ -289,7 +290,7 @@ mod v2 {
289290
async fn empty_shallow_clone() -> crate::Result {
290291
let mut provider = mock_reader("v2/clone-deepen-5.response");
291292
let mut reader = provider.as_read_without_sidebands();
292-
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true).await?;
293+
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true, true).await?;
293294
assert!(r.acknowledgements().is_empty(), "it should go straight to the packfile");
294295
assert!(r.shallow_updates().is_empty(), "it should go straight to the packfile");
295296
assert!(r.has_pack());
@@ -303,7 +304,7 @@ mod v2 {
303304
async fn clone_with_sidebands() -> crate::Result {
304305
let mut provider = mock_reader("v2/clone-only-2.response");
305306
let mut reader = provider.as_read_without_sidebands();
306-
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true).await?;
307+
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true, true).await?;
307308
assert!(r.acknowledgements().is_empty(), "it should go straight to the packfile");
308309
assert!(r.has_pack());
309310

@@ -325,8 +326,9 @@ mod v2 {
325326
#[maybe_async::test(feature = "blocking-client", async(feature = "async-client", async_std::test))]
326327
async fn fetch_acks_without_pack() -> crate::Result {
327328
let mut provider = mock_reader("v2/fetch-no-pack.response");
328-
let r = fetch::Response::from_line_reader(Protocol::V2, &mut provider.as_read_without_sidebands(), true)
329-
.await?;
329+
let r =
330+
fetch::Response::from_line_reader(Protocol::V2, &mut provider.as_read_without_sidebands(), true, true)
331+
.await?;
330332
assert_eq!(r.acknowledgements(), &[Acknowledgement::Nak]);
331333
Ok(())
332334
}
@@ -336,7 +338,7 @@ mod v2 {
336338
let mut provider = mock_reader("v2/fetch-err-line.response");
337339
provider.fail_on_err_lines(true);
338340
let mut sidebands = provider.as_read_without_sidebands();
339-
match fetch::Response::from_line_reader(Protocol::V2, &mut sidebands, true).await {
341+
match fetch::Response::from_line_reader(Protocol::V2, &mut sidebands, true, true).await {
340342
Ok(_) => panic!("need error response"),
341343
Err(err) => match err {
342344
fetch::response::Error::UploadPack(err) => {
@@ -351,7 +353,7 @@ mod v2 {
351353
async fn fetch_acks_and_pack() -> crate::Result {
352354
let mut provider = mock_reader("v2/fetch.response");
353355
let mut reader = provider.as_read_without_sidebands();
354-
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true).await?;
356+
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader, true, true).await?;
355357
assert_eq!(
356358
r.acknowledgements(),
357359
&[

gix/src/remote/connection/fetch/receive_pack.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,13 @@ where
221221
if sideband_all {
222222
setup_remote_progress(progress, &mut reader, should_interrupt);
223223
}
224-
let response =
225-
gix_protocol::fetch::Response::from_line_reader(protocol_version, &mut reader, is_done).await?;
224+
let response = gix_protocol::fetch::Response::from_line_reader(
225+
protocol_version,
226+
&mut reader,
227+
is_done,
228+
!is_done,
229+
)
230+
.await?;
226231
let has_pack = response.has_pack();
227232
previous_response = Some(response);
228233
if has_pack {

0 commit comments

Comments
 (0)