use std::collections::BTreeMap;
use crate::{EntryKind, GitError, ObjectId, Repository, Result};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SnapshotEntry {
pub path: Vec<u8>,
pub mode: u32,
pub id: ObjectId,
pub kind: EntryKind,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CommitSnapshot {
pub commit: ObjectId,
pub tree: ObjectId,
pub entries: Vec<SnapshotEntry>,
}
impl Repository {
pub fn snapshot(&self, revision: &str) -> Result<CommitSnapshot> {
at(self, revision)
}
pub fn tree_manifest(&self, tree: ObjectId) -> Result<Vec<SnapshotEntry>> {
manifest(self, tree)
}
}
pub(crate) fn at(repository: &Repository, revision: &str) -> Result<CommitSnapshot> {
let commit = repository.resolve(revision)?;
let tree = repository.commit_metadata(commit)?.tree;
Ok(CommitSnapshot {
commit,
tree,
entries: manifest(repository, tree)?,
})
}
pub(crate) fn manifest(repository: &Repository, tree: ObjectId) -> Result<Vec<SnapshotEntry>> {
let mut entries = BTreeMap::new();
flatten(repository, tree, &[], 0, &mut entries)?;
Ok(entries.into_values().collect())
}
fn flatten(
repository: &Repository,
tree: ObjectId,
prefix: &[u8],
depth: usize,
output: &mut BTreeMap<Vec<u8>, SnapshotEntry>,
) -> Result<()> {
if depth >= repository.limits().max_tree_depth {
return Err(GitError::LimitExceeded {
resource: "tree depth",
limit: repository.limits().max_tree_depth,
});
}
for entry in repository.tree(tree)?.entries {
let path = join(prefix, &entry.name);
if entry.kind == EntryKind::Tree {
flatten(repository, entry.id, &path, depth + 1, output)?;
continue;
}
if output.len() >= repository.limits().max_tree_entries {
return Err(GitError::LimitExceeded {
resource: "snapshot entries",
limit: repository.limits().max_tree_entries,
});
}
output.insert(
path.clone(),
SnapshotEntry {
path,
mode: entry.mode,
id: entry.id,
kind: entry.kind,
},
);
}
Ok(())
}
fn join(prefix: &[u8], name: &[u8]) -> Vec<u8> {
let mut path = Vec::with_capacity(prefix.len() + name.len() + usize::from(!prefix.is_empty()));
if !prefix.is_empty() {
path.extend_from_slice(prefix);
path.push(b'/');
}
path.extend_from_slice(name);
path
}