From 37e0b5c978601521f6b797d21f0c7fc5420f6d02 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Mon, 21 Sep 2026 11:28:55 +0200 Subject: [PATCH] pull: fetch every component tarball of multi-orig packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sources listed with "3.0 (quilt)" extra components (node-jest, php-*, ...) carry one tarball per bundled module next to the main orig, named _.orig-.tar.. fetch_orig_tarball picked the single file matching ".orig.tar." — which cannot even match the component naming — so a git pull only fetched the main orig. The later dpkg-source -b quilt verification then failed with "can't find file to patch" on the first patch touching a component directory. Select the files with the existing build::changes::is_orig_tarball helper (mirroring dpkg's \.orig(-.+)?\.tar\. strip pattern) and fetch all of them, pristine-tar checkout first with a checksummed archive download fallback, per tarball. The end-to-end test now asserts every stanza-listed orig lands in the package dir instead of just any *.orig.tar.* file, and gains a node-jest (trixie, 24 components) regression case. Verified live: pkh pull node-jest -d debian fetches all 15 origs of the sid ds7 repack, and dpkg-source -b builds the debian.tar.xz and dsc without touching the series. --- src/pull.rs | 108 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 42 deletions(-) diff --git a/src/pull.rs b/src/pull.rs index fc4c18f..7386ec2 100644 --- a/src/pull.rs +++ b/src/pull.rs @@ -452,50 +452,57 @@ async fn fetch_orig_tarball( Path::new(&info.stanza.package).to_path_buf() }; - // Find the orig tarball in the file list - // Usually ends with .orig.tar.gz or .orig.tar.xz - let orig_file = info + // Upstream tarballs in the file list: the main orig tarball plus, for + // multi-orig ("3.0 (quilt)" extra-component) sources, one component + // tarball per bundled module (`*.orig-.tar.`). dpkg-source + // unpacks all of them side by side, so a git pull must fetch them all. + let orig_files: Vec<_> = info .stanza .files .iter() - .find(|f| f.name.contains(".orig.tar.")) - .ok_or_else(|| { - format!( - "Could not find orig tarball in file list for package '{}'. \ - Available files: {:?}", - info.stanza.package, - info.stanza - .files - .iter() - .map(|f| &f.name) - .collect::>() - ) - })?; - let filename = &orig_file.name; + .filter(|f| crate::build::changes::is_orig_tarball(&f.name)) + .collect(); + if orig_files.is_empty() { + return Err(format!( + "Could not find orig tarball in file list for package '{}'. \ + Available files: {:?}", + info.stanza.package, + info.stanza + .files + .iter() + .map(|f| &f.name) + .collect::>() + ) + .into()); + } // 1. Try executing pristine-tar // Setup pristine-tar branch if needed (by tracking remote branch) let _ = setup_pristine_tar_branch(&package_dir, info.dist.as_str()); - if let Err(e) = checkout_pristine_tar(&package_dir, filename.as_str()) { - debug!( - "pristine-tar failed: {}. Falling back to archive download.", - e - ); + for orig_file in orig_files { + let filename = &orig_file.name; - // 2. Fallback to archive download - // We download to the parent directory of the package repo (which is standard for build tools) - // or the current directory if cwd is None (which effectively is the parent of the package dir) - let target_dir = cwd.unwrap_or_else(|| Path::new(".")); - download_file_checksum( - format!("{}/{}", info.archive_url, filename).as_str(), - &orig_file.checksum, - orig_file.checksum_algo, - target_dir, - progress, - ) - .await?; + if let Err(e) = checkout_pristine_tar(&package_dir, filename.as_str()) { + debug!( + "pristine-tar failed: {}. Falling back to archive download.", + e + ); + + // 2. Fallback to archive download + // We download to the parent directory of the package repo (which is standard for build tools) + // or the current directory if cwd is None (which effectively is the parent of the package dir) + let target_dir = cwd.unwrap_or_else(|| Path::new(".")); + download_file_checksum( + format!("{}/{}", info.archive_url, filename).as_str(), + &orig_file.checksum, + orig_file.checksum_algo, + target_dir, + progress, + ) + .await?; + } } Ok(()) @@ -844,23 +851,29 @@ mod tests { } } - // Check for orig tarball in package dir (only for non-native packages) - let mut found_tarball = false; + // Check for the orig tarballs in the package dir (only for non-native + // packages): every orig listed in the stanza must be present, including + // the component tarballs of multi-orig packages (dpkg-source needs them + // all to unpack the merged upstream tree) let mut found_dsc = false; - for entry in std::fs::read_dir(package_dir).unwrap() { + for entry in std::fs::read_dir(&package_dir).unwrap() { let entry = entry.unwrap(); let name = entry.file_name().to_string_lossy().to_string(); - if name.contains(".orig.tar.") { - found_tarball = true; - } if name.ends_with(".dsc") { found_dsc = true; } } - // Only check for orig tarball if the package is not native if !info.is_native() { - assert!(found_tarball, "Orig tarball not found in package dir"); + for file in &info.stanza.files { + if crate::build::changes::is_orig_tarball(&file.name) { + assert!( + package_dir.join(&file.name).exists(), + "Orig tarball '{}' not found in package dir", + file.name + ); + } + } } assert!(found_dsc, "DSC file not found in package dir"); } @@ -914,6 +927,17 @@ mod tests { test_pull_package_end_to_end("paraview", Some("noble"), None, None).await; } + /// Multi-orig ("3.0 (quilt)" extra component) regression test: node-jest + /// ships its bundled modules as separate `*.orig-.tar.xz` + /// tarballs next to the main orig. The git pull path must fetch every + /// component, or the later `dpkg-source -b` quilt verification fails + /// with "can't find file to patch" on the first patch touching a + /// component directory. + #[tokio::test] + async fn test_pull_node_jest_debian_end_to_end() { + test_pull_package_end_to_end("node-jest", Some("trixie"), None, None).await; + } + /// Build a minimal uncompressed ustar archive from (name, data) entries. /// /// Raw header blocks are crafted instead of using `tar::Builder` because