1use std::path::Path;
5
6use crate::{
7 object::{ObjectSource, State},
8 util::{ResourceBudget, ResourceKind, ResourceUsage},
9};
10
11use super::{
12 lookup::{load_blob_within_budget, lookup_blob_at_path},
13 mapping::identity_mapping,
14 types::{
15 BlameFrontierGroup, BlameFrontierRecord, BlamePreparation, BlameSliceError,
16 BlameSliceLimits, BlameTarget, origin_from_state,
17 },
18};
19
20fn count_lines(bytes: &[u8]) -> Result<usize, BlameSliceError> {
22 std::str::from_utf8(bytes)
23 .map(|text| text.lines().count())
24 .map_err(|_| BlameSliceError::Unblamable)
25}
26
27pub fn prepare_file_blame<S: ObjectSource>(
29 source: &S,
30 state: &State,
31 path: &Path,
32 limits: BlameSliceLimits,
33) -> Result<BlamePreparation, BlameSliceError> {
34 let mut budget = ResourceBudget::new(ResourceUsage {
35 scratch_bytes: limits.scratch_bytes,
36 lines: limits.lines,
37 work: limits.diff_work,
38 states: limits.states,
39 decoded_bytes: limits.decoded_bytes,
40 });
41 budget.consume(ResourceKind::States, 1)?;
42
43 let Some(blob_hash) = lookup_blob_at_path(source, &state.tree, path)? else {
44 return Ok(BlamePreparation::MissingPath);
45 };
46 let blob = load_blob_within_budget(source, &blob_hash, &mut budget)?;
47 let Ok(line_count) = count_lines(blob.content()) else {
48 return Ok(BlamePreparation::Unblamable);
49 };
50 budget.require(ResourceKind::Lines, line_count as u64)?;
51
52 let origin = origin_from_state(state);
53 if line_count == 0 {
54 return Ok(BlamePreparation::Empty {
55 file_blob: blob_hash,
56 origin,
57 });
58 }
59
60 let line_count = u32::try_from(line_count).map_err(|_| BlameSliceError::Unblamable)?;
61 let target = BlameTarget::bind(state.id(), path, blob_hash, line_count)?;
62 Ok(BlamePreparation::Active {
63 file_blob: blob_hash,
64 line_count,
65 frontier: BlameFrontierGroup {
66 target: target.clone(),
67 records: vec![BlameFrontierRecord {
68 origin,
69 blob_hash,
70 state_line_count: line_count,
71 mappings: identity_mapping(line_count),
72 target,
73 }],
74 },
75 })
76}