Skip to content

shallow protocol #770

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 11, 2023
6 changes: 5 additions & 1 deletion crate-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -637,10 +637,14 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/gix-lock/README.
* [x] find remote itself
- [ ] respect `branch.<name>.merge` in the returned remote.
* **remotes**
* [ ] clone
* [x] clone
* [ ] shallow
* [ ] include-tags when shallow is used (needs separate fetch)
* [ ] prune non-existing shallow commits
* [ ] [bundles](https://git-scm.com/docs/git-bundle)
* [x] fetch
* [ ] 'ref-in-want'
* [ ] standard negotiation algorithms (right now we only have a 'naive' one)
* [ ] push
* [x] ls-refs
* [x] ls-refs with ref-spec filter
Expand Down
7 changes: 5 additions & 2 deletions gitoxide-core/src/repository/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub struct Options {
pub bare: bool,
pub handshake_info: bool,
pub no_tags: bool,
pub shallow: gix::remote::fetch::Shallow,
}

pub const PROGRESS_RANGE: std::ops::RangeInclusive<u8> = 1..=3;
Expand All @@ -30,6 +31,7 @@ pub(crate) mod function {
handshake_info,
bare,
no_tags,
shallow,
}: Options,
) -> anyhow::Result<()>
where
Expand Down Expand Up @@ -66,8 +68,9 @@ pub(crate) mod function {
if no_tags {
prepare = prepare.configure_remote(|r| Ok(r.with_fetch_tags(gix::remote::fetch::Tags::None)));
}
let (mut checkout, fetch_outcome) =
prepare.fetch_then_checkout(&mut progress, &gix::interrupt::IS_INTERRUPTED)?;
let (mut checkout, fetch_outcome) = prepare
.with_shallow(shallow)
.fetch_then_checkout(&mut progress, &gix::interrupt::IS_INTERRUPTED)?;

let (repo, outcome) = if bare {
(checkout.persist(), None)
Expand Down
3 changes: 3 additions & 0 deletions gitoxide-core/src/repository/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub struct Options {
pub remote: Option<String>,
/// If non-empty, override all ref-specs otherwise configured in the remote
pub ref_specs: Vec<BString>,
pub shallow: gix::remote::fetch::Shallow,
pub handshake_info: bool,
}

Expand All @@ -30,6 +31,7 @@ pub(crate) mod function {
dry_run,
remote,
handshake_info,
shallow,
ref_specs,
}: Options,
) -> anyhow::Result<()>
Expand All @@ -50,6 +52,7 @@ pub(crate) mod function {
.connect(gix::remote::Direction::Fetch, progress)?
.prepare_fetch(Default::default())?
.with_dry_run(dry_run)
.with_shallow(shallow)
.receive(&gix::interrupt::IS_INTERRUPTED)?;

if handshake_info {
Expand Down
2 changes: 1 addition & 1 deletion gix-packetline/src/read/sidebands/async_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ where
}

/// Read a packet line as string line.
pub fn read_line<'b>(&'b mut self, buf: &'b mut String) -> ReadLineFuture<'a, 'b, T, F> {
pub fn read_line_to_string<'b>(&'b mut self, buf: &'b mut String) -> ReadLineFuture<'a, 'b, T, F> {
ReadLineFuture { parent: self, buf }
}

Expand Down
28 changes: 16 additions & 12 deletions gix-packetline/src/read/sidebands/blocking_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,22 @@ where
);
self.parent.read_line()
}

/// Like `BufRead::read_line()`, but will only read one packetline at a time.
///
/// It will also be easier to call as sometimes it's unclear which implementation we get on a type like this with
/// plenty of generic parameters.
pub fn read_line_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
assert_eq!(
self.cap, 0,
"we don't support partial buffers right now - read-line must be used consistently"
);
let line = std::str::from_utf8(self.fill_buf()?).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
buf.push_str(line);
let bytes = line.len();
self.cap = 0;
Ok(bytes)
}
}

impl<'a, T, F> BufRead for WithSidebands<'a, T, F>
Expand Down Expand Up @@ -168,18 +184,6 @@ where
fn consume(&mut self, amt: usize) {
self.pos = std::cmp::min(self.pos + amt, self.cap);
}

fn read_line(&mut self, buf: &mut String) -> io::Result<usize> {
assert_eq!(
self.cap, 0,
"we don't support partial buffers right now - read-line must be used consistently"
);
let line = std::str::from_utf8(self.fill_buf()?).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
buf.push_str(line);
let bytes = line.len();
self.cap = 0;
Ok(bytes)
}
}

impl<'a, T, F> io::Read for WithSidebands<'a, T, F>
Expand Down
20 changes: 10 additions & 10 deletions gix-packetline/tests/read/sideband.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#[cfg(feature = "blocking-io")]
use std::io::{BufRead, Read};
use std::io::Read;

use bstr::{BString, ByteSlice};
#[cfg(all(not(feature = "blocking-io"), feature = "async-io"))]
Expand Down Expand Up @@ -106,16 +106,16 @@ async fn read_line_trait_method_reads_one_packet_line_at_a_time() -> crate::Resu

let mut out = String::new();
let mut r = rd.as_read();
r.read_line(&mut out).await?;
r.read_line_to_string(&mut out).await?;
assert_eq!(out, "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");
out.clear();
r.read_line(&mut out).await?;
r.read_line_to_string(&mut out).await?;
assert_eq!(out, "808e50d724f604f69ab93c6da2919c014667bedb refs/heads/master\n");
out.clear();
r.read_line(&mut out).await?;
r.read_line_to_string(&mut out).await?;
assert_eq!(out, "", "flush means empty lines…");
out.clear();
r.read_line(&mut out).await?;
r.read_line_to_string(&mut out).await?;
assert_eq!(out, "", "…which can't be overcome unless the reader is reset");
assert_eq!(
r.stopped_at(),
Expand All @@ -127,18 +127,18 @@ async fn read_line_trait_method_reads_one_packet_line_at_a_time() -> crate::Resu
rd.reset();

let mut r = rd.as_read();
r.read_line(&mut out).await?;
r.read_line_to_string(&mut out).await?;
assert_eq!(out, "NAK\n");

drop(r);

let mut r = rd.as_read_with_sidebands(|_, _| ());
out.clear();
r.read_line(&mut out).await?;
r.read_line_to_string(&mut out).await?;
assert_eq!(out, "&");

out.clear();
r.read_line(&mut out).await?;
r.read_line_to_string(&mut out).await?;
assert_eq!(out, "");

Ok(())
Expand Down Expand Up @@ -199,7 +199,7 @@ async fn peek_past_an_actual_eof_is_an_error() -> crate::Result {
assert_eq!(res.expect("one line")??, b"ERR e");

let mut buf = String::new();
reader.read_line(&mut buf).await?;
reader.read_line_to_string(&mut buf).await?;
assert_eq!(
buf, "ERR e",
"by default ERR lines won't propagate as failure but are merely text"
Expand All @@ -223,7 +223,7 @@ async fn peek_past_a_delimiter_is_no_error() -> crate::Result {
assert_eq!(res.expect("one line")??, b"hello");

let mut buf = String::new();
reader.read_line(&mut buf).await?;
reader.read_line_to_string(&mut buf).await?;
assert_eq!(buf, "hello");

let res = reader.peek_data_line().await;
Expand Down
13 changes: 8 additions & 5 deletions gix-protocol/src/fetch/response/async_io.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::io;

use futures_lite::AsyncBufReadExt;
use gix_transport::{client, Protocol};

use crate::fetch::{
Expand All @@ -16,7 +15,7 @@ async fn parse_v2_section<T>(
parse: impl Fn(&str) -> Result<T, response::Error>,
) -> Result<bool, response::Error> {
line.clear();
while reader.read_line(line).await? != 0 {
while reader.readline_str(line).await? != 0 {
res.push(parse(line)?);
line.clear();
}
Expand Down Expand Up @@ -62,7 +61,7 @@ impl Response {
Some(client::MessageKind::Flush),
"If this isn't a flush packet, we don't know what's going on"
);
reader.read_line(&mut line).await?;
reader.readline_str(&mut line).await?;
reader.reset(Protocol::V1);
match reader.peek_data_line().await {
Some(Ok(Ok(line))) => String::from_utf8_lossy(line),
Expand All @@ -76,7 +75,11 @@ impl Response {
if Response::parse_v1_ack_or_shallow_or_assume_pack(&mut acks, &mut shallows, &peeked_line) {
break 'lines true;
}
assert_ne!(reader.read_line(&mut line).await?, 0, "consuming a peeked line works");
assert_ne!(
reader.readline_str(&mut line).await?,
0,
"consuming a peeked line works"
);
};
Ok(Response {
acks,
Expand All @@ -94,7 +97,7 @@ impl Response {
let mut wanted_refs = Vec::<WantedRef>::new();
let has_pack = 'section: loop {
line.clear();
if reader.read_line(&mut line).await? == 0 {
if reader.readline_str(&mut line).await? == 0 {
return Err(response::Error::Io(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Could not read message headline",
Expand Down
8 changes: 4 additions & 4 deletions gix-protocol/src/fetch/response/blocking_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn parse_v2_section<T>(
parse: impl Fn(&str) -> Result<T, response::Error>,
) -> Result<bool, response::Error> {
line.clear();
while reader.read_line(line)? != 0 {
while reader.readline_str(line)? != 0 {
res.push(parse(line)?);
line.clear();
}
Expand Down Expand Up @@ -61,7 +61,7 @@ impl Response {
Some(client::MessageKind::Flush),
"If this isn't a flush packet, we don't know what's going on"
);
reader.read_line(&mut line)?;
reader.readline_str(&mut line)?;
reader.reset(Protocol::V1);
match reader.peek_data_line() {
Some(Ok(Ok(line))) => String::from_utf8_lossy(line),
Expand All @@ -75,7 +75,7 @@ impl Response {
if Response::parse_v1_ack_or_shallow_or_assume_pack(&mut acks, &mut shallows, &peeked_line) {
break 'lines true;
}
assert_ne!(reader.read_line(&mut line)?, 0, "consuming a peeked line works");
assert_ne!(reader.readline_str(&mut line)?, 0, "consuming a peeked line works");
};
Ok(Response {
acks,
Expand All @@ -93,7 +93,7 @@ impl Response {
let mut wanted_refs = Vec::<WantedRef>::new();
let has_pack = 'section: loop {
line.clear();
if reader.read_line(&mut line)? == 0 {
if reader.readline_str(&mut line)? == 0 {
return Err(response::Error::Io(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Could not read message headline",
Expand Down
27 changes: 27 additions & 0 deletions gix-protocol/src/handshake/refs/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,20 @@ impl<'a> gix_transport::client::ReadlineBufRead for Fixture<'a> {
self.0 = lines.as_bytes();
Some(Ok(Ok(gix_packetline::PacketLineRef::Data(res))))
}

fn readline_str(&mut self, line: &mut String) -> std::io::Result<usize> {
use bstr::{BStr, ByteSlice};
let bytes: &BStr = self.0.into();
let mut lines = bytes.lines();
let res = match lines.next() {
None => return Ok(0),
Some(line) => line,
};
self.0 = lines.as_bytes();
let len = res.len();
line.push_str(res.to_str().expect("valid UTF8 in fixture"));
Ok(len)
}
}

#[cfg(feature = "async-client")]
Expand Down Expand Up @@ -220,4 +234,17 @@ impl<'a> gix_transport::client::ReadlineBufRead for Fixture<'a> {
self.0 = lines.as_bytes();
Some(Ok(Ok(gix_packetline::PacketLineRef::Data(res))))
}
async fn readline_str(&mut self, line: &mut String) -> std::io::Result<usize> {
use bstr::{BStr, ByteSlice};
let bytes: &BStr = self.0.into();
let mut lines = bytes.lines();
let res = match lines.next() {
None => return Ok(0),
Some(line) => line,
};
self.0 = lines.as_bytes();
let len = res.len();
line.push_str(res.to_str().expect("valid UTF8 in fixture"));
Ok(len)
}
}
Loading