Skip to content

Commit 507d342

Browse files
committed
[clone] Support for reading multi-step negoritaions, but…
…only for V2 this will work correctly. V1 may indeed hang in stateful connections, unless we know exactly what caused that response. For now that's acceptable.
1 parent ded46fd commit 507d342

File tree

8 files changed

+86
-8
lines changed

8 files changed

+86
-8
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,8 @@ From there, we can derive a few rules to try adhere to:
445445

446446
## Shortcomings
447447

448+
* **fetches using protocol V1 and stateful connections, i.e. ssh, git, file, may hang**
449+
* This can be fixed by making response parsing.
448450
* **lean** and **light** and **small** builds don't support non-UTF-8 paths _in the CLI_
449451
* This is because they depend on `argh`, which [does not yet support parsing OsStrings](https://github.com/google/argh/issues/33). We however
450452
believe it eventually will do so and thus don't move on to [`pico-args`](https://github.com/RazrFalcon/pico-args/blob/master/examples/app.rs).

git-packetline/tests/packet_line/reader/read.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,39 @@ fn read_pack_with_progress_extraction() -> crate::Result {
102102
);
103103
Ok(())
104104
}
105+
#[test]
106+
fn peek_past_an_actual_eof_is_an_error() -> crate::Result {
107+
let input = b"0009ERR e";
108+
let mut rd = git_packetline::Provider::new(&input[..], &[]);
109+
let mut reader = rd.as_read();
110+
assert_eq!(reader.peek_data_line().expect("one line")??, b"ERR e");
111+
let mut buf = String::new();
112+
reader.read_line(&mut buf)?;
113+
114+
assert_eq!(
115+
reader.peek_data_line().expect("an err").expect_err("foo").kind(),
116+
std::io::ErrorKind::UnexpectedEof,
117+
"peeking past the end is not an error as the caller should make sure we dont try 'invalid' reads"
118+
);
119+
Ok(())
120+
}
121+
122+
#[test]
123+
fn peek_past_a_delimiter_is_no_error() -> crate::Result {
124+
let input = b"0009hello0000";
125+
let mut rd = git_packetline::Provider::new(&input[..], &[PacketLine::Flush]);
126+
let mut reader = rd.as_read();
127+
assert_eq!(reader.peek_data_line().expect("one line")??, b"hello");
128+
129+
let mut buf = String::new();
130+
reader.read_line(&mut buf)?;
131+
132+
assert!(
133+
reader.peek_data_line().is_none(),
134+
"peeking past a flush packet is a 'natural' event that shold not cause an error"
135+
);
136+
Ok(())
137+
}
105138

106139
#[test]
107140
fn handling_of_err_lines() {

git-protocol/src/fetch/response.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,17 @@ impl Response {
102102
let (acks, has_pack) = 'lines: loop {
103103
line.clear();
104104
let peeked_line = match reader.peek_data_line() {
105-
Some(line) => String::from_utf8_lossy(line??),
105+
Some(Ok(Ok(line))) => String::from_utf8_lossy(line),
106+
// This special case (block) deals with a single NAK being a legitimate EOF sometimes
107+
// Note that this might block forever in stateful connections as there it's not really clear
108+
// if something will be following or not by just looking at the response. Instead you have to know
109+
// the arguments sent to the server and count response lines based on intricate knowledge on how the
110+
// server works.
111+
// For now this is acceptable, as V2 can be used as a workaround, which also is the default.
112+
Some(Err(err)) if err.kind() == io::ErrorKind::UnexpectedEof => break 'lines (acks, false),
113+
Some(Err(err)) => return Err(err.into()),
114+
Some(Ok(Err(err))) => return Err(err.into()),
115+
106116
None => break 'lines (acks, false), // EOF
107117
};
108118

git-protocol/tests/fetch/response.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,22 @@ mod v1 {
3131
}
3232

3333
#[test]
34-
fn simple_fetch_acks_and_pack() -> crate::Result {
34+
fn fetch_acks_without_pack() -> crate::Result {
35+
let mut provider = mock_reader("v1/fetch-no-pack.response");
36+
let r = fetch::Response::from_line_reader(Protocol::V1, &mut provider.as_read_without_sidebands())?;
37+
assert_eq!(
38+
r.acknowledgements(),
39+
&[
40+
Acknowledgement::Common(id("47ee0b7fe4f3a7d776c78794873e6467e1c47e59")),
41+
Acknowledgement::Common(id("3f02c0ad360d96e8dbba92f97b42ebbaa4319db1")),
42+
Acknowledgement::NAK,
43+
]
44+
);
45+
Ok(())
46+
}
47+
48+
#[test]
49+
fn fetch_acks_and_pack() -> crate::Result {
3550
let mut provider = mock_reader("v1/fetch.response");
3651
let mut reader = provider.as_read_without_sidebands();
3752
let r = fetch::Response::from_line_reader(Protocol::V1, &mut reader)?;
@@ -74,7 +89,15 @@ mod v2 {
7489
}
7590

7691
#[test]
77-
fn simple_fetch_acks_and_pack() -> crate::Result {
92+
fn fetch_acks_without_pack() -> crate::Result {
93+
let mut provider = mock_reader("v2/fetch-no-pack.response");
94+
let r = fetch::Response::from_line_reader(Protocol::V2, &mut provider.as_read_without_sidebands())?;
95+
assert_eq!(r.acknowledgements(), &[Acknowledgement::NAK,]);
96+
Ok(())
97+
}
98+
99+
#[test]
100+
fn fetch_acks_and_pack() -> crate::Result {
78101
let mut provider = mock_reader("v2/fetch.response");
79102
let mut reader = provider.as_read_without_sidebands();
80103
let r = fetch::Response::from_line_reader(Protocol::V2, &mut reader)?;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
0038ACK 47ee0b7fe4f3a7d776c78794873e6467e1c47e59 common
2+
0038ACK 3f02c0ad360d96e8dbba92f97b42ebbaa4319db1 common
3+
0008NAK
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
0014acknowledgments
2+
0008NAK
3+
0000

gitoxide-core/src/protocol.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ impl From<Protocol> for git_transport::Protocol {
3030

3131
impl Default for Protocol {
3232
fn default() -> Self {
33+
// Note that it's very important this remains V2, as V1 may block forver in stateful (i.e. non-http) connections when fetching
34+
// as we chose not to complicate matters by counting which arguments where sent (just yet).
3335
Protocol::V2
3436
}
3537
}

tasks.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
### Cloning
2-
* **git-transport**
3-
* [ ] a way to support shallow lines during V1 handshake (doesnt' seem to happen in V2 at all)
42
* **git-protocol**
53
* [x] sideband-all support
6-
* [ ] test for multi-stage fetch response (one without a pack file in first round)
7-
* [ ] delegate interaction to support clone
4+
* [x] test for multi-stage fetch response (one without a pack file in first round)
5+
* [x] delegate interaction to support clone
86
* [x] parse server negotiation response
97
* [x] negotiation via delegate
10-
* [ ] received pack file passed to delegate
8+
* [x] received pack file passed to delegate
119
* [x] assure there is a way to do fetches with have/want negotiation
1210
* **gixp-pack-receive**
1311
* [ ] hookup `git-protocol` with delegate to allow for receiving full packs
@@ -21,6 +19,10 @@
2119

2220
### NEXT ITERATION: Fetching _(more analysis needed after previous block)_
2321

22+
* **git-transport**
23+
* [ ] a way to support shallow lines during V1 handshake (doesnt' seem to happen in V2 at all)
24+
* **prodash**
25+
* [ ] force sub-progress
2426
* **git-ref**
2527
* [ ] create ref pointing to ID
2628
* _assure to keep the path towards symbolic refs open, and allow specifying if these should be followed or not_

0 commit comments

Comments
 (0)