Skip to main content

objects/blame/
prepare.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Resolve the target path into the first frontier or a terminal reason.
3
4use 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
20// Local line count so prepare does not own strings.
21fn 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
27/// Build the first frontier for `path` at `state` without walking parents.
28pub 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}