1use foldhash::HashMap;
2use foldhash::HashSet;
3use serde::Deserialize;
4use serde::Serialize;
5
6use mago_database::file::FileId;
7
8use crate::differ::compute_file_diff;
9use crate::metadata::CodebaseMetadata;
10use crate::symbol::SymbolIdentifier;
11
12pub type DiffHunk = (usize, usize, isize, isize);
20
21pub type DeletionRange = (usize, usize);
27
28#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35pub struct CodebaseDiff {
36 keep: HashSet<SymbolIdentifier>,
40
41 changed: HashSet<SymbolIdentifier>,
45
46 diff_map: HashMap<FileId, Vec<DiffHunk>>,
49
50 deletion_ranges_map: HashMap<FileId, Vec<DeletionRange>>,
53}
54
55impl CodebaseDiff {
56 #[inline]
57 #[must_use]
58 pub fn new() -> Self {
59 Self::default()
60 }
61
62 #[must_use]
69 pub fn between(old_metadata: &CodebaseMetadata, new_metadata: &CodebaseMetadata) -> Self {
70 let mut aggregate_diff = CodebaseDiff::new();
71
72 let mut all_file_ids = old_metadata.get_all_file_ids();
73 all_file_ids.extend(new_metadata.get_all_file_ids());
74 all_file_ids.sort();
75 all_file_ids.dedup();
76
77 for file_id in all_file_ids {
78 let old_sig = old_metadata.get_file_signature(&file_id);
79 let new_sig = new_metadata.get_file_signature(&file_id);
80
81 let file_diff = compute_file_diff(file_id, old_sig, new_sig);
82
83 aggregate_diff.extend(file_diff);
84 }
85
86 aggregate_diff
87 }
88
89 #[inline]
91 pub fn extend(&mut self, other: Self) {
92 self.keep.extend(other.keep);
93 self.changed.extend(other.changed);
94 for (source, diffs) in other.diff_map {
95 self.diff_map.entry(source).or_default().extend(diffs);
96 }
97 for (source, ranges) in other.deletion_ranges_map {
98 self.deletion_ranges_map.entry(source).or_default().extend(ranges);
99 }
100 }
101
102 #[inline]
104 #[must_use]
105 pub fn get_keep(&self) -> &HashSet<SymbolIdentifier> {
106 &self.keep
107 }
108
109 #[inline]
111 #[must_use]
112 pub fn get_changed(&self) -> &HashSet<SymbolIdentifier> {
113 &self.changed
114 }
115
116 #[inline]
118 #[must_use]
119 pub fn get_diff_map(&self) -> &HashMap<FileId, Vec<DiffHunk>> {
120 &self.diff_map
121 }
122
123 #[inline]
125 #[must_use]
126 pub fn get_deletion_ranges_map(&self) -> &HashMap<FileId, Vec<DeletionRange>> {
127 &self.deletion_ranges_map
128 }
129
130 #[inline]
132 pub fn set_keep(&mut self, keep_set: impl IntoIterator<Item = SymbolIdentifier>) {
133 self.keep = keep_set.into_iter().collect();
134 }
135
136 #[inline]
138 #[must_use]
139 pub fn with_keep(mut self, keep_set: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
140 self.set_keep(keep_set);
141 self
142 }
143
144 #[inline]
146 pub fn add_keep_entry(&mut self, entry: SymbolIdentifier) -> bool {
147 self.keep.insert(entry)
148 }
149
150 #[inline]
152 #[must_use]
153 pub fn with_added_keep_entry(mut self, entry: SymbolIdentifier) -> Self {
154 self.add_keep_entry(entry);
155 self
156 }
157
158 #[inline]
160 pub fn add_keep_entries(&mut self, entries: impl IntoIterator<Item = SymbolIdentifier>) {
161 self.keep.extend(entries);
162 }
163
164 #[inline]
166 #[must_use]
167 pub fn with_added_keep_entries(mut self, entries: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
168 self.add_keep_entries(entries);
169 self
170 }
171
172 #[inline]
174 pub fn unset_keep(&mut self) {
175 self.keep.clear();
176 }
177
178 #[inline]
180 #[must_use]
181 pub fn without_keep(mut self) -> Self {
182 self.unset_keep();
183 self
184 }
185
186 #[inline]
188 pub fn set_changed(&mut self, change_set: impl IntoIterator<Item = SymbolIdentifier>) {
189 self.changed = change_set.into_iter().collect();
190 }
191
192 #[inline]
194 #[must_use]
195 pub fn with_changed(mut self, change_set: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
196 self.set_changed(change_set);
197 self
198 }
199
200 #[inline]
202 pub fn add_changed_entry(&mut self, entry: SymbolIdentifier) -> bool {
203 self.changed.insert(entry)
204 }
205
206 #[inline]
208 #[must_use]
209 pub fn contains_changed_entry(&self, entry: &SymbolIdentifier) -> bool {
210 self.changed.contains(entry)
211 }
212
213 #[inline]
215 #[must_use]
216 pub fn with_added_changed_entry(mut self, entry: SymbolIdentifier) -> Self {
217 self.add_changed_entry(entry);
218 self
219 }
220
221 #[inline]
223 pub fn add_changed_entries(&mut self, entries: impl IntoIterator<Item = SymbolIdentifier>) {
224 self.changed.extend(entries);
225 }
226
227 #[inline]
229 #[must_use]
230 pub fn with_added_changed_entries(mut self, entries: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
231 self.add_changed_entries(entries);
232 self
233 }
234
235 #[inline]
237 pub fn unset_changed(&mut self) {
238 self.changed.clear();
239 }
240
241 #[inline]
243 #[must_use]
244 pub fn without_changed(mut self) -> Self {
245 self.unset_changed();
246 self
247 }
248
249 #[inline]
251 pub fn set_diff_map(&mut self, map: HashMap<FileId, Vec<DiffHunk>>) {
252 self.diff_map = map;
253 }
254
255 #[inline]
257 #[must_use]
258 pub fn with_diff_map(mut self, map: HashMap<FileId, Vec<DiffHunk>>) -> Self {
259 self.set_diff_map(map);
260 self
261 }
262
263 #[inline]
265 pub fn add_diff_map_entry(&mut self, source: FileId, diffs: Vec<DiffHunk>) -> Option<Vec<DiffHunk>> {
266 self.diff_map.insert(source, diffs)
267 }
268
269 #[inline]
271 #[must_use]
272 pub fn with_added_diff_map_entry(mut self, source: FileId, diffs: Vec<DiffHunk>) -> Self {
273 self.add_diff_map_entry(source, diffs);
274 self
275 }
276
277 #[inline]
279 pub fn add_diffs_for_source(&mut self, source: FileId, diffs: impl IntoIterator<Item = DiffHunk>) {
280 self.diff_map.entry(source).or_default().extend(diffs);
281 }
282
283 #[inline]
285 #[must_use]
286 pub fn with_added_diffs_for_source(mut self, source: FileId, diffs: impl IntoIterator<Item = DiffHunk>) -> Self {
287 self.add_diffs_for_source(source, diffs);
288 self
289 }
290
291 #[inline]
293 pub fn unset_diff_map(&mut self) {
294 self.diff_map.clear();
295 }
296
297 #[inline]
299 #[must_use]
300 pub fn without_diff_map(mut self) -> Self {
301 self.unset_diff_map();
302 self
303 }
304
305 #[inline]
307 pub fn set_deletion_ranges_map(&mut self, map: HashMap<FileId, Vec<DeletionRange>>) {
308 self.deletion_ranges_map = map;
309 }
310
311 #[inline]
313 #[must_use]
314 pub fn with_deletion_ranges_map(mut self, map: HashMap<FileId, Vec<DeletionRange>>) -> Self {
315 self.set_deletion_ranges_map(map);
316 self
317 }
318
319 #[inline]
321 pub fn add_deletion_ranges_entry(
322 &mut self,
323 source: FileId,
324 ranges: Vec<DeletionRange>,
325 ) -> Option<Vec<DeletionRange>> {
326 self.deletion_ranges_map.insert(source, ranges)
327 }
328
329 #[inline]
331 #[must_use]
332 pub fn with_added_deletion_ranges_entry(mut self, file: FileId, ranges: Vec<DeletionRange>) -> Self {
333 self.add_deletion_ranges_entry(file, ranges);
334 self
335 }
336
337 #[inline]
339 pub fn add_deletion_ranges_for_source(&mut self, file: FileId, ranges: impl IntoIterator<Item = (usize, usize)>) {
340 self.deletion_ranges_map.entry(file).or_default().extend(ranges);
341 }
342
343 #[inline]
345 #[must_use]
346 pub fn with_added_deletion_ranges_for_source(
347 mut self,
348 file: FileId,
349 ranges: impl IntoIterator<Item = (usize, usize)>,
350 ) -> Self {
351 self.add_deletion_ranges_for_source(file, ranges);
352 self
353 }
354
355 #[inline]
357 pub fn unset_deletion_ranges_map(&mut self) {
358 self.deletion_ranges_map.clear();
359 }
360
361 #[inline]
363 #[must_use]
364 pub fn without_deletion_ranges_map(mut self) -> Self {
365 self.unset_deletion_ranges_map();
366 self
367 }
368}