1use foldhash::HashMap;
2use foldhash::HashSet;
3
4use mago_database::file::FileId;
5
6use crate::differ::compute_file_diff;
7use crate::metadata::CodebaseMetadata;
8use crate::symbol::SymbolIdentifier;
9
10pub type DiffHunk = (usize, usize, isize, isize);
18
19pub type DeletionRange = (usize, usize);
25
26#[derive(Default, Debug, Clone, PartialEq, Eq)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34pub struct CodebaseDiff {
35 keep: HashSet<SymbolIdentifier>,
39
40 changed: HashSet<SymbolIdentifier>,
44
45 diff_map: HashMap<FileId, Vec<DiffHunk>>,
48
49 deletion_ranges_map: HashMap<FileId, Vec<DeletionRange>>,
52}
53
54impl CodebaseDiff {
55 #[inline]
56 #[must_use]
57 pub fn new() -> Self {
58 Self::default()
59 }
60
61 #[must_use]
68 pub fn between(old_metadata: &CodebaseMetadata, new_metadata: &CodebaseMetadata) -> Self {
69 let mut aggregate_diff = CodebaseDiff::new();
70
71 let mut all_file_ids = old_metadata.get_all_file_ids();
72 all_file_ids.extend(new_metadata.get_all_file_ids());
73 all_file_ids.sort();
74 all_file_ids.dedup();
75
76 for file_id in all_file_ids {
77 let old_sig = old_metadata.get_file_signature(&file_id);
78 let new_sig = new_metadata.get_file_signature(&file_id);
79
80 let file_diff = compute_file_diff(file_id, old_sig, new_sig);
81
82 aggregate_diff.extend(file_diff);
83 }
84
85 aggregate_diff
86 }
87
88 #[inline]
90 pub fn extend(&mut self, other: Self) {
91 self.keep.extend(other.keep);
92 self.changed.extend(other.changed);
93 for (source, diffs) in other.diff_map {
94 self.diff_map.entry(source).or_default().extend(diffs);
95 }
96 for (source, ranges) in other.deletion_ranges_map {
97 self.deletion_ranges_map.entry(source).or_default().extend(ranges);
98 }
99 }
100
101 #[inline]
103 #[must_use]
104 pub fn get_keep(&self) -> &HashSet<SymbolIdentifier> {
105 &self.keep
106 }
107
108 #[inline]
110 #[must_use]
111 pub fn get_changed(&self) -> &HashSet<SymbolIdentifier> {
112 &self.changed
113 }
114
115 #[inline]
117 #[must_use]
118 pub fn get_diff_map(&self) -> &HashMap<FileId, Vec<DiffHunk>> {
119 &self.diff_map
120 }
121
122 #[inline]
124 #[must_use]
125 pub fn get_deletion_ranges_map(&self) -> &HashMap<FileId, Vec<DeletionRange>> {
126 &self.deletion_ranges_map
127 }
128
129 #[inline]
131 pub fn set_keep(&mut self, keep_set: impl IntoIterator<Item = SymbolIdentifier>) {
132 self.keep = keep_set.into_iter().collect();
133 }
134
135 #[inline]
137 #[must_use]
138 pub fn with_keep(mut self, keep_set: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
139 self.set_keep(keep_set);
140 self
141 }
142
143 #[inline]
145 pub fn add_keep_entry(&mut self, entry: SymbolIdentifier) -> bool {
146 self.keep.insert(entry)
147 }
148
149 #[inline]
151 #[must_use]
152 pub fn with_added_keep_entry(mut self, entry: SymbolIdentifier) -> Self {
153 self.add_keep_entry(entry);
154 self
155 }
156
157 #[inline]
159 pub fn add_keep_entries(&mut self, entries: impl IntoIterator<Item = SymbolIdentifier>) {
160 self.keep.extend(entries);
161 }
162
163 #[inline]
165 #[must_use]
166 pub fn with_added_keep_entries(mut self, entries: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
167 self.add_keep_entries(entries);
168 self
169 }
170
171 #[inline]
173 pub fn unset_keep(&mut self) {
174 self.keep.clear();
175 }
176
177 #[inline]
179 #[must_use]
180 pub fn without_keep(mut self) -> Self {
181 self.unset_keep();
182 self
183 }
184
185 #[inline]
187 pub fn set_changed(&mut self, change_set: impl IntoIterator<Item = SymbolIdentifier>) {
188 self.changed = change_set.into_iter().collect();
189 }
190
191 #[inline]
193 #[must_use]
194 pub fn with_changed(mut self, change_set: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
195 self.set_changed(change_set);
196 self
197 }
198
199 #[inline]
201 pub fn add_changed_entry(&mut self, entry: SymbolIdentifier) -> bool {
202 self.changed.insert(entry)
203 }
204
205 #[inline]
207 #[must_use]
208 pub fn contains_changed_entry(&self, entry: &SymbolIdentifier) -> bool {
209 self.changed.contains(entry)
210 }
211
212 #[inline]
214 #[must_use]
215 pub fn with_added_changed_entry(mut self, entry: SymbolIdentifier) -> Self {
216 self.add_changed_entry(entry);
217 self
218 }
219
220 #[inline]
222 pub fn add_changed_entries(&mut self, entries: impl IntoIterator<Item = SymbolIdentifier>) {
223 self.changed.extend(entries);
224 }
225
226 #[inline]
228 #[must_use]
229 pub fn with_added_changed_entries(mut self, entries: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
230 self.add_changed_entries(entries);
231 self
232 }
233
234 #[inline]
236 pub fn unset_changed(&mut self) {
237 self.changed.clear();
238 }
239
240 #[inline]
242 #[must_use]
243 pub fn without_changed(mut self) -> Self {
244 self.unset_changed();
245 self
246 }
247
248 #[inline]
250 pub fn set_diff_map(&mut self, map: HashMap<FileId, Vec<DiffHunk>>) {
251 self.diff_map = map;
252 }
253
254 #[inline]
256 #[must_use]
257 pub fn with_diff_map(mut self, map: HashMap<FileId, Vec<DiffHunk>>) -> Self {
258 self.set_diff_map(map);
259 self
260 }
261
262 #[inline]
264 pub fn add_diff_map_entry(&mut self, source: FileId, diffs: Vec<DiffHunk>) -> Option<Vec<DiffHunk>> {
265 self.diff_map.insert(source, diffs)
266 }
267
268 #[inline]
270 #[must_use]
271 pub fn with_added_diff_map_entry(mut self, source: FileId, diffs: Vec<DiffHunk>) -> Self {
272 self.add_diff_map_entry(source, diffs);
273 self
274 }
275
276 #[inline]
278 pub fn add_diffs_for_source(&mut self, source: FileId, diffs: impl IntoIterator<Item = DiffHunk>) {
279 self.diff_map.entry(source).or_default().extend(diffs);
280 }
281
282 #[inline]
284 #[must_use]
285 pub fn with_added_diffs_for_source(mut self, source: FileId, diffs: impl IntoIterator<Item = DiffHunk>) -> Self {
286 self.add_diffs_for_source(source, diffs);
287 self
288 }
289
290 #[inline]
292 pub fn unset_diff_map(&mut self) {
293 self.diff_map.clear();
294 }
295
296 #[inline]
298 #[must_use]
299 pub fn without_diff_map(mut self) -> Self {
300 self.unset_diff_map();
301 self
302 }
303
304 #[inline]
306 pub fn set_deletion_ranges_map(&mut self, map: HashMap<FileId, Vec<DeletionRange>>) {
307 self.deletion_ranges_map = map;
308 }
309
310 #[inline]
312 #[must_use]
313 pub fn with_deletion_ranges_map(mut self, map: HashMap<FileId, Vec<DeletionRange>>) -> Self {
314 self.set_deletion_ranges_map(map);
315 self
316 }
317
318 #[inline]
320 pub fn add_deletion_ranges_entry(
321 &mut self,
322 source: FileId,
323 ranges: Vec<DeletionRange>,
324 ) -> Option<Vec<DeletionRange>> {
325 self.deletion_ranges_map.insert(source, ranges)
326 }
327
328 #[inline]
330 #[must_use]
331 pub fn with_added_deletion_ranges_entry(mut self, file: FileId, ranges: Vec<DeletionRange>) -> Self {
332 self.add_deletion_ranges_entry(file, ranges);
333 self
334 }
335
336 #[inline]
338 pub fn add_deletion_ranges_for_source(&mut self, file: FileId, ranges: impl IntoIterator<Item = (usize, usize)>) {
339 self.deletion_ranges_map.entry(file).or_default().extend(ranges);
340 }
341
342 #[inline]
344 #[must_use]
345 pub fn with_added_deletion_ranges_for_source(
346 mut self,
347 file: FileId,
348 ranges: impl IntoIterator<Item = (usize, usize)>,
349 ) -> Self {
350 self.add_deletion_ranges_for_source(file, ranges);
351 self
352 }
353
354 #[inline]
356 pub fn unset_deletion_ranges_map(&mut self) {
357 self.deletion_ranges_map.clear();
358 }
359
360 #[inline]
362 #[must_use]
363 pub fn without_deletion_ranges_map(mut self) -> Self {
364 self.unset_deletion_ranges_map();
365 self
366 }
367}