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).
This commit is contained in:
2026-10-02 01:18:48 +02:00
parent 3227c0abb9
commit 69bb29a291
+47
View File
@@ -263,6 +263,19 @@ where
for entry in archive.entries()? { for entry in archive.entries()? {
let mut entry = entry?; 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(); let path = entry.path()?.to_path_buf();
// Archives built with './'-prefixed entries (common in third-party // 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] #[test]
fn test_extract_tar_rejects_hardlink_escape() { fn test_extract_tar_rejects_hardlink_escape() {
let temp_dir = tempfile::tempdir().unwrap(); let temp_dir = tempfile::tempdir().unwrap();