Skip to main content

mago_codex/
diff.rs

1use foldhash::HashSet;
2
3use crate::differ::compute_file_diff;
4use crate::metadata::CodebaseMetadata;
5use crate::symbol::SymbolIdentifier;
6
7/// Represents the differences between two states of a codebase, typically used for incremental analysis.
8///
9/// This structure uses a single fingerprint hash per symbol to determine changes. Any change to a symbol
10/// (signature, body, modifiers, attributes) produces a different hash, triggering re-analysis.
11///
12/// Provides a comprehensive API for modification and querying following established conventions.
13#[derive(Default, Debug, Clone, PartialEq, Eq)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub struct CodebaseDiff {
16    /// Set of `(Symbol, Member)` pairs whose fingerprint hash is UNCHANGED.
17    /// These symbols can be safely skipped during re-analysis.
18    /// Member is empty for top-level symbols.
19    keep: HashSet<SymbolIdentifier>,
20
21    /// Set of `(Symbol, Member)` pairs that are new, deleted, or have a different fingerprint hash.
22    /// These symbols MUST be re-analyzed.
23    /// Member is empty for top-level symbols.
24    changed: HashSet<SymbolIdentifier>,
25}
26
27impl CodebaseDiff {
28    #[inline]
29    #[must_use]
30    pub fn new() -> Self {
31        Self::default()
32    }
33
34    /// Computes the `CodebaseDiff` between two `CodebaseMetadata` instances.
35    ///
36    /// This method compares the metadata of the old and new codebases to determine which symbols have changed,
37    /// which can be kept unchanged, and what text diffs exist for source files.
38    ///
39    /// It aggregates this information into a `CodebaseDiff` instance that can be used for incremental analysis.
40    #[must_use]
41    pub fn between(old_metadata: &CodebaseMetadata, new_metadata: &CodebaseMetadata) -> Self {
42        let mut aggregate_diff = CodebaseDiff::new();
43
44        let mut all_file_ids = old_metadata.get_all_file_ids();
45        all_file_ids.extend(new_metadata.get_all_file_ids());
46        all_file_ids.sort();
47        all_file_ids.dedup();
48
49        for file_id in all_file_ids {
50            let old_sig = old_metadata.get_file_signature(&file_id);
51            let new_sig = new_metadata.get_file_signature(&file_id);
52
53            let file_diff = compute_file_diff(file_id, old_sig, new_sig);
54
55            aggregate_diff.extend(file_diff);
56        }
57
58        aggregate_diff
59    }
60
61    /// Merges changes from another `CodebaseDiff` into this one.
62    #[inline]
63    pub fn extend(&mut self, other: Self) {
64        self.keep.extend(other.keep);
65        self.changed.extend(other.changed);
66    }
67
68    /// Returns a reference to the set of symbols/members to keep unchanged.
69    #[inline]
70    #[must_use]
71    pub fn get_keep(&self) -> &HashSet<SymbolIdentifier> {
72        &self.keep
73    }
74
75    /// Returns a reference to the set of changed symbols/members.
76    #[inline]
77    #[must_use]
78    pub fn get_changed(&self) -> &HashSet<SymbolIdentifier> {
79        &self.changed
80    }
81
82    /// Returns a new instance with the 'keep' set replaced.
83    #[inline]
84    #[must_use]
85    pub fn with_keep(mut self, keep_set: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
86        self.keep = keep_set.into_iter().collect();
87        self
88    }
89
90    /// Adds a single entry to the 'keep' set. Returns `true` if the entry was not already present.
91    #[inline]
92    pub fn add_keep_entry(&mut self, entry: SymbolIdentifier) -> bool {
93        self.keep.insert(entry)
94    }
95
96    /// Returns a new instance with the 'changed' set replaced.
97    #[inline]
98    #[must_use]
99    pub fn with_changed(mut self, change_set: impl IntoIterator<Item = SymbolIdentifier>) -> Self {
100        self.changed = change_set.into_iter().collect();
101        self
102    }
103
104    /// Checks if the 'changed' set contains a specific entry.
105    #[inline]
106    #[must_use]
107    pub fn contains_changed_entry(&self, entry: &SymbolIdentifier) -> bool {
108        self.changed.contains(entry)
109    }
110}