Skip to content

Commit 41fdb84

Browse files
committed
feat: WithSidebands now offers a read_data_line(byte_buf) method.
That way one won't have to assume UTF8 encoding in the returned buffer. Note that the reason for it not returning a reference to its internal buffer is due to the async implementation requiring it. Its future-based architecture can't really express the lifetimes associated with it (yet).
1 parent 8fb21d7 commit 41fdb84

File tree

4 files changed

+125
-13
lines changed

4 files changed

+125
-13
lines changed

git-packetline/src/line/mod.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ use crate::{decode, BandRef, Channel, ErrorRef, PacketLineRef, TextRef, ERR_PREF
44

55
impl<'a> PacketLineRef<'a> {
66
/// Return this instance as slice if it's [`Data`][PacketLineRef::Data].
7-
pub fn as_slice(&self) -> Option<&[u8]> {
7+
pub fn as_slice(&self) -> Option<&'a [u8]> {
88
match self {
99
PacketLineRef::Data(d) => Some(d),
1010
PacketLineRef::Flush | PacketLineRef::Delimiter | PacketLineRef::ResponseEnd => None,
1111
}
1212
}
1313
/// Return this instance's [`as_slice()`][PacketLineRef::as_slice()] as [`BStr`].
14-
pub fn as_bstr(&self) -> Option<&BStr> {
14+
pub fn as_bstr(&self) -> Option<&'a BStr> {
1515
self.as_slice().map(Into::into)
1616
}
1717
/// Interpret this instance's [`as_slice()`][PacketLineRef::as_slice()] as [`ErrorRef`].
@@ -20,13 +20,13 @@ impl<'a> PacketLineRef<'a> {
2020
///
2121
/// Note that this creates an unchecked error using the slice verbatim, which is useful to [serialize it][ErrorRef::write_to()].
2222
/// See [`check_error()`][PacketLineRef::check_error()] for a version that assures the error information is in the expected format.
23-
pub fn as_error(&self) -> Option<ErrorRef<'_>> {
23+
pub fn as_error(&self) -> Option<ErrorRef<'a>> {
2424
self.as_slice().map(ErrorRef)
2525
}
2626
/// Check this instance's [`as_slice()`][PacketLineRef::as_slice()] is a valid [`ErrorRef`] and return it.
2727
///
2828
/// This works for any data received in an error [channel][crate::Channel].
29-
pub fn check_error(&self) -> Option<ErrorRef<'_>> {
29+
pub fn check_error(&self) -> Option<ErrorRef<'a>> {
3030
self.as_slice().and_then(|data| {
3131
if data.len() >= ERR_PREFIX.len() && &data[..ERR_PREFIX.len()] == ERR_PREFIX {
3232
Some(ErrorRef(&data[ERR_PREFIX.len()..]))
@@ -36,15 +36,15 @@ impl<'a> PacketLineRef<'a> {
3636
})
3737
}
3838
/// Return this instance as text, with the trailing newline truncated if present.
39-
pub fn as_text(&self) -> Option<TextRef<'_>> {
39+
pub fn as_text(&self) -> Option<TextRef<'a>> {
4040
self.as_slice().map(Into::into)
4141
}
4242

4343
/// Interpret the data in this [`slice`][PacketLineRef::as_slice()] as [`BandRef`] according to the given `kind` of channel.
4444
///
4545
/// Note that this is only relevant in a side-band channel.
4646
/// See [`decode_band()`][PacketLineRef::decode_band()] in case `kind` is unknown.
47-
pub fn as_band(&self, kind: Channel) -> Option<BandRef<'_>> {
47+
pub fn as_band(&self, kind: Channel) -> Option<BandRef<'a>> {
4848
self.as_slice().map(|d| match kind {
4949
Channel::Data => BandRef::Data(d),
5050
Channel::Progress => BandRef::Progress(d),
@@ -53,7 +53,7 @@ impl<'a> PacketLineRef<'a> {
5353
}
5454

5555
/// Decode the band of this [`slice`][PacketLineRef::as_slice()]
56-
pub fn decode_band(&self) -> Result<BandRef<'_>, decode::band::Error> {
56+
pub fn decode_band(&self) -> Result<BandRef<'a>, decode::band::Error> {
5757
let d = self.as_slice().ok_or(decode::band::Error::NonDataLine)?;
5858
Ok(match d[0] {
5959
1 => BandRef::Data(&d[1..]),
@@ -73,11 +73,11 @@ impl<'a> From<&'a [u8]> for TextRef<'a> {
7373

7474
impl<'a> TextRef<'a> {
7575
/// Return this instance's data.
76-
pub fn as_slice(&self) -> &[u8] {
76+
pub fn as_slice(&self) -> &'a [u8] {
7777
self.0
7878
}
7979
/// Return this instance's data as [`BStr`].
80-
pub fn as_bstr(&self) -> &BStr {
80+
pub fn as_bstr(&self) -> &'a BStr {
8181
self.0.into()
8282
}
8383
}

git-packetline/src/read/sidebands/async_io.rs

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,19 @@ where
148148

149149
/// Effectively forwards to the parent [StreamingPeekableIter::peek_line()], allowing to see what would be returned
150150
/// next on a call to [`read_line()`][io::BufRead::read_line()].
151-
pub async fn peek_data_line(&mut self) -> Option<std::io::Result<Result<&[u8], crate::decode::Error>>> {
151+
///
152+
/// # Warning
153+
///
154+
/// This skips all sideband handling and may return an unprocessed line with sidebands still contained in it.
155+
pub async fn peek_data_line(&mut self) -> Option<std::io::Result<Result<&[u8], decode::Error>>> {
152156
match self.state {
153157
State::Idle { ref mut parent } => match parent
154158
.as_mut()
155159
.expect("parent is always available if we are idle")
156160
.peek_line()
157161
.await
158162
{
159-
Some(Ok(Ok(crate::PacketLineRef::Data(line)))) => Some(Ok(Ok(line))),
163+
Some(Ok(Ok(PacketLineRef::Data(line)))) => Some(Ok(Ok(line))),
160164
Some(Ok(Err(err))) => Some(Ok(Err(err))),
161165
Some(Err(err)) => Some(Err(err)),
162166
_ => None,
@@ -165,10 +169,55 @@ where
165169
}
166170
}
167171

168-
/// Read a packet line as line.
172+
/// Read a packet line as string line.
169173
pub fn read_line<'b>(&'b mut self, buf: &'b mut String) -> ReadLineFuture<'a, 'b, T, F> {
170174
ReadLineFuture { parent: self, buf }
171175
}
176+
177+
/// Read a packet line from the underlying packet reader, returning empty lines if a stop-packetline was reached.
178+
///
179+
/// # Warning
180+
///
181+
/// This skips all sideband handling and may return an unprocessed line with sidebands still contained in it.
182+
pub async fn read_data_line(&mut self) -> Option<std::io::Result<Result<PacketLineRef<'_>, decode::Error>>> {
183+
match &mut self.state {
184+
State::Idle { parent: Some(parent) } => {
185+
assert_eq!(
186+
self.cap, 0,
187+
"we don't support partial buffers right now - read-line must be used consistently"
188+
);
189+
parent.read_line().await
190+
}
191+
_ => None,
192+
}
193+
}
194+
}
195+
196+
pub struct ReadDataLineFuture<'a, 'b, T: AsyncRead, F> {
197+
parent: &'b mut WithSidebands<'a, T, F>,
198+
buf: &'b mut Vec<u8>,
199+
}
200+
201+
impl<'a, 'b, T, F> Future for ReadDataLineFuture<'a, 'b, T, F>
202+
where
203+
T: AsyncRead + Unpin,
204+
F: FnMut(bool, &[u8]) + Unpin,
205+
{
206+
type Output = std::io::Result<usize>;
207+
208+
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
209+
assert_eq!(
210+
self.parent.cap, 0,
211+
"we don't support partial buffers right now - read-line must be used consistently"
212+
);
213+
let Self { buf, parent } = &mut *self;
214+
let line = ready!(Pin::new(parent).poll_fill_buf(cx))?;
215+
buf.clear();
216+
buf.extend_from_slice(line);
217+
let bytes = line.len();
218+
self.parent.cap = 0;
219+
Poll::Ready(Ok(bytes))
220+
}
172221
}
173222

174223
pub struct ReadLineFuture<'a, 'b, T: AsyncRead, F> {

git-packetline/src/read/sidebands/blocking_io.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,31 @@ where
8484

8585
/// Effectively forwards to the parent [StreamingPeekableIter::peek_line()], allowing to see what would be returned
8686
/// next on a call to [`read_line()`][io::BufRead::read_line()].
87+
///
88+
/// # Warning
89+
///
90+
/// This skips all sideband handling and may return an unprocessed line with sidebands still contained in it.
8791
pub fn peek_data_line(&mut self) -> Option<io::Result<Result<&[u8], crate::decode::Error>>> {
8892
match self.parent.peek_line() {
89-
Some(Ok(Ok(crate::PacketLineRef::Data(line)))) => Some(Ok(Ok(line))),
93+
Some(Ok(Ok(PacketLineRef::Data(line)))) => Some(Ok(Ok(line))),
9094
Some(Ok(Err(err))) => Some(Ok(Err(err))),
9195
Some(Err(err)) => Some(Err(err)),
9296
_ => None,
9397
}
9498
}
99+
100+
/// Read a whole packetline from the underlying reader, with empty lines indicating a stop packetline.
101+
///
102+
/// # Warning
103+
///
104+
/// This skips all sideband handling and may return an unprocessed line with sidebands still contained in it.
105+
pub fn read_data_line(&mut self) -> Option<io::Result<Result<PacketLineRef<'_>, crate::decode::Error>>> {
106+
assert_eq!(
107+
self.cap, 0,
108+
"we don't support partial buffers right now - read-line must be used consistently"
109+
);
110+
self.parent.read_line()
111+
}
95112
}
96113

97114
impl<'a, T, F> BufRead for WithSidebands<'a, T, F>

git-packetline/tests/read/sideband.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,52 @@ async fn read_line_trait_method_reads_one_packet_line_at_a_time() -> crate::Resu
144144
Ok(())
145145
}
146146

147+
#[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))]
148+
async fn readline_reads_one_packet_line_at_a_time() -> crate::Result {
149+
let buf = fixture_bytes("v1/01-clone.combined-output-no-binary");
150+
151+
let mut rd = git_packetline::StreamingPeekableIter::new(&buf[..], &[PacketLineRef::Flush]);
152+
153+
let mut r = rd.as_read();
154+
let line = r.read_data_line().await.unwrap()??.as_bstr().unwrap();
155+
assert_eq!(line, "808e50d724f604f69ab93c6da2919c014667bedb HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative no-progress include-tag multi_ack_detailed symref=HEAD:refs/heads/master object-format=sha1 agent=git/2.28.0\n");
156+
let line = r.read_data_line().await.unwrap()??.as_bstr().unwrap();
157+
assert_eq!(line, "808e50d724f604f69ab93c6da2919c014667bedb refs/heads/master\n");
158+
let line = r.read_data_line().await;
159+
assert!(line.is_none(), "flush means `None`");
160+
let line = r.read_data_line().await;
161+
assert!(line.is_none(), "…which can't be overcome unless the reader is reset");
162+
assert_eq!(
163+
r.stopped_at(),
164+
Some(PacketLineRef::Flush),
165+
"it knows what stopped the reader"
166+
);
167+
168+
drop(r);
169+
rd.reset();
170+
171+
let mut r = rd.as_read();
172+
let line = r.read_data_line().await.unwrap()??.as_bstr().unwrap();
173+
assert_eq!(line.as_bstr(), "NAK\n");
174+
175+
drop(r);
176+
177+
let mut r = rd.as_read_with_sidebands(|_, _| ());
178+
let line = r.read_data_line().await.unwrap()??.as_bstr().unwrap();
179+
assert_eq!(
180+
line.as_bstr(),
181+
"\x02Enumerating objects: 3, done.\n",
182+
"sidebands are ignored entirely here"
183+
);
184+
for _ in 0..6 {
185+
let _discard_more_progress = r.read_data_line().await.unwrap()??.as_bstr().unwrap();
186+
}
187+
let line = r.read_data_line().await;
188+
assert!(line.is_none(), "and we have reached the end");
189+
190+
Ok(())
191+
}
192+
147193
#[maybe_async::test(feature = "blocking-io", async(feature = "async-io", async_std::test))]
148194
async fn peek_past_an_actual_eof_is_an_error() -> crate::Result {
149195
let input = b"0009ERR e";

0 commit comments

Comments
 (0)