pull,package_info,ui,cli: report the pull flow via BuildView

Retire the ProgressCallback option-of-closure, the one pre-port
reporting idiom left in the tree: pull and package_info::lookup now
take &dyn BuildView like every other core flow, and the announcements
map onto message (unsized work), progress (item counts: series probes,
git objects, extraction) and progress again for byte downloads, which
the old callback carried in the same 4-argument shape.

The terminal side swaps the (ProgressBar, closure) pair for PullView, a
BuildView implementation owning one bar of the shared MultiProgress
with the same spinner/transfer style switching; it releases the line on
drop, which the CLI triggers explicitly on both outcomes since
exit(1) skips destructors. Headless callers (tests, internal lookups)
pass &report::Quiet. git clone sideband parsing is skipped entirely
when the view is not enabled, matching the old None-callback shortcut.
This commit is contained in:
2026-10-03 02:17:22 +02:00
parent 9004a33bae
commit 8c8ee5beee
8 changed files with 203 additions and 191 deletions
+87 -84
View File
@@ -13,14 +13,14 @@ use log::debug;
use regex::Regex;
use crate::ProgressCallback;
use crate::report::BuildView;
fn clone_repo(
url: &str,
package: &str,
branch: Option<&str>,
cwd: Option<&Path>,
progress: ProgressCallback<'_>,
view: &dyn BuildView,
) -> Result<(), Box<dyn Error>> {
let target_path = if let Some(path) = cwd {
path.join(package)
@@ -29,11 +29,11 @@ fn clone_repo(
};
let mut callbacks = git2::RemoteCallbacks::new();
if let Some(ref progress_cb) = progress {
// The sideband parser is only worth running when something listens
if view.is_enabled() {
// Download progress
callbacks.transfer_progress(move |stats| {
(progress_cb)(
"",
view.progress(
"Receiving objects...",
stats.received_objects(),
stats.total_objects(),
@@ -42,7 +42,7 @@ fn clone_repo(
});
// Remote progress: messages 'Remote: compressing objects 10% (34/340)'
// Parse progress informations to display them in callbacks
// Parse progress informations to display them in the view
callbacks.sideband_progress(move |data| {
let msg = String::from_utf8_lossy(data);
let re = Regex::new(r"(.*):[ ]*([0-9]*)% \(([0-9]*)/([0-9]*)\)").unwrap();
@@ -61,7 +61,7 @@ fn clone_repo(
.parse::<usize>()
.unwrap_or(0);
(progress_cb)("", msg.as_str(), objects, total);
view.progress(msg.as_str(), objects, total);
}
true
@@ -232,7 +232,7 @@ struct ExtractedArchive {
fn extract_tar_archive<D, F>(
file_path: &Path,
dest: &Path,
progress: ProgressCallback<'_>,
view: &dyn BuildView,
decoder_factory: F,
) -> Result<ExtractedArchive, Box<dyn Error>>
where
@@ -373,9 +373,7 @@ where
current_entry += 1;
// Report progress
if let Some(cb) = progress {
cb("", "Extracting...", current_entry, total_entries);
}
view.progress("Extracting...", current_entry, total_entries);
}
let wrapper = if multiple_roots {
@@ -389,16 +387,16 @@ where
fn extract_archive(
path: &Path,
dest: &Path,
progress: ProgressCallback<'_>,
view: &dyn BuildView,
) -> Result<ExtractedArchive, Box<dyn Error>> {
let filename = path.file_name().unwrap().to_string_lossy();
if filename.ends_with(".tar.gz") || filename.ends_with(".tgz") {
extract_tar_archive(path, dest, progress, GzDecoder::new)
extract_tar_archive(path, dest, view, GzDecoder::new)
} else if filename.ends_with(".tar.xz") || filename.ends_with(".txz") {
extract_tar_archive(path, dest, progress, XzDecoder::new)
extract_tar_archive(path, dest, view, XzDecoder::new)
} else if filename.ends_with(".tar.bz2") || filename.ends_with(".tbz2") {
extract_tar_archive(path, dest, progress, bzip2_rs::DecoderReader::new)
extract_tar_archive(path, dest, view, bzip2_rs::DecoderReader::new)
} else {
Err(format!("Unsupported archive format: {}", filename).into())
}
@@ -439,7 +437,7 @@ async fn download_file_checksum(
checksum: &str,
algo: crate::package_info::ChecksumAlgo,
target_dir: &Path,
progress: ProgressCallback<'_>,
view: &dyn BuildView,
) -> Result<(), Box<dyn Error>> {
// Archive mirrors and CDNs are busy enough that single attempts fail
// spuriously (dropped connections, truncated bodies, index generations
@@ -448,7 +446,7 @@ async fn download_file_checksum(
const ATTEMPTS: u32 = 3;
let mut last_error: Box<dyn Error> = String::new().into();
for attempt in 1..=ATTEMPTS {
match download_file_checksum_once(url, checksum, algo, target_dir, progress).await {
match download_file_checksum_once(url, checksum, algo, target_dir, view).await {
Ok(()) => return Ok(()),
Err(e) => {
log::warn!("download of '{url}' failed (attempt {attempt}/{ATTEMPTS}): {e}");
@@ -468,7 +466,7 @@ async fn download_file_checksum_once(
checksum: &str,
algo: crate::package_info::ChecksumAlgo,
target_dir: &Path,
progress: ProgressCallback<'_>,
view: &dyn BuildView,
) -> Result<(), Box<dyn Error>> {
// Download with the shared client (connect timeout). Large orig tarballs
// can legitimately take longer than the client's default total timeout,
@@ -504,9 +502,9 @@ async fn download_file_checksum_once(
file.write_all(&chunk)?;
buffer.extend_from_slice(&chunk);
if let (Some(cb), Some(total)) = (progress, total_size) {
if let Some(total) = total_size {
let index = min(buffer.len(), total as usize);
cb("", "Downloading...", index, total as usize);
view.progress("Downloading...", index, total as usize);
}
}
@@ -575,7 +573,7 @@ fn setup_pristine_tar_branch(package_dir: &Path, dist: &str) -> Result<(), Box<d
async fn fetch_orig_tarball(
info: &PackageInfo,
cwd: Option<&Path>,
progress: ProgressCallback<'_>,
view: &dyn BuildView,
) -> Result<(), Box<dyn Error>> {
let package_dir = if let Some(path) = cwd {
path.join(&info.stanza.package)
@@ -630,7 +628,7 @@ async fn fetch_orig_tarball(
&orig_file.checksum,
orig_file.checksum_algo,
target_dir,
progress,
view,
)
.await?;
}
@@ -642,7 +640,7 @@ async fn fetch_orig_tarball(
async fn fetch_dsc_file(
info: &PackageInfo,
cwd: Option<&Path>,
progress: ProgressCallback<'_>,
view: &dyn BuildView,
) -> Result<(), Box<dyn Error>> {
let target_dir = cwd.unwrap_or_else(|| Path::new("."));
@@ -662,7 +660,7 @@ async fn fetch_dsc_file(
&dsc_file.checksum,
dsc_file.checksum_algo,
target_dir,
progress,
view,
)
.await?;
@@ -672,7 +670,7 @@ async fn fetch_dsc_file(
async fn fetch_archive_sources(
info: &PackageInfo,
cwd: Option<&Path>,
progress: ProgressCallback<'_>,
view: &dyn BuildView,
) -> Result<(), Box<dyn Error>> {
let package_dir = if let Some(path) = cwd {
path
@@ -702,14 +700,7 @@ async fn fetch_archive_sources(
for file in &files {
let url = format!("{}/{}", info.archive_url, file.name);
download_file_checksum(
&url,
&file.checksum,
file.checksum_algo,
package_dir,
progress,
)
.await?;
download_file_checksum(&url, &file.checksum, file.checksum_algo, package_dir, view).await?;
// Extract all tar archives. Every tar of the source package matches
// (orig, debian tar, native tar, extra components); a tarball in an
@@ -725,7 +716,7 @@ async fn fetch_archive_sources(
let path = package_dir.join(&file.name);
let extract_dir = package_dir.join(&info.stanza.package);
let extracted = extract_archive(&path, &extract_dir, progress)?;
let extracted = extract_archive(&path, &extract_dir, view)?;
// Special case: the debian tar does only contain 'debian'
if file.name.contains("debian.tar.") {
@@ -807,7 +798,7 @@ async fn fetch_archive_sources(
pub async fn pull(
package_info: &PackageInfo,
cwd: Option<&Path>,
progress: ProgressCallback<'_>,
view: &dyn BuildView,
force_archive: bool,
) -> Result<(), Box<dyn Error>> {
let package = &package_info.stanza.package;
@@ -861,50 +852,37 @@ pub async fn pull(
None
};
if let Some(cb) = progress {
cb(
&format!(
"Cloning {}{}...",
url,
if let Some(b) = &branch_name {
format!(" (branch {})", b)
} else {
String::new()
}
),
"",
0,
0,
);
}
view.message(&format!(
"Cloning {}{}...",
url,
if let Some(b) = &branch_name {
format!(" (branch {})", b)
} else {
String::new()
}
));
clone_repo(
url.as_str(),
package,
branch_name.as_deref(),
Some(&package_dir),
progress,
view,
)?;
if !package_info.is_native() {
if let Some(cb) = progress {
cb("Fetching orig tarball...", "", 0, 0);
}
fetch_orig_tarball(package_info, Some(&package_dir), progress).await?;
view.message("Fetching orig tarball...");
fetch_orig_tarball(package_info, Some(&package_dir), view).await?;
} else {
debug!("Native package, skipping orig tarball fetch.");
}
if let Some(cb) = progress {
cb("Fetching dsc file...", "", 0, 0);
}
fetch_dsc_file(package_info, Some(&package_dir), progress).await?;
view.message("Fetching dsc file...");
fetch_dsc_file(package_info, Some(&package_dir), view).await?;
} else {
// Fallback to archive fetching
if let Some(cb) = progress {
cb("Downloading from archive...", "", 0, 0);
}
fetch_archive_sources(package_info, Some(&package_dir), progress).await?;
view.message("Downloading from archive...");
fetch_archive_sources(package_info, Some(&package_dir), view).await?;
}
Ok(())
@@ -931,12 +909,26 @@ mod tests {
let cwd = temp_dir.path();
// Main 'pull' command: the one we want to test
let info = crate::package_info::lookup(package, None, series, "", dist, None, None, None)
.await
.unwrap();
pull(&info, Some(cwd), None, archive.unwrap_or(false))
.await
.unwrap();
let info = crate::package_info::lookup(
package,
None,
series,
"",
dist,
None,
None,
&crate::report::Quiet,
)
.await
.unwrap();
pull(
&info,
Some(cwd),
&crate::report::Quiet,
archive.unwrap_or(false),
)
.await
.unwrap();
let package_dir = cwd.join(package);
assert!(package_dir.exists());
@@ -1136,7 +1128,7 @@ mod tests {
.unwrap();
let dest = temp_dir.path().join("dest");
let result = extract_tar_archive(&tar_path, &dest, None, |f| f);
let result = extract_tar_archive(&tar_path, &dest, &crate::report::Quiet, |f| f);
let err = match result {
Ok(_) => panic!("extraction of a traversal archive should fail"),
@@ -1169,7 +1161,7 @@ mod tests {
.unwrap();
let dest = temp_dir.path().join("dest");
let result = extract_tar_archive(&tar_path, &dest, None, |f| f);
let result = extract_tar_archive(&tar_path, &dest, &crate::report::Quiet, |f| f);
let err = match result {
Ok(_) => panic!("extraction of an absolute-path archive should fail"),
@@ -1202,7 +1194,7 @@ mod tests {
.unwrap();
let dest = temp_dir.path().join("dest");
extract_tar_archive(&tar_path, &dest, None, |f| f).unwrap();
extract_tar_archive(&tar_path, &dest, &crate::report::Quiet, |f| f).unwrap();
assert_eq!(
std::fs::read_to_string(dest.join("hello-1.0/file.txt")).unwrap(),
@@ -1235,7 +1227,7 @@ mod tests {
std::fs::write(&tar_path, tar_bytes).unwrap();
let dest = temp_dir.path().join("dest");
extract_tar_archive(&tar_path, &dest, None, |f| f).unwrap();
extract_tar_archive(&tar_path, &dest, &crate::report::Quiet, |f| f).unwrap();
assert_eq!(
std::fs::read_to_string(dest.join("pkg-1.0/docs/v1/manual.txt")).unwrap(),
@@ -1276,7 +1268,8 @@ mod tests {
std::fs::write(&tar_path, tar_bytes).unwrap();
let tree = temp_dir.path().join("tree");
let extracted = extract_tar_archive(&tar_path, &tree, None, |f| f).unwrap();
let extracted =
extract_tar_archive(&tar_path, &tree, &crate::report::Quiet, |f| f).unwrap();
unwrap_wrapper(&tree, &extracted.wrapper.unwrap()).unwrap();
assert!(!tree.join("pax_global_header").exists());
@@ -1297,7 +1290,7 @@ mod tests {
std::fs::write(&tar_path, tar_bytes).unwrap();
let dest = temp_dir.path().join("dest");
let result = extract_tar_archive(&tar_path, &dest, None, |f| f);
let result = extract_tar_archive(&tar_path, &dest, &crate::report::Quiet, |f| f);
let err = match result {
Ok(_) => panic!("extraction of an escaping hard link should fail"),
@@ -1332,7 +1325,8 @@ mod tests {
.unwrap();
let tree = temp_dir.path().join("tree");
let extracted = extract_tar_archive(&tar_path, &tree, None, |f| f).unwrap();
let extracted =
extract_tar_archive(&tar_path, &tree, &crate::report::Quiet, |f| f).unwrap();
unwrap_wrapper(&tree, &extracted.wrapper.unwrap()).unwrap();
assert_eq!(
@@ -1366,7 +1360,8 @@ mod tests {
std::fs::write(&tar_path, tar_bytes).unwrap();
let tree = temp_dir.path().join("tree");
let extracted = extract_tar_archive(&tar_path, &tree, None, |f| f).unwrap();
let extracted =
extract_tar_archive(&tar_path, &tree, &crate::report::Quiet, |f| f).unwrap();
unwrap_wrapper(&tree, &extracted.wrapper.unwrap()).unwrap();
let meta = std::fs::symlink_metadata(tree.join("README")).unwrap();
@@ -1392,7 +1387,8 @@ mod tests {
.unwrap();
let tree = temp_dir.path().join("tree");
let extracted = extract_tar_archive(&tar_path, &tree, None, |f| f).unwrap();
let extracted =
extract_tar_archive(&tar_path, &tree, &crate::report::Quiet, |f| f).unwrap();
assert!(extracted.wrapper.is_none());
assert!(tree.join("Makefile").exists());
@@ -1413,7 +1409,7 @@ mod tests {
let main = temp_dir.path().join("main.orig.tar");
std::fs::write(&main, build_tar(&[("dovecot-2.4.5/README", b"main")])).unwrap();
let extracted = extract_tar_archive(&main, &tree, None, |f| f).unwrap();
let extracted = extract_tar_archive(&main, &tree, &crate::report::Quiet, |f| f).unwrap();
unwrap_into(
&tree,
&extracted.wrapper.unwrap(),
@@ -1430,7 +1426,8 @@ mod tests {
]),
)
.unwrap();
let extracted = extract_tar_archive(&component, &tree, None, |f| f).unwrap();
let extracted =
extract_tar_archive(&component, &tree, &crate::report::Quiet, |f| f).unwrap();
unwrap_into(
&tree,
&extracted.wrapper.unwrap(),
@@ -1472,7 +1469,8 @@ mod tests {
.unwrap();
let tree = temp_dir.path().join("tree");
let extracted = extract_tar_archive(&tar_path, &tree, None, |f| f).unwrap();
let extracted =
extract_tar_archive(&tar_path, &tree, &crate::report::Quiet, |f| f).unwrap();
unwrap_into(
&tree,
&extracted.wrapper.unwrap(),
@@ -1512,8 +1510,13 @@ mod tests {
std::fs::write(&tar_path, BZ2_TAR_WRAPPED).unwrap();
let tree = temp_dir.path().join("tree");
let extracted =
extract_tar_archive(&tar_path, &tree, None, bzip2_rs::DecoderReader::new).unwrap();
let extracted = extract_tar_archive(
&tar_path,
&tree,
&crate::report::Quiet,
bzip2_rs::DecoderReader::new,
)
.unwrap();
unwrap_wrapper(&tree, &extracted.wrapper.unwrap()).unwrap();
assert_eq!(