Files
sausage/userland/git/src/archive.rs
T

87 lines
3.5 KiB
Rust

//! Export a committed tree or literal subtree as tar, without an index or worktree.
use crate::{Result, repo::error};
use std::{io::Write, path::Path};
pub(crate) fn archive(
repo: &gix::Repository,
revision: &str,
path: Option<&str>,
output: Option<&Path>,
) -> Result<()> {
if let Some(path) = path {
crate::repo::validate_path(path)?;
}
let root = crate::local::tree(repo, revision)?;
let writer: Box<dyn Write> = match output {
Some(path) => {
Box::new(std::fs::File::create(path).map_err(|e| error("archive output", e))?)
}
None => Box::new(std::io::stdout().lock()),
};
let mut tar = tar::Builder::new(writer);
fn walk(
repo: &gix::Repository,
id: gix::ObjectId,
prefix: &str,
filter: Option<&str>,
tar: &mut tar::Builder<Box<dyn Write>>,
) -> Result<bool> {
let tree = repo.find_tree(id).map_err(|e| error("archive tree", e))?;
let mut found = filter.is_none();
for entry in tree.iter() {
let entry = entry.map_err(|e| error("archive entry", e))?;
let path = format!("{prefix}{}", entry.filename());
let selected = filter.is_none_or(|f| path == f || path.starts_with(&format!("{f}/")));
let ancestor = filter.is_some_and(|f| f.starts_with(&format!("{path}/")));
if !selected && !ancestor {
continue;
}
found |= selected;
if entry.mode().is_tree() {
if selected {
let mut header = tar::Header::new_gnu();
header.set_entry_type(tar::EntryType::Directory);
header.set_mode(0o755);
header.set_size(0);
tar.append_data(&mut header, &path, std::io::empty())
.map_err(|e| error("archive directory", e))?;
}
found |= walk(
repo,
entry.oid().to_owned(),
&format!("{path}/"),
filter,
tar,
)?;
} else if selected && !entry.mode().is_commit() {
let object = repo
.find_blob(entry.oid())
.map_err(|e| error("archive blob", e))?;
let mut header = tar::Header::new_gnu();
header.set_mode(entry.mode().kind() as u32 & 0o777);
if entry.mode().is_link() {
header.set_entry_type(tar::EntryType::Symlink);
header.set_size(0);
header
.set_link_name(
std::str::from_utf8(&object.data)
.map_err(|e| error("archive link", e))?,
)
.map_err(|e| error("archive link", e))?;
tar.append_data(&mut header, &path, std::io::empty())
.map_err(|e| error("archive link", e))?;
} else {
header.set_size(object.data.len() as u64);
tar.append_data(&mut header, &path, object.data.as_slice())
.map_err(|e| error("archive file", e))?;
}
}
}
Ok(found)
}
if !walk(repo, root, "", path, &mut tar)? {
return Err(error("archive", "path did not match any files"));
}
tar.finish().map_err(|e| error("archive finish", e))
}