use std::path::Path;
use crate::{
object::{ObjectSource, State},
util::{ResourceBudget, ResourceKind, ResourceUsage},
};
use super::{
lookup::{load_blob_within_budget, lookup_blob_at_path},
mapping::identity_mapping,
types::{
BlameFrontierGroup, BlameFrontierRecord, BlamePreparation, BlameSliceError,
BlameSliceLimits, BlameTarget, origin_from_state,
},
};
fn count_lines(bytes: &[u8]) -> Result<usize, BlameSliceError> {
std::str::from_utf8(bytes)
.map(|text| text.lines().count())
.map_err(|_| BlameSliceError::Unblamable)
}
pub fn prepare_file_blame<S: ObjectSource>(
source: &S,
state: &State,
path: &Path,
limits: BlameSliceLimits,
) -> Result<BlamePreparation, BlameSliceError> {
let mut budget = ResourceBudget::new(ResourceUsage {
scratch_bytes: limits.scratch_bytes,
lines: limits.lines,
work: limits.diff_work,
states: limits.states,
decoded_bytes: limits.decoded_bytes,
});
budget.consume(ResourceKind::States, 1)?;
let Some(blob_hash) = lookup_blob_at_path(source, &state.tree, path)? else {
return Ok(BlamePreparation::MissingPath);
};
let blob = load_blob_within_budget(source, &blob_hash, &mut budget)?;
let Ok(line_count) = count_lines(blob.content()) else {
return Ok(BlamePreparation::Unblamable);
};
budget.require(ResourceKind::Lines, line_count as u64)?;
let origin = origin_from_state(state);
if line_count == 0 {
return Ok(BlamePreparation::Empty {
file_blob: blob_hash,
origin,
});
}
let line_count = u32::try_from(line_count).map_err(|_| BlameSliceError::Unblamable)?;
let target = BlameTarget::bind(state.id(), path, blob_hash, line_count)?;
Ok(BlamePreparation::Active {
file_blob: blob_hash,
line_count,
frontier: BlameFrontierGroup {
target: target.clone(),
records: vec![BlameFrontierRecord {
origin,
blob_hash,
state_line_count: line_count,
mappings: identity_mapping(line_count),
target,
}],
},
})
}