mirror of
https://github.com/safishamsi/graphify.git
synced 2026-09-26 23:45:44 +00:00
fix(build_merge): replace re-extracted files instead of accumulating stale edges (#1344)
build_merge: prune a re-extracted file's stale nodes/edges before merge instead of accumulating (fixes #1283, #1285). Validated: full suite 2107 passed. Thanks @RelywOo.
This commit is contained in:
+37
-4
@@ -443,8 +443,11 @@ def build_merge(
|
||||
) -> nx.Graph:
|
||||
"""Load existing graph.json, merge new chunks into it, and save back.
|
||||
|
||||
Never replaces - only grows (or prunes deleted-file nodes via prune_sources).
|
||||
Safe to call repeatedly: existing nodes and edges are preserved.
|
||||
Re-extracted files REPLACE their prior contribution: any source_file present
|
||||
in new_chunks is dropped from the loaded graph before merging, so a changed
|
||||
file's stale nodes/edges don't accumulate. Files absent from new_chunks are
|
||||
preserved unchanged; deleted files are removed via prune_sources.
|
||||
Safe to call repeatedly.
|
||||
root: if given, absolute source_file paths in new_chunks are made relative (#932).
|
||||
"""
|
||||
graph_path = Path(graph_path)
|
||||
@@ -462,10 +465,40 @@ def build_merge(
|
||||
links_key = "links" if "links" in data else "edges"
|
||||
existing_nodes = list(data.get("nodes", []))
|
||||
existing_edges = list(data.get(links_key, []))
|
||||
base = [{"nodes": existing_nodes, "edges": existing_edges}]
|
||||
had_graph = True
|
||||
else:
|
||||
existing_nodes = []
|
||||
base = []
|
||||
existing_edges = []
|
||||
had_graph = False
|
||||
|
||||
# Re-extracted files REPLACE their prior contribution. Every source_file
|
||||
# present in new_chunks is dropped from the loaded base before merging, so a
|
||||
# CHANGED file's stale nodes/edges don't accumulate across incremental
|
||||
# updates. Without this, build() merges old+new for the same file and only
|
||||
# exact-duplicate edges collapse — edges/nodes that disappeared from the new
|
||||
# version survive forever. Brand-new files aren't in base, so this is a no-op
|
||||
# for them; genuinely deleted files are still handled via prune_sources.
|
||||
# Matched in both raw and _norm_source_file form because new_chunks may carry
|
||||
# absolute win32 paths while the stored graph keeps relative posix (#1007).
|
||||
_replace_root = str(Path(root).resolve()) if root is not None else None
|
||||
new_sources: set[str] = set()
|
||||
for ch in new_chunks:
|
||||
for n in ch.get("nodes", []):
|
||||
sf = n.get("source_file")
|
||||
if not sf:
|
||||
continue
|
||||
new_sources.add(sf)
|
||||
norm = _norm_source_file(sf, _replace_root)
|
||||
if norm:
|
||||
new_sources.add(norm)
|
||||
if new_sources:
|
||||
def _kept(item: dict) -> bool:
|
||||
sf = item.get("source_file")
|
||||
return sf not in new_sources and _norm_source_file(sf, _replace_root) not in new_sources
|
||||
existing_nodes = [n for n in existing_nodes if _kept(n)]
|
||||
existing_edges = [e for e in existing_edges if _kept(e)]
|
||||
|
||||
base = [{"nodes": existing_nodes, "edges": existing_edges}] if had_graph else []
|
||||
|
||||
all_chunks = base + list(new_chunks)
|
||||
G = build(all_chunks, directed=directed, dedup=dedup, dedup_llm_backend=dedup_llm_backend, root=root)
|
||||
|
||||
Reference in New Issue
Block a user