feat(rust): extract .tgz archives (Rust-only short suffix)
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
//!
|
||||
//! Extraction backends: `sevenz-rust2` for .7z, `zip` for .zip, `unrar` for
|
||||
//! .rar — pure Rust, no external binaries. Tarballs and bare compressed
|
||||
//! files (.tar/.tar.gz/.tar.bz2/.tar.xz/.gz/.bz2/.xz) are handled by the
|
||||
//! files (.tar/.tar.gz/.tgz/.tar.bz2/.tar.xz/.gz/.bz2/.xz) are handled by the
|
||||
//! system `tar`/`gzip`/`bzip2`/`xz` binaries, exactly like the python
|
||||
//! version shelled out to `7z`: merged output capture, exit-code parity
|
||||
//! (0 ok, 1 warnings), and the 4h per-archive timeout. The tar/flate2/
|
||||
@@ -37,8 +37,11 @@ use std::time::{Duration, Instant};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
/// Archive suffixes recognized by the extractors (python `ARCHIVE_SUFFIXES`).
|
||||
pub const ARCHIVE_SUFFIXES: [&str; 7] = [".zip", ".7z", ".rar", ".tar", ".gz", ".bz2", ".xz"];
|
||||
/// Archive suffixes recognized by the extractors (python `ARCHIVE_SUFFIXES`,
|
||||
/// plus `.tgz` — a Rust-only extension; the python version never extracted
|
||||
/// those). `.tgz` is a gzip-compressed tar, so it maps onto the tarball path.
|
||||
pub const ARCHIVE_SUFFIXES: [&str; 8] =
|
||||
[".zip", ".7z", ".rar", ".tar", ".gz", ".bz2", ".xz", ".tgz"];
|
||||
|
||||
/// Stop extracting below this free space (python `MIN_FREE_BYTES`).
|
||||
pub const MIN_FREE_BYTES: u64 = 20 * 1024 * 1024 * 1024;
|
||||
@@ -388,6 +391,9 @@ impl Extractor {
|
||||
}
|
||||
}
|
||||
Some(".tar") => extract_tar_cmd(archive, &actual, None),
|
||||
// .tgz is a gzip-compressed tar: same as .tar.gz, but the tar flag
|
||||
// cannot be inferred from the name (Rust-only extension).
|
||||
Some(".tgz") => extract_tar_cmd(archive, &actual, Some(".gz")),
|
||||
_ => Err(format!("no extractor for {name}")),
|
||||
};
|
||||
match res {
|
||||
@@ -957,6 +963,10 @@ mod tests {
|
||||
let targz = gzip_stored(&make_ustar("delta.txt", b"tarred payload"));
|
||||
fs::write(s.join("logs.tar.gz"), targz).unwrap();
|
||||
|
||||
// a .tgz (gzip-compressed tar under the short suffix — Rust-only)
|
||||
let tgz = gzip_stored(&make_ustar("epsilon.txt", b"tgz payload"));
|
||||
fs::write(s.join("bundle.tgz"), tgz).unwrap();
|
||||
|
||||
// garbage zip -> must land in .broken
|
||||
fs::write(s.join("bad.zip"), b"this is not a zip archive at all").unwrap();
|
||||
|
||||
@@ -1081,6 +1091,10 @@ mod tests {
|
||||
fs::read_to_string(o.join("src1/logs/delta.txt")).unwrap(),
|
||||
"tarred payload"
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_to_string(o.join("src1/bundle/epsilon.txt")).unwrap(),
|
||||
"tgz payload"
|
||||
);
|
||||
|
||||
// no archives left in the extracted tree (outside .broken)
|
||||
for p in walk_files_sorted(o) {
|
||||
@@ -1164,6 +1178,7 @@ mod tests {
|
||||
"The Twin Lance",
|
||||
),
|
||||
("src1/logs.tar.gz", "src1/logs", "logs"),
|
||||
("src1/bundle.tgz", "src1/bundle", "bundle"),
|
||||
]
|
||||
.iter()
|
||||
.map(|(a, d, u)| (a.to_string(), d.to_string(), u.to_string()))
|
||||
@@ -1255,6 +1270,10 @@ mod tests {
|
||||
);
|
||||
assert_eq!(display_unit("Pack.zip"), "Pack");
|
||||
assert!(is_archive("c.7z.7z"));
|
||||
// .tgz: Rust-only short suffix for a gzip-compressed tar
|
||||
assert_eq!(archive_suffixes("x.tgz"), vec![".tgz"]);
|
||||
assert_eq!(display_unit("x.tgz"), "x");
|
||||
assert!(is_archive("X.TGZ"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user