1use std::{cell::RefCell, collections::HashMap, path::Path};
5
6use crate::object::{Blob, ContentHash, FileProvenance, ObjectSource, State, StateId, Tree};
7
8use super::{
9 advance::advance_file_blame_slice,
10 finalize::finalize_file_provenance,
11 prepare::prepare_file_blame,
12 types::{
13 BlamePreparation, BlameSliceAdvance, BlameSliceError, BlameSliceLimits, BlameTarget,
14 OriginRange,
15 },
16};
17
18struct BlameObjectCache<'source, S> {
25 source: &'source S,
26 trees: RefCell<HashMap<ContentHash, Option<Tree>>>,
27 states: RefCell<HashMap<StateId, Option<State>>>,
28 blobs: RefCell<HashMap<ContentHash, Option<Blob>>>,
29}
30
31impl<'source, S> BlameObjectCache<'source, S> {
32 fn new(source: &'source S) -> Self {
33 Self {
34 source,
35 trees: RefCell::new(HashMap::new()),
36 states: RefCell::new(HashMap::new()),
37 blobs: RefCell::new(HashMap::new()),
38 }
39 }
40}
41
42impl<S: ObjectSource> ObjectSource for BlameObjectCache<'_, S> {
43 fn get_tree(&self, hash: &ContentHash) -> crate::error::Result<Option<Tree>> {
44 if let Some(tree) = self.trees.borrow().get(hash).cloned() {
45 return Ok(tree);
46 }
47 let tree = self.source.get_tree(hash)?;
48 self.trees.borrow_mut().insert(*hash, tree.clone());
49 Ok(tree)
50 }
51
52 fn get_state(&self, id: &StateId) -> crate::error::Result<Option<State>> {
53 if let Some(state) = self.states.borrow().get(id).cloned() {
54 return Ok(state);
55 }
56 let state = self.source.get_state(id)?;
57 self.states.borrow_mut().insert(*id, state.clone());
58 Ok(state)
59 }
60
61 fn get_blob(&self, hash: &ContentHash) -> crate::error::Result<Option<Blob>> {
62 if let Some(blob) = self.blobs.borrow().get(hash).cloned() {
63 return Ok(blob);
64 }
65 let blob = self.source.get_blob(hash)?;
66 self.blobs.borrow_mut().insert(*hash, blob.clone());
67 Ok(blob)
68 }
69
70 fn decoded_blob_len(&self, hash: &ContentHash) -> crate::error::Result<Option<u64>> {
71 if let Some(blob) = self.blobs.borrow().get(hash) {
72 return Ok(blob.as_ref().map(|blob| blob.content().len() as u64));
73 }
74 self.source.decoded_blob_len(hash)
75 }
76}
77
78pub fn blame_file<S: ObjectSource>(
82 source: &S,
83 state: &State,
84 path: &Path,
85 limits: BlameSliceLimits,
86) -> Result<FileProvenance, BlameSliceError> {
87 let source = BlameObjectCache::new(source);
88 match prepare_file_blame(&source, state, path, limits)? {
89 BlamePreparation::MissingPath => Err(BlameSliceError::MissingPath),
90 BlamePreparation::Unblamable => Err(BlameSliceError::Unblamable),
91 BlamePreparation::Empty { file_blob, origin } => finalize_file_provenance(
92 file_blob,
93 0,
94 [OriginRange {
95 target_start: 0,
96 len: 0,
97 origin,
98 }],
99 ),
100 BlamePreparation::Active {
101 file_blob,
102 line_count,
103 mut frontier,
104 } => {
105 let expected = BlameTarget::bind(state.id(), path, file_blob, line_count)?;
106 frontier.require_target(&expected)?;
107 let mut finalized = Vec::new();
108 loop {
109 frontier.require_target(&expected)?;
110 match advance_file_blame_slice(&source, path, frontier, limits)? {
111 BlameSliceAdvance::Progress {
112 next,
113 finalized: more,
114 ..
115 } => {
116 finalized.extend(more);
117 frontier = next;
118 }
119 BlameSliceAdvance::Complete {
120 finalized: more, ..
121 } => {
122 finalized.extend(more);
123 break;
124 }
125 }
126 }
127 finalize_file_provenance(file_blob, line_count, finalized)
128 }
129 }
130}