From 69bb29a2914fe072fdc19e029447170cc410ac19 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Fri, 2 Oct 2026 01:18:48 +0200 Subject: [PATCH] pull: ignore the pax global header when detecting a tarball wrapper Git-archive tarballs open with a pax global header record ('pax_global_header'): GNU tar consumes it and never lists or extracts it, but the tar crate yields it as an entry. Counting it for the wrapper detection made every git-archive orig look multi-rooted - linux-riscv's orig stayed nested under 'linux-6.8/' and its .diff.gz failed to apply with 'can't find file to patch'. Skip the record outright: unpack would no-op on it anyway, so this only keeps it out of the wrapper detection (and the tree). --- src/pull.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/pull.rs b/src/pull.rs index 32c02cf..47a455b 100644 --- a/src/pull.rs +++ b/src/pull.rs @@ -263,6 +263,19 @@ where for entry in archive.entries()? { let mut entry = entry?; + + // A pax global header record ('pax_global_header', the opening + // entry of git-archive tarballs) is metadata about the archive, + // not content: GNU tar consumes it and never lists or extracts + // it, but the tar crate yields it as an entry. It must not be + // extracted (unpack would no-op) and must not count for the + // wrapper detection below — it used to make every git-archive + // orig look multi-rooted, skipping the unwrap and leaving the + // sources nested one level too deep. + if entry.header().entry_type().is_pax_global_extensions() { + continue; + } + let path = entry.path()?.to_path_buf(); // Archives built with './'-prefixed entries (common in third-party @@ -1235,6 +1248,40 @@ mod tests { ); } + #[test] + fn test_extract_tar_skips_pax_global_header() { + // Regression: git-archive tarballs open with a pax global header + // record ('pax_global_header'): GNU tar consumes it and never + // lists or extracts it, but the tar crate yields it as an entry. + // Counting it for the wrapper detection made every git-archive + // orig look multi-rooted: the unwrap was skipped, the sources + // stayed nested one level too deep and the package's .diff.gz + // did not apply (linux-riscv). + let temp_dir = tempfile::tempdir().unwrap(); + let tar_path = temp_dir.path().join("git.orig.tar"); + let mut tar_bytes = Vec::new(); + push_tar_entry( + &mut tar_bytes, + "pax_global_header", + b'g', + b"52 comment=d0e1c2d", + None, + ); + push_tar_entry(&mut tar_bytes, "pkg-1.0/Makefile", b'0', b"all:", None); + tar_bytes.extend_from_slice(&[0u8; 1024]); + 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(); + unwrap_wrapper(&tree, &extracted.wrapper.unwrap()).unwrap(); + + assert!(!tree.join("pax_global_header").exists()); + assert_eq!( + std::fs::read_to_string(tree.join("Makefile")).unwrap(), + "all:" + ); + } + #[test] fn test_extract_tar_rejects_hardlink_escape() { let temp_dir = tempfile::tempdir().unwrap();