diff --git a/graphify/export.py b/graphify/export.py index 4c9a4477..dd64b6c8 100644 --- a/graphify/export.py +++ b/graphify/export.py @@ -1,5 +1,6 @@ # write graph to HTML, JSON, SVG, GraphML, Obsidian vault, and Neo4j Cypher from __future__ import annotations +import hashlib import html as _html import json import math @@ -62,10 +63,16 @@ def backup_if_protected(out_dir: Path) -> "Path | None": reason = "+".join(filter(None, ["semantic" if is_semantic else "", "curated" if is_curated else ""])) today = date.today().isoformat() backup_dir = out / today - suffix = 2 - while backup_dir.exists(): - backup_dir = out / f"{today}_{suffix}" - suffix += 1 + graph_src = out / "graph.json" + + # Skip re-copying if today's backup already has identical graph.json content. + # If content differs (graph changed since the last backup today), overwrite + # the backup in place — one folder per day, always the latest pre-overwrite state. + if backup_dir.exists() and (backup_dir / "graph.json").exists(): + src_hash = hashlib.sha256(graph_src.read_bytes()).hexdigest() + bak_hash = hashlib.sha256((backup_dir / "graph.json").read_bytes()).hexdigest() + if src_hash == bak_hash: + return backup_dir # identical content, nothing to do try: backup_dir.mkdir(parents=True, exist_ok=True) diff --git a/tests/test_export.py b/tests/test_export.py index 7f0ed5e7..65964d24 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -228,8 +228,8 @@ def test_backup_default_labels_only(tmp_path): assert backup_if_protected(tmp_path) is None -def test_backup_same_day_collision(tmp_path): - """Second backup on same day gets _2 suffix.""" +def test_backup_same_day_no_accumulation(tmp_path): + """Same content on same day returns existing backup dir without re-copying.""" from graphify.export import backup_if_protected from datetime import date (tmp_path / "graph.json").write_text('{"nodes":[],"links":[]}') @@ -237,8 +237,21 @@ def test_backup_same_day_collision(tmp_path): b1 = backup_if_protected(tmp_path) b2 = backup_if_protected(tmp_path) assert b1 is not None and b2 is not None - assert b1 != b2 - assert b2.name == f"{date.today().isoformat()}_2" + assert b1 == b2 # same dir, no _2 accumulation + assert b1.name == date.today().isoformat() + + +def test_backup_same_day_changed_content(tmp_path): + """Changed graph.json on same day overwrites the existing backup in place.""" + from graphify.export import backup_if_protected + from datetime import date + (tmp_path / "graph.json").write_text('{"nodes":[],"links":[]}') + (tmp_path / ".graphify_semantic_marker").write_text("{}") + b1 = backup_if_protected(tmp_path) + (tmp_path / "graph.json").write_text('{"nodes":[{"id":"x"}],"links":[]}') + b2 = backup_if_protected(tmp_path) + assert b1 == b2 # still one folder per day + assert (b2 / "graph.json").read_text() == '{"nodes":[{"id":"x"}],"links":[]}' def test_backup_env_disable(tmp_path, monkeypatch):