mirror of
https://github.com/safishamsi/graphify.git
synced 2026-09-21 21:16:31 +00:00
fix(global-graph): rewire edges to deduplicated external nodes (#1250)
global_add deduplicates external-library nodes (no source_file) by label against externals already in the global graph, but dropped every edge incident to a skipped node. Each repo after the first lost its edges to shared externals, so cross-repo "what uses library X" queries only saw the first-added repo. Build a remap from each deduplicated external to the surviving global node and rewrite edge endpoints through it before add_edge, skipping self-loops introduced by the remapping. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
565026d3d1
commit
444d73e1c8
@@ -105,20 +105,24 @@ def global_add(source_path: Path, repo_tag: str) -> dict:
|
||||
for n, d in G.nodes(data=True)
|
||||
if not d.get("source_file") and d.get("label")
|
||||
}
|
||||
nodes_to_skip = set()
|
||||
# Map each deduplicated external onto the existing global node so that
|
||||
# edges incident to it can be rewired instead of dropped.
|
||||
remap = {}
|
||||
for node, data in prefixed.nodes(data=True):
|
||||
if not data.get("source_file") and data.get("label") in external_labels:
|
||||
nodes_to_skip.add(node)
|
||||
remap[node] = external_labels[data["label"]]
|
||||
|
||||
# Compose: add prefixed nodes (except deduplicated externals) into global graph
|
||||
for node, data in prefixed.nodes(data=True):
|
||||
if node not in nodes_to_skip:
|
||||
if node not in remap:
|
||||
G.add_node(node, **data)
|
||||
for u, v, data in prefixed.edges(data=True):
|
||||
if u not in nodes_to_skip and v not in nodes_to_skip:
|
||||
u = remap.get(u, u)
|
||||
v = remap.get(v, v)
|
||||
if u != v: # don't introduce self-loops via remapping
|
||||
G.add_edge(u, v, **data)
|
||||
|
||||
added = prefixed.number_of_nodes() - len(nodes_to_skip)
|
||||
added = prefixed.number_of_nodes() - len(remap)
|
||||
_save_global_graph(G)
|
||||
|
||||
manifest["repos"][repo_tag] = {
|
||||
|
||||
@@ -279,6 +279,47 @@ def test_merge_graphs_prefixes_ids(tmp_path):
|
||||
assert merged.number_of_nodes() == 2 # no silent collapse
|
||||
|
||||
|
||||
def test_global_add_rewires_edges_to_deduplicated_externals(tmp_path):
|
||||
"""Edges incident to an external node that gets deduplicated against an
|
||||
already-present external must be rewired to the existing node, not dropped."""
|
||||
g1 = tmp_path / "graph1.json"
|
||||
g2 = tmp_path / "graph2.json"
|
||||
GA = _make_graph(
|
||||
[
|
||||
{"id": "moda", "label": "ModA", "source_file": "src/a.py"},
|
||||
{"id": "requests", "label": "requests"},
|
||||
],
|
||||
[{"source": "moda", "target": "requests", "relation": "imports"}],
|
||||
)
|
||||
GB = _make_graph(
|
||||
[
|
||||
{"id": "modb", "label": "ModB", "source_file": "src/b.py"},
|
||||
{"id": "requests", "label": "requests"},
|
||||
],
|
||||
[{"source": "modb", "target": "requests", "relation": "imports"}],
|
||||
)
|
||||
_graph_to_json(GA, g1)
|
||||
_graph_to_json(GB, g2)
|
||||
|
||||
global_dir = tmp_path / ".graphify"
|
||||
with patch("graphify.global_graph._GLOBAL_DIR", global_dir), \
|
||||
patch("graphify.global_graph._GLOBAL_GRAPH", global_dir / "global-graph.json"), \
|
||||
patch("graphify.global_graph._GLOBAL_MANIFEST", global_dir / "global-manifest.json"):
|
||||
from graphify.global_graph import global_add, _load_global_graph
|
||||
global_add(g1, "repoA")
|
||||
global_add(g2, "repoB")
|
||||
G = _load_global_graph()
|
||||
|
||||
# repoB's external "requests" was deduplicated against repoA's
|
||||
assert "repoA::requests" in G.nodes
|
||||
assert "repoB::requests" not in G.nodes
|
||||
# repoA's edge is untouched
|
||||
assert G.has_edge("repoA::moda", "repoA::requests")
|
||||
# repoB's edge must be rewired to the existing external node, not dropped
|
||||
assert G.has_edge("repoB::modb", "repoA::requests")
|
||||
assert G.edges["repoB::modb", "repoA::requests"]["relation"] == "imports"
|
||||
|
||||
|
||||
def test_global_add_rejects_oversized_source_graph(monkeypatch, tmp_path):
|
||||
"""#F4: global_add must refuse to read a source graph.json that
|
||||
exceeds the size cap, rather than json.loads-ing it into memory."""
|
||||
|
||||
Reference in New Issue
Block a user