use std::path::{Component, Path};
use crate::object::{Blob, ContentHash, ObjectSource, Tree};
use crate::util::{ResourceBudget, ResourceKind};
use super::types::BlameSliceError;
pub(super) fn lookup_blob_at_path<S: ObjectSource>(
source: &S,
tree_hash: &ContentHash,
path: &Path,
) -> Result<Option<ContentHash>, BlameSliceError> {
let Some(tree) = source.get_tree(tree_hash)? else {
return Err(BlameSliceError::MissingObject {
kind: "tree",
id: tree_hash.to_string(),
});
};
walk_blob(source, tree, path)
}
fn walk_blob<S: ObjectSource>(
source: &S,
mut tree: Tree,
path: &Path,
) -> Result<Option<ContentHash>, BlameSliceError> {
let mut segments = Vec::new();
for component in path.components() {
match component {
Component::Normal(name) => {
let Some(name) = name.to_str() else {
return Ok(None);
};
segments.push(name.to_string());
}
Component::CurDir => {}
_ => return Ok(None),
}
}
if segments.is_empty() {
return Ok(None);
}
let last = segments.len() - 1;
for (index, name) in segments.iter().enumerate() {
let Some(entry) = tree.get(name).cloned() else {
return Ok(None);
};
if index == last {
return Ok(entry.blob_hash());
}
let Some(child_hash) = entry.tree_hash() else {
return Ok(None);
};
tree = source
.get_tree(&child_hash)?
.ok_or_else(|| BlameSliceError::MissingObject {
kind: "tree",
id: child_hash.to_string(),
})?;
}
Ok(None)
}
pub(super) fn load_blob_within_budget<S: ObjectSource>(
source: &S,
hash: &ContentHash,
budget: &mut ResourceBudget,
) -> Result<Blob, BlameSliceError> {
let Some(size) = source.decoded_blob_len(hash)? else {
return Err(BlameSliceError::MissingObject {
kind: "blob",
id: hash.to_string(),
});
};
budget.consume(ResourceKind::DecodedBytes, size)?;
let Some(blob) = source.get_blob(hash)? else {
return Err(BlameSliceError::MissingObject {
kind: "blob",
id: hash.to_string(),
});
};
if blob.content().len() as u64 != size {
return Err(BlameSliceError::InvalidFrontier(
"blob size changed between probe and load".into(),
));
}
Ok(blob)
}