Skip to content

Commit

Permalink
git: show fetch progress with git.subprocess = true
Browse files Browse the repository at this point in the history
  • Loading branch information
bsdinis committed Jan 31, 2025
1 parent 007bb80 commit e2ddeb1
Showing 1 changed file with 95 additions and 5 deletions.
100 changes: 95 additions & 5 deletions lib/src/git_subprocess.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use std::thread;
use bstr::ByteSlice;
use thiserror::Error;

use crate::git::Progress;
use crate::git::RefSpec;
use crate::git::RefToPush;
use crate::git::RemoteCallbacks;
Expand Down Expand Up @@ -123,10 +124,10 @@ impl<'a> GitSubprocessContext<'a> {
}
let mut command = self.create_command();
command.stdout(Stdio::piped());
command
.arg("fetch")
// attempt to prune stale refs
.arg("--prune");
command.args(["fetch", "--prune"]);
if callbacks.progress.is_some() {
command.arg("--progress");
}
if let Some(d) = depth {
command.arg(format!("--depth={d}"));
}
Expand Down Expand Up @@ -523,12 +524,40 @@ fn wait_with_progress(
})
}

#[derive(Default)]
struct GitProgress {
// (frac, total)
deltas: (u64, u64),
objects: (u64, u64),
counted_objects: (u64, u64),
compressed_objects: (u64, u64),
}

impl GitProgress {
fn to_progress(&self) -> Progress {
Progress {
bytes_downloaded: None,
overall: self.fraction() as f32 / self.total() as f32,
}
}

fn fraction(&self) -> u64 {
self.objects.0 + self.deltas.0 + self.counted_objects.0 + self.compressed_objects.0
}

fn total(&self) -> u64 {
self.objects.1 + self.deltas.1 + self.counted_objects.1 + self.compressed_objects.1
}
}

fn read_to_end_with_progress<R: Read>(
src: R,
callbacks: &mut RemoteCallbacks<'_>,
) -> io::Result<Vec<u8>> {
let mut reader = BufReader::new(src);
let mut data = Vec::new();
let mut git_progress = GitProgress::default();

loop {
// progress sent through sideband channel may be terminated by \r
let start = data.len();
Expand All @@ -537,7 +566,25 @@ fn read_to_end_with_progress<R: Read>(
if line.is_empty() {
break;
}
if let Some(message) = line.strip_prefix(b"remote: ") {

if update_progress(line, &mut git_progress.objects, "Receiving objects:")
|| update_progress(line, &mut git_progress.deltas, "Receiving objects:")
|| update_progress(
line,
&mut git_progress.counted_objects,
"remote: Counting objects:",
)
|| update_progress(
line,
&mut git_progress.compressed_objects,
"remote: Compressing objects:",
)
{
if let Some(cb) = callbacks.progress.as_mut() {
cb(&git_progress.to_progress());
}
data.truncate(start);
} else if let Some(message) = line.strip_prefix(b"remote: ") {
if let Some(cb) = callbacks.sideband_progress.as_mut() {
cb(message);
}
Expand All @@ -547,6 +594,18 @@ fn read_to_end_with_progress<R: Read>(
Ok(data)
}

fn update_progress(line: &[u8], progress: &mut (u64, u64), prefix: &str) -> bool {
if line.starts_with_str(prefix) {
if let Some((frac, total)) = read_progress_line(line) {
*progress = (frac, total);
}

true
} else {
false
}
}

fn read_until_cr_or_lf<R: io::BufRead + ?Sized>(
reader: &mut R,
dest_buf: &mut Vec<u8>,
Expand All @@ -561,14 +620,27 @@ fn read_until_cr_or_lf<R: io::BufRead + ?Sized>(
Some(i) => (i + 1, true),
None => (data.len(), false),
};

dest_buf.extend_from_slice(&data[..n]);
reader.consume(n);

if found || n == 0 {
return Ok(());
}
}
}

/// Read progress lines of the form: `<text> (<frac>/<total>)`
/// Ensures that frac < total
fn read_progress_line(line: &[u8]) -> Option<(u64, u64)> {
let (_prefix, suffix) = line.split_once_str("(")?;
let (fraction, _suffix) = suffix.split_once_str(")")?;
let (frac_str, total_str) = fraction.split_once_str("/")?;
let frac = frac_str.to_str().ok()?.parse().ok()?;
let total = total_str.to_str().ok()?.parse().ok()?;
(frac <= total).then_some((frac, total))
}

#[cfg(test)]
mod test {
use indoc::indoc;
Expand Down Expand Up @@ -717,4 +789,22 @@ Done";
);
assert_eq!(output, b"blah blah\nsome error message");
}

#[test]
fn test_read_progress_line() {
assert_eq!(
read_progress_line(b"Receiving objects: (42/100)\r"),
Some((42, 100))
);
assert_eq!(
read_progress_line(b"Resolving deltas: (0/1000)\r"),
Some((0, 1000))
);
assert_eq!(read_progress_line(b"Receiving objects: (420/100)\r"), None);
assert_eq!(
read_progress_line(b"remote: this is something else\n"),
None
);
assert_eq!(read_progress_line(b"fatal: this is a git error\n"), None);
}
}

0 comments on commit e2ddeb1

Please sign in to comment.