-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgraph_diff.py
More file actions
122 lines (104 loc) · 3.54 KB
/
Copy pathgraph_diff.py
File metadata and controls
122 lines (104 loc) · 3.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
"""Graph snapshot diffing -- compare graph state over time."""
from __future__ import annotations
import json
import logging
from pathlib import Path
from typing import Any
from .graph import GraphStore
logger = logging.getLogger(__name__)
def take_snapshot(store: GraphStore) -> dict[str, Any]:
"""Take a snapshot of the current graph state.
Returns a dict with node and edge counts, qualified names,
and community assignments for later diffing.
"""
stats = store.get_stats()
nodes = store.get_all_nodes(exclude_files=False)
community_map = store.get_all_community_ids()
return {
"node_count": stats.total_nodes,
"edge_count": stats.total_edges,
"nodes": {
n.qualified_name: {
"kind": n.kind,
"file": n.file_path,
"community_id": community_map.get(
n.qualified_name
),
}
for n in nodes
},
"edges": {
f"{e.source_qualified}->"
f"{e.target_qualified}:{e.kind}"
for e in store.get_all_edges()
},
}
def save_snapshot(snapshot: dict, path: Path) -> None:
"""Save a snapshot to a JSON file."""
data = dict(snapshot)
if isinstance(data.get("edges"), set):
data["edges"] = sorted(data["edges"])
path.write_text(
json.dumps(data, indent=2), encoding="utf-8"
)
def load_snapshot(path: Path) -> dict:
"""Load a snapshot from a JSON file."""
data = json.loads(path.read_text(encoding="utf-8"))
if isinstance(data.get("edges"), list):
data["edges"] = set(data["edges"])
return data
def diff_snapshots(
before: dict, after: dict,
) -> dict[str, Any]:
"""Compare two graph snapshots.
Returns:
Dict with new_nodes, removed_nodes, new_edges,
removed_edges, community_changes, and summary
statistics.
"""
before_nodes = set(before.get("nodes", {}).keys())
after_nodes = set(after.get("nodes", {}).keys())
before_edges = before.get("edges", set())
after_edges = after.get("edges", set())
new_nodes = after_nodes - before_nodes
removed_nodes = before_nodes - after_nodes
new_edges = after_edges - before_edges
removed_edges = before_edges - after_edges
# Community changes for nodes that exist in both
community_changes = []
for qn in before_nodes & after_nodes:
before_cid = before["nodes"][qn].get(
"community_id"
)
after_cid = after["nodes"][qn].get(
"community_id"
)
if before_cid != after_cid:
community_changes.append({
"node": qn,
"before_community": before_cid,
"after_community": after_cid,
})
return {
"new_nodes": [
{"qualified_name": qn, **after["nodes"][qn]}
for qn in sorted(new_nodes)
][:100],
"removed_nodes": sorted(removed_nodes)[:100],
"new_edges": sorted(new_edges)[:100],
"removed_edges": sorted(removed_edges)[:100],
"community_changes": community_changes[:50],
"summary": {
"nodes_added": len(new_nodes),
"nodes_removed": len(removed_nodes),
"edges_added": len(new_edges),
"edges_removed": len(removed_edges),
"community_moves": len(community_changes),
"before_total": before.get(
"node_count", 0
),
"after_total": after.get(
"node_count", 0
),
},
}