diff --git a/graphify/tree_html.py b/graphify/tree_html.py index 65d151cde..c0a44b9ff 100644 --- a/graphify/tree_html.py +++ b/graphify/tree_html.py @@ -152,18 +152,27 @@ def _attach_files( return root_node, matched_files, by_file +_NO_REPO_LABEL = "(no repo)" + + def _repo_groups( file_nodes: List[Dict[str, Any]], -) -> Dict[str, List[Dict[str, Any]]]: - """Group nodes by their ``repo`` tag (set by ``merge-graphs`` / ``global add``). +) -> "tuple[Dict[str, List[Dict[str, Any]]], List[Dict[str, Any]]]": + """Split nodes into ``({repo tag: nodes}, untagged nodes)``. - Nodes without a usable tag go under ``"(no repo)"`` so they are not lost. + The tag is the ``repo`` attribute set by ``merge-graphs`` / ``global add``. + Untagged nodes are returned separately so they never count as a repo: only + real tags decide whether a graph is treated as a merged one. """ groups: Dict[str, List[Dict[str, Any]]] = defaultdict(list) + untagged: List[Dict[str, Any]] = [] for n in file_nodes: tag = n.get("repo") - groups[tag if isinstance(tag, str) and tag else "(no repo)"].append(n) - return groups + if isinstance(tag, str) and tag: + groups[tag].append(n) + else: + untagged.append(n) + return groups, untagged def build_tree( @@ -195,9 +204,11 @@ def build_tree( return {"name": "(empty graph)", "total_count": 0, "children": []} explicit_root = root is not None - groups = _repo_groups(file_nodes) + groups, untagged = _repo_groups(file_nodes) if len(groups) < 2: + # Zero or one real repo tag: not a merged graph. Every node, tagged or + # not, goes through the original single-root path. if root is None: root = _common_root([n["source_file"] for n in file_nodes]) root_node, matched_files, by_file = _attach_files( @@ -214,8 +225,11 @@ def build_tree( } total_matched = total_files = 0 sample = "" - for tag in sorted(groups): - group = groups[tag] + ordered = sorted(groups.items()) + if untagged: + # Kept visible, but only once two real tags made this a merged graph. + ordered.append((_NO_REPO_LABEL, untagged)) + for tag, group in ordered: paths = [n["source_file"] for n in group] if root is not None: group_root = root diff --git a/tests/test_tree_html.py b/tests/test_tree_html.py index 697b79cdb..72b62156e 100644 --- a/tests/test_tree_html.py +++ b/tests/test_tree_html.py @@ -136,6 +136,26 @@ def test_build_tree_merged_graph_untagged_nodes_are_kept(): assert set(_by_name(build_tree(graph))) == {"repo-a", "repo-b", "(no repo)"} +def test_build_tree_partially_tagged_single_repo_is_not_a_merged_graph(): + """One real repo tag plus untagged nodes is not a merged graph: it must + take the original single-root path, not grow a "(no repo)" sibling.""" + graph = {"nodes": [ + {"id": "a", "label": "A", "source_file": "pkg/a.py", "repo": "only-repo"}, + {"id": "b", "label": "B", "source_file": "pkg/b.py"}, + ]} + tree = build_tree(graph) + assert tree["name"] == "pkg" + assert set(_by_name(tree)) == {"a.py", "b.py"} + + +def test_build_tree_untagged_graph_is_unchanged_by_repo_grouping(): + graph = _graph({"pkg/a.py": "func_a", "pkg/sub/b.py": "func_b"}) + assert "repo" not in graph["nodes"][0] + tree = build_tree(graph) + assert tree["name"] == "pkg" + assert {"a.py", "sub"} <= set(_by_name(tree)) + + def test_build_tree_merged_graph_explicit_root_matching_nothing_raises(): graph = _merged_graph({ "repo-a": {"src/a.py": "A"},