-
-
Notifications
You must be signed in to change notification settings - Fork 339
/
Copy patharchive.rs
108 lines (98 loc) · 3.76 KB
/
archive.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use std::path::{Path, PathBuf};
use anyhow::{anyhow, bail};
use gix::{worktree::archive, NestedProgress, Progress};
pub struct Options {
pub format: Option<archive::Format>,
pub files: Vec<(String, String)>,
pub prefix: Option<String>,
pub add_paths: Vec<PathBuf>,
}
pub fn stream(
repo: gix::Repository,
destination_path: &Path,
rev_spec: Option<&str>,
mut progress: impl NestedProgress,
Options {
format,
prefix,
add_paths,
files,
}: Options,
) -> anyhow::Result<()> {
let format = format.map_or_else(|| format_from_ext(destination_path), Ok)?;
let object = repo.rev_parse_single(rev_spec.unwrap_or("HEAD"))?.object()?;
let (modification_date, tree) = fetch_rev_info(object)?;
let start = std::time::Instant::now();
let (mut stream, index) = repo.worktree_stream(tree)?;
if !add_paths.is_empty() {
let root = gix::path::realpath(
repo.workdir()
.ok_or_else(|| anyhow!("Adding files requires a worktree directory that contains them"))?,
)?;
for path in add_paths {
stream.add_entry_from_path(&root, &gix::path::realpath(&path)?)?;
}
}
for (path, content) in files {
stream.add_entry(gix::worktree::stream::AdditionalEntry {
id: gix::hash::Kind::Sha1.null(),
mode: gix::object::tree::EntryKind::Blob.into(),
relative_path: path.into(),
source: gix::worktree::stream::entry::Source::Memory(content.into()),
});
}
let mut entries = progress.add_child("entries");
entries.init(Some(index.entries().len()), gix::progress::count("entries"));
let mut bytes = progress.add_child("written");
bytes.init(None, gix::progress::bytes());
let mut file = gix::progress::Write {
inner: std::io::BufWriter::with_capacity(128 * 1024, std::fs::File::create(destination_path)?),
progress: &mut bytes,
};
repo.worktree_archive(
stream,
&mut file,
&mut entries,
&gix::interrupt::IS_INTERRUPTED,
gix::worktree::archive::Options {
format,
tree_prefix: prefix.map(gix::bstr::BString::from),
modification_time: modification_date.unwrap_or_else(|| {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or_default() as gix::date::SecondsSinceUnixEpoch
}),
},
)?;
entries.show_throughput(start);
bytes.show_throughput(start);
Ok(())
}
fn fetch_rev_info(
object: gix::Object<'_>,
) -> anyhow::Result<(Option<gix::date::SecondsSinceUnixEpoch>, gix::ObjectId)> {
Ok(match object.kind {
gix::object::Kind::Commit => {
let commit = object.into_commit();
(Some(commit.committer()?.seconds()), commit.tree_id()?.detach())
}
gix::object::Kind::Tree => (None, object.id),
gix::object::Kind::Tag => fetch_rev_info(object.peel_to_kind(gix::object::Kind::Commit)?)?,
gix::object::Kind::Blob => bail!("Cannot derive commit or tree from blob at {}", object.id),
})
}
fn format_from_ext(path: &Path) -> anyhow::Result<archive::Format> {
Ok(match path.extension().and_then(std::ffi::OsStr::to_str) {
None => bail!("Cannot derive archive format from a file without extension"),
Some("tar") => archive::Format::Tar,
Some("gz") => archive::Format::TarGz {
compression_level: None,
},
Some("zip") => archive::Format::Zip {
compression_level: None,
},
Some("stream") => archive::Format::InternalTransientNonPersistable,
Some(ext) => bail!("Format for extension '{ext}' is unsupported"),
})
}