fix(export,cli): stamp graph provenance from the analysed repo, not the shell cwd (#2534 family)
This commit is contained in:
+11
-1
@@ -3924,7 +3924,17 @@ def dispatch_command(cmd: str) -> None:
|
||||
# passing --allow-partial (the good graph is preserved and the manifest
|
||||
# is not stamped, so the retry re-extracts).
|
||||
_force_write = cli_allow_partial or not _extraction_incomplete
|
||||
_wrote = _to_json(G, communities, str(graph_json_path), force=_force_write)
|
||||
# Stamp provenance from the ANALYSED repo, not the shell's cwd: without
|
||||
# this, to_json's fallback asks `git rev-parse HEAD` in whatever repo the
|
||||
# command was invoked from, so `graphify extract <target>` run from
|
||||
# another repo's root stamped the invoker's commit into the target's
|
||||
# graph.json — and cluster then propagates that stamp into
|
||||
# GRAPH_REPORT.md (#2534 keeps the extract-time stamp by design). Same
|
||||
# cwd-anchoring mistake #2316 fixed for watch/update, surviving in the
|
||||
# extract path.
|
||||
from graphify.watch import _git_head as _gh_target
|
||||
_wrote = _to_json(G, communities, str(graph_json_path), force=_force_write,
|
||||
built_at_commit=_gh_target(cwd=Path(target).resolve()))
|
||||
if not _wrote:
|
||||
# The shrink guard refused: this partial build is smaller than the
|
||||
# existing graph. Exit before writing the manifest/marker below, which
|
||||
|
||||
+17
-4
@@ -170,11 +170,21 @@ def attach_hyperedges(G: nx.Graph, hyperedges: list) -> None:
|
||||
G.graph["hyperedges"] = existing
|
||||
|
||||
|
||||
def _git_head() -> str | None:
|
||||
"""Return the current git HEAD commit hash, or None if not in a git repo."""
|
||||
def _git_head(cwd: "str | Path | None" = None) -> str | None:
|
||||
"""Return git HEAD for the repo containing ``cwd``, or None outside a repo.
|
||||
|
||||
``cwd`` selects the repository to ask, exactly as in watch._git_head
|
||||
(#2316). Without it the command inherits the caller's working directory,
|
||||
which stamps the *invoking* repo's commit when the graph being written
|
||||
describes a different repo — provenance must come from the repo the graph
|
||||
describes, so callers pass the graph's own location.
|
||||
"""
|
||||
import subprocess as _sp
|
||||
try:
|
||||
r = _sp.run(["git", "rev-parse", "HEAD"], capture_output=True, text=True, timeout=3)
|
||||
r = _sp.run(
|
||||
["git", "rev-parse", "HEAD"], capture_output=True, text=True, timeout=3,
|
||||
cwd=str(cwd) if cwd is not None else None,
|
||||
)
|
||||
return r.stdout.strip() if r.returncode == 0 else None
|
||||
except Exception:
|
||||
return None
|
||||
@@ -349,7 +359,10 @@ def to_json(G: nx.Graph, communities: dict[int, list[str]], output_path: str, *,
|
||||
if isinstance(data.get("graph"), dict) and "hyperedges" in data["graph"]:
|
||||
data["graph"]["hyperedges"] = hyperedges
|
||||
data["hyperedges"] = hyperedges
|
||||
commit = built_at_commit if built_at_commit is not None else _git_head()
|
||||
# Fallback provenance comes from the repo the graph is being written INTO
|
||||
# (output_path lives in <target>/graphify-out/), never the shell's cwd —
|
||||
# the same cwd-anchoring mistake #2316 fixed for `update`.
|
||||
commit = built_at_commit if built_at_commit is not None else _git_head(Path(output_path).resolve().parent)
|
||||
if commit:
|
||||
data["built_at_commit"] = commit
|
||||
from graphify.paths import write_json_atomic
|
||||
|
||||
@@ -72,6 +72,43 @@ def test_to_json_sorts_graph_collections_across_insertion_order(tmp_path):
|
||||
|
||||
assert outputs[0].read_bytes() == outputs[1].read_bytes()
|
||||
|
||||
|
||||
def test_to_json_commit_fallback_uses_output_repo_not_cwd(tmp_path, monkeypatch):
|
||||
# Without an explicit built_at_commit, provenance must come from the repo
|
||||
# the graph is written into, not from whatever repo the shell happens to
|
||||
# be in — running `graphify extract <target>` from another repo's root
|
||||
# used to stamp the invoker's HEAD into the target's graph.json.
|
||||
import subprocess
|
||||
import networkx as nx
|
||||
|
||||
def git(cwd, *args):
|
||||
subprocess.run(
|
||||
["git", "-c", "user.email=t@t", "-c", "user.name=t", *args],
|
||||
cwd=cwd, check=True, capture_output=True,
|
||||
)
|
||||
|
||||
target = tmp_path / "target"
|
||||
(target / "graphify-out").mkdir(parents=True)
|
||||
git(target, "init")
|
||||
git(target, "commit", "--allow-empty", "-m", "target")
|
||||
target_head = subprocess.run(
|
||||
["git", "rev-parse", "HEAD"], cwd=target, check=True,
|
||||
capture_output=True, text=True,
|
||||
).stdout.strip()
|
||||
|
||||
invoker = tmp_path / "invoker"
|
||||
invoker.mkdir()
|
||||
git(invoker, "init")
|
||||
git(invoker, "commit", "--allow-empty", "-m", "invoker")
|
||||
monkeypatch.chdir(invoker)
|
||||
|
||||
G = nx.Graph()
|
||||
G.add_node("n1", label="n1")
|
||||
out = target / "graphify-out" / "graph.json"
|
||||
assert to_json(G, {0: ["n1"]}, str(out), force=True)
|
||||
assert json.loads(out.read_text())["built_at_commit"] == target_head
|
||||
|
||||
|
||||
def test_to_cypher_creates_file():
|
||||
G = make_graph()
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
|
||||
Reference in New Issue
Block a user