Skip to content

Node IDs for file / directory / project nodes embed the absolute scan path (leaks local username, makes committed graph.json non-portable / non-idempotent) #1789

Description

@fremat79

Summary

graphify already relativizes the source_file field of every node so the graph
is portable across machines. However, the node id for structural nodes
(files, directories, and — for .NET — .csproj/.sln/solution/project container
nodes) is still built from the absolute resolved path. As a result:

  • the committed graph.json leaks the local OS username and full home path
    inside those ids, and
  • the same file produces a different id on a different machine (or a different
    checkout location), so graphify update on another clone re-creates those nodes
    → diff churn + non-idempotent output for a versioned graph.

Symbol nodes (classes, functions, methods) are unaffected because their ids are
name/symbol-based; only path-derived structural nodes are impacted.

Environment

  • graphify (graphifyy) 0.8.19
  • Windows (but the logic is OS-agnostic; same on Linux/macOS with /home/<user>/…)

Current behavior

  1. detect() resolves the scan root to an absolute path unconditionally:
    • detect.pyroot = root.resolve() (≈ line 866). Every file path handled
      downstream is therefore absolute.
  2. Structural / file nodes get their id from the absolute path string:
    • extract.py_make_id(str(path)) (e.g. the file-node id assignments around
      lines 1684 / 2526 / 2646). path here is absolute (from step 1).
  3. By contrast, symbol nodes use a name-based id: _make_id(module_name) /
    _make_id(str(resolved)) — no path, no leak.
  4. Crucially, graphify already relativizes source_file but not the id:
    • watch.py::_relativize_source_files (≈ lines 131–143):
      item["source_file"] = str(source_path.resolve().relative_to(root)), called
      from _rebuild_code (≈ line 438). So the scan root is known and the code
      already knows how to make paths relative — it just isn't applied to the id.

Concrete example

Project on machine A, user alice:

C:\Users\alice\repos\MyApp\src\Widgets\MyApp.Widgets.csproj

produces a node whose source_file is nicely relative:

{ "source_file": "src/Widgets/MyApp.Widgets.csproj", "label": "MyApp.Widgets.csproj",
  "id": "c_users_alice_repos_myapp_src_widgets_myapp_widgets_csproj" }

but the id still hard-codes the absolute path (note c_users_alice_repos_myapp_…).

The same repo cloned by user bob to D:\work\MyApp yields the id
d_work_myapp_src_widgets_myapp_widgets_csproj — a different id for the same file.
Directory container nodes behave the same (e.g. a Widgets folder node →
c_users_alice_repos_myapp_src_widgets).

Why this matters

Teams that want to commit graph.json to version control (so colleagues can
query the graph without rebuilding) hit three problems on the affected nodes:

  1. Privacy / hygiene: the committed file contains each contributor's OS username
    and local folder layout.
  2. Non-portability: on a fresh clone the ids don't match the committed graph.
  3. Non-idempotency / churn: graphify update (which re-runs detect()
    absolute root) re-adds absolute-path-id versions of those nodes on every run and
    on every different machine, producing noisy, per-contributor diffs.

Today the only workarounds are to (a) exclude the files that create these nodes via
.graphifyignore (loses the info, e.g. the .NET project-dependency graph), or
(b) post-process graph.json to rewrite the ids — both external to graphify.

Reproduction (minimal)

mkdir -p /tmp/demo/src && printf '<Project/>' > /tmp/demo/src/Demo.csproj
graphify /tmp/demo            # or: cd /tmp/demo && graphify .
grep -o '"id": *"[^"]*demo[^"]*"' graphify-out/graph.json | head
# -> ids contain the absolute path, e.g. "tmp_demo_src_demo_csproj" and, on a real
#    home dir, "<drive>_users_<username>_…"

Expected behavior / proposed fix

Make structural/file/directory/project node ids relative to the scan root, the
same way source_file is already relativized. Options, smallest first:

  1. Relativize before _make_id: when building a file/dir node id, pass the path
    relative to scan_root (the code already computes relative_to(root) in
    _relativize_source_files). This keeps ids stable and portable and requires no
    new API — it just makes the id consistent with the already-relative source_file.
  2. Or add an opt-in flag / env var (e.g. --relative-ids / GRAPHIFY_RELATIVE_IDS=1)
    for teams that commit graph.json, if a default change is a concern for backward
    compatibility of existing graphs.

Either way the goal is: the same source file yields the same node id regardless of
where the repo is checked out, and the committed graph.json never contains an
absolute local path.
This would make graph.json a first-class, VCS-friendly
artifact.

Happy to help test a patch. Thanks for graphify!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions