Skip to main content

infigraph_core/diff/
mod.rs

1//! Semantic diff between two git refs at the symbol level.
2//!
3//! Instead of a line diff, this compares the extracted symbol graphs of two
4//! git tree-states and classifies each change as Added / Removed / BodyChanged /
5//! SignatureChanged.  The caller supplies a project root and two git refs
6//! (e.g. "HEAD~1", "main"); the module checks out each ref into a temp
7//! worktree, indexes it with the current language registry, and returns a
8//! structured `SymbolDiff`.
9
10mod compute;
11mod format;
12
13pub use compute::*;
14pub use format::*;
15
16use serde::{Deserialize, Serialize};
17
18/// How a symbol changed between two refs.
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
20pub enum ChangeKind {
21    /// Symbol exists in new ref but not in old ref.
22    Added,
23    /// Symbol exists in old ref but not in new ref.
24    Removed,
25    /// Symbol exists in both; signature_hash changed (parameter / return type change).
26    SignatureChanged,
27    /// Symbol exists in both; body changed but signature is the same.
28    BodyChanged,
29    /// Symbol moved to a different file.
30    Moved { from_file: String },
31    /// Symbol renamed in the same file (structurally similar body).
32    Renamed { old_name: String },
33}
34
35impl std::fmt::Display for ChangeKind {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        match self {
38            ChangeKind::Added => write!(f, "ADDED"),
39            ChangeKind::Removed => write!(f, "REMOVED"),
40            ChangeKind::SignatureChanged => write!(f, "SIGNATURE_CHANGED"),
41            ChangeKind::BodyChanged => write!(f, "BODY_CHANGED"),
42            ChangeKind::Moved { from_file } => write!(f, "MOVED(from:{})", from_file),
43            ChangeKind::Renamed { old_name } => write!(f, "RENAMED(from:{})", old_name),
44        }
45    }
46}
47
48/// A single symbol-level change.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct SymbolChange {
51    pub name: String,
52    pub kind: String,
53    pub file: String,
54    pub change: ChangeKind,
55    /// Callers in the current graph (populated by caller when graph is available).
56    pub caller_count: usize,
57}
58
59/// Full semantic diff result.
60#[derive(Debug, Default)]
61pub struct SymbolDiff {
62    pub old_ref: String,
63    pub new_ref: String,
64    pub changes: Vec<SymbolChange>,
65}
66
67impl SymbolDiff {
68    pub fn added(&self) -> impl Iterator<Item = &SymbolChange> {
69        self.changes
70            .iter()
71            .filter(|c| c.change == ChangeKind::Added)
72    }
73    pub fn removed(&self) -> impl Iterator<Item = &SymbolChange> {
74        self.changes
75            .iter()
76            .filter(|c| c.change == ChangeKind::Removed)
77    }
78    pub fn modified(&self) -> impl Iterator<Item = &SymbolChange> {
79        self.changes.iter().filter(|c| {
80            matches!(
81                c.change,
82                ChangeKind::BodyChanged
83                    | ChangeKind::SignatureChanged
84                    | ChangeKind::Moved { .. }
85                    | ChangeKind::Renamed { .. }
86            )
87        })
88    }
89}
90
91/// A flat symbol record used during diff.
92#[derive(Clone)]
93pub(crate) struct FlatSym {
94    pub(crate) file: String,
95    pub(crate) name: String,
96    pub(crate) kind: String,
97    pub(crate) sig_hash: String,
98    pub(crate) params: String,
99    pub(crate) return_type: String,
100}