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