Snapshot only committed extensions and publish the last-good marker atomically
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
//! 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))
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Local Git operations adapted from the v2 gix plugin, without RPC or host imports.
|
||||
#![allow(dead_code)]
|
||||
mod archive;
|
||||
mod branches;
|
||||
mod changes;
|
||||
mod checkout;
|
||||
@@ -78,6 +79,14 @@ pub struct Cli {
|
||||
}
|
||||
#[derive(Subcommand)]
|
||||
enum Command {
|
||||
Archive {
|
||||
#[arg(long, default_value = "tar", value_parser = ["tar"])]
|
||||
format: String,
|
||||
#[arg(short = 'o', long)]
|
||||
output: Option<std::path::PathBuf>,
|
||||
revision: String,
|
||||
path: Option<String>,
|
||||
},
|
||||
Show {
|
||||
#[arg(short = 's', long = "no-patch")]
|
||||
no_patch: bool,
|
||||
@@ -1501,6 +1510,14 @@ pub fn run(cli: Cli) -> Result<i32> {
|
||||
)?;
|
||||
}
|
||||
},
|
||||
Command::Archive {
|
||||
format: _,
|
||||
output,
|
||||
revision,
|
||||
path,
|
||||
} => {
|
||||
archive::archive(&repository, &revision, path.as_deref(), output.as_deref())?;
|
||||
}
|
||||
Command::CatFile {
|
||||
kind,
|
||||
pretty: _,
|
||||
|
||||
Reference in New Issue
Block a user