pull: always extract tars when archive pulling, merging dirs
Some checks failed
CI / build (push) Failing after 3m18s

This commit is contained in:
2026-01-13 23:22:55 +01:00
parent b4a60e2ae2
commit d7a99f77f5

View File

@@ -1,6 +1,7 @@
use std::cmp::min;
use std::error::Error;
use std::path::Path;
use std::path::PathBuf;
use crate::package_info::PackageInfo;
@@ -110,23 +111,71 @@ fn copy_dir_all(src: &Path, dst: &Path) -> Result<(), Box<dyn Error>> {
Ok(())
}
fn extract_archive(path: &Path, dest: &Path) -> Result<(), Box<dyn Error>> {
let file = File::open(path)?;
/// Helper function to extract tar archive with progress tracking
fn extract_tar_archive<D, F>(
file_path: &Path,
dest: &Path,
progress: ProgressCallback<'_>,
decoder_factory: F,
) -> Result<Vec<String>, Box<dyn Error>>
where
D: std::io::Read,
F: Fn(File) -> D,
{
let file = File::open(file_path)?;
let decoder = decoder_factory(file);
let mut archive = Archive::new(decoder);
// Get total number of entries for progress tracking
let total_entries = archive.entries()?.count();
let mut current_entry = 0;
// Reset the archive to read entries again
let file = File::open(file_path)?;
let decoder = decoder_factory(file);
let mut archive = Archive::new(decoder);
let mut extracted_files = Vec::new();
for entry in archive.entries()? {
let mut entry = entry?;
let path = entry.path()?.to_path_buf();
let dest_path = dest.join(&path);
// Create parent directories if needed
if let Some(parent) = dest_path.parent() {
std::fs::create_dir_all(parent)?;
}
// Extract the file
entry.unpack(&dest_path)?;
extracted_files.push(dest_path.to_string_lossy().to_string());
current_entry += 1;
// Report progress
if let Some(cb) = progress {
cb("", "Extracting...", current_entry, total_entries);
}
}
Ok(extracted_files)
}
fn extract_archive(
path: &Path,
dest: &Path,
progress: ProgressCallback<'_>,
) -> Result<Vec<String>, Box<dyn Error>> {
let filename = path.file_name().unwrap().to_string_lossy();
if filename.ends_with(".tar.gz") || filename.ends_with(".tgz") {
let tar = GzDecoder::new(file);
let mut archive = Archive::new(tar);
archive.unpack(dest)?;
extract_tar_archive(path, dest, progress, GzDecoder::new)
} else if filename.ends_with(".tar.xz") || filename.ends_with(".txz") {
let tar = XzDecoder::new(file);
let mut archive = Archive::new(tar);
archive.unpack(dest)?;
extract_tar_archive(path, dest, progress, XzDecoder::new)
} else {
return Err(format!("Unsupported archive format: {}", filename).into());
Err(format!("Unsupported archive format: {}", filename).into())
}
Ok(())
}
fn checkout_pristine_tar(package_dir: &Path, filename: &str) -> Result<(), Box<dyn Error>> {
@@ -326,75 +375,59 @@ async fn fetch_archive_sources(
for file in &info.stanza.files {
let url = format!("{}/{}", info.archive_url, file.name);
download_file_checksum(&url, &file.sha256, package_dir, progress).await?;
}
// Extract the debian tarball or diff
let debian_file = info
.stanza
.files
.iter()
.find(|f| f.name.contains(".debian.tar.") || f.name.contains(".diff.gz"));
// Extract all tar archives, merging extracted directories
if file.name.contains(".tar.") {
let path = package_dir.join(&file.name);
let extract_dir = package_dir.join(&info.stanza.package);
if let Some(file) = debian_file {
let path = package_dir.join(&file.name);
let extract_dir = package_dir.join(&info.stanza.package);
let extracted = extract_archive(&path, &extract_dir, progress)?;
if (file.name.ends_with(".tar.xz") || file.name.ends_with(".tar.gz"))
&& let Err(e) = extract_archive(&path, &extract_dir)
{
return Err(format!("Failed to extract {}: {}", file.name, e).into());
}
// Special case: the debian tar does only contain 'debian'
if file.name.contains("debian.tar.") {
continue;
}
// Remove archive after extraction
std::fs::remove_file(&path)?;
}
// Extract the orig tarball if present and not native package
if !info.is_native()
&& let Some(orig_file) = info
.stanza
.files
.iter()
.find(|f| f.name.contains(".orig.tar."))
{
let path = package_dir.join(&orig_file.name);
let extract_dir = package_dir;
if (orig_file.name.ends_with(".tar.xz") || orig_file.name.ends_with(".tar.gz"))
&& let Err(e) = extract_archive(&path, extract_dir)
{
return Err(format!("Failed to extract {}: {}", orig_file.name, e).into());
}
// Rename from 'package-origversion' to 'package', merging with existing directory
let target_dir = package_dir.join(&info.stanza.package);
let entries = std::fs::read_dir(package_dir)?;
for entry in entries {
let entry = entry?;
let entry_path = entry.path();
if entry_path.is_dir() {
let dir_name = entry.file_name().to_string_lossy().to_string();
if dir_name.starts_with(&format!("{}-", info.stanza.package)) {
// Found a directory like 'package-version', rename it to 'package'
if target_dir.exists() {
// Target exists, we need to merge contents
for sub_entry in std::fs::read_dir(&entry_path)? {
let sub_entry = sub_entry?;
let sub_path = sub_entry.path();
let target_path = target_dir.join(sub_entry.file_name());
if sub_path.is_dir() {
std::fs::create_dir_all(&target_path)?;
// Recursively copy directory contents
copy_dir_all(&sub_path, &target_path)?;
} else {
std::fs::copy(&sub_path, &target_path)?;
}
}
std::fs::remove_dir_all(&entry_path)?;
} else {
std::fs::rename(&entry_path, &target_dir)?;
// List root directories extracted and use the first one as the source directory
debug!("Root directories extracted:");
let mut source_dir: Option<PathBuf> = None;
for file in &extracted {
let path = Path::new(file);
// Check if this is a directory and is at the archive root level
// (i.e., the path relative to extract_dir has no parent components)
if let Ok(relative_path) = path.strip_prefix(&extract_dir)
&& relative_path.components().count() == 1
&& path.is_dir()
{
debug!("- {}", relative_path.file_name().unwrap().to_string_lossy());
// Use the first directory found as the source
if source_dir.is_none() {
source_dir = Some(path.to_path_buf());
}
break;
}
}
// Use the extracted directory as the source, assuming there is only one
if let Some(src_dir) = source_dir {
let target_dir = package_dir.join(&info.stanza.package);
if target_dir.exists() {
// Target exists, we need to merge contents
for sub_entry in std::fs::read_dir(&src_dir)? {
let sub_entry = sub_entry?;
let sub_path = sub_entry.path();
let target_path = target_dir.join(sub_entry.file_name());
if sub_path.is_dir() {
std::fs::create_dir_all(&target_path)?;
// Recursively copy directory contents
copy_dir_all(&sub_path, &target_path)?;
} else {
std::fs::copy(&sub_path, &target_path)?;
}
}
std::fs::remove_dir_all(&src_dir)?;
} else {
std::fs::rename(&src_dir, &target_dir)?;
}
}
}