pull: keep self-prefixed multi-orig components in place

A component tarball whose entries already carry the addon prefix
('www/...' in sqlite3's 'sqlite3_3.53.4.orig-www.tar.xz') extracts
to its end state, but the wrapper detection named it a wrapper and
unwrap_into then cleared the addon target - destroying the component
just extracted into it - and unwrapped the emptied directory into
itself: no 'www' at all, and the deb failed at dh_installdocs over
the missing 'www/*' docs.

dpkg-source unpacks a component into '<root>/<addon>': when the
tarball's own layout already is that, the extraction is the end
state; leave it alone.
This commit is contained in:
2026-10-02 02:23:22 +02:00
parent 14704db777
commit 0ef0505829
+45 -5
View File
@@ -167,19 +167,24 @@ fn component_addon(name: &str) -> Option<&str> {
/// `dir/<addon>` for a multi-orig component, replacing any pre-existing
/// addon directory.
fn unwrap_into(dir: &Path, wrapper: &Path, addon: Option<&str>) -> Result<(), Box<dyn Error>> {
let target = match addon {
match addon {
// dpkg-source unpacks a component into '<root>/<addon>': a tarball
// whose entries already carry that prefix ('www/...' in
// 'sqlite3_3.53.4.orig-www.tar.xz') extracts to its end state, and
// unwrapping it would clear the target and move the cleared
// directory into itself.
Some(addon) if wrapper.file_name().is_some_and(|name| name == addon) => Ok(()),
Some(addon) => {
let target = dir.join(addon);
if target.is_dir() {
std::fs::remove_dir_all(&target)?;
}
std::fs::create_dir_all(&target)?;
target
}
None => dir.to_path_buf(),
};
unwrap_wrapper(&target, wrapper)
}
None => unwrap_wrapper(dir, wrapper),
}
}
/// Move the contents of an archive's wrapper directory up into `dir` itself.
///
@@ -1450,6 +1455,41 @@ mod tests {
assert!(!tree.join("dovecot-pigeonhole-2.4.5").exists());
}
#[test]
fn test_unwrap_component_already_prefixed_with_addon() {
// Regression: a component tarball whose entries already carry the
// addon prefix ('www/...' in 'sqlite3_3.53.4.orig-www.tar.xz')
// extracts to its end state. It used to be treated as a wrapper of
// the same name as the addon: unwrap_into cleared the target —
// destroying the component just extracted into it — and unwrapped
// the emptied directory into itself, leaving no 'www' at all.
let temp_dir = tempfile::tempdir().unwrap();
let tar_path = temp_dir.path().join("pkg_1.0.orig-www.tar.gz");
std::fs::write(
&tar_path,
build_tar(&[("www/README", b"docs"), ("www/img/x", b"img")]),
)
.unwrap();
let tree = temp_dir.path().join("tree");
let extracted = extract_tar_archive(&tar_path, &tree, None, |f| f).unwrap();
unwrap_into(
&tree,
&extracted.wrapper.unwrap(),
component_addon("pkg_1.0.orig-www.tar.gz"),
)
.unwrap();
assert_eq!(
std::fs::read_to_string(tree.join("www/README")).unwrap(),
"docs"
);
assert_eq!(
std::fs::read_to_string(tree.join("www/img/x")).unwrap(),
"img"
);
}
/// A minimal ustar archive of `pkg-1.0/{Makefile,doc/readme}`,
/// bz2-compressed.
const BZ2_TAR_WRAPPED: &[u8] = &[