Skip to content

Commit 3efae38

Browse files
safishamsiclaude
andcommitted
Rate-limit backup_if_protected to one folder per day via content hash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 98100f3 commit 3efae38

2 files changed

Lines changed: 28 additions & 8 deletions

File tree

graphify/export.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# write graph to HTML, JSON, SVG, GraphML, Obsidian vault, and Neo4j Cypher
22
from __future__ import annotations
3+
import hashlib
34
import html as _html
45
import json
56
import math
@@ -62,10 +63,16 @@ def backup_if_protected(out_dir: Path) -> "Path | None":
6263
reason = "+".join(filter(None, ["semantic" if is_semantic else "", "curated" if is_curated else ""]))
6364
today = date.today().isoformat()
6465
backup_dir = out / today
65-
suffix = 2
66-
while backup_dir.exists():
67-
backup_dir = out / f"{today}_{suffix}"
68-
suffix += 1
66+
graph_src = out / "graph.json"
67+
68+
# Skip re-copying if today's backup already has identical graph.json content.
69+
# If content differs (graph changed since the last backup today), overwrite
70+
# the backup in place — one folder per day, always the latest pre-overwrite state.
71+
if backup_dir.exists() and (backup_dir / "graph.json").exists():
72+
src_hash = hashlib.sha256(graph_src.read_bytes()).hexdigest()
73+
bak_hash = hashlib.sha256((backup_dir / "graph.json").read_bytes()).hexdigest()
74+
if src_hash == bak_hash:
75+
return backup_dir # identical content, nothing to do
6976

7077
try:
7178
backup_dir.mkdir(parents=True, exist_ok=True)

tests/test_export.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,17 +228,30 @@ def test_backup_default_labels_only(tmp_path):
228228
assert backup_if_protected(tmp_path) is None
229229

230230

231-
def test_backup_same_day_collision(tmp_path):
232-
"""Second backup on same day gets _2 suffix."""
231+
def test_backup_same_day_no_accumulation(tmp_path):
232+
"""Same content on same day returns existing backup dir without re-copying."""
233233
from graphify.export import backup_if_protected
234234
from datetime import date
235235
(tmp_path / "graph.json").write_text('{"nodes":[],"links":[]}')
236236
(tmp_path / ".graphify_semantic_marker").write_text("{}")
237237
b1 = backup_if_protected(tmp_path)
238238
b2 = backup_if_protected(tmp_path)
239239
assert b1 is not None and b2 is not None
240-
assert b1 != b2
241-
assert b2.name == f"{date.today().isoformat()}_2"
240+
assert b1 == b2 # same dir, no _2 accumulation
241+
assert b1.name == date.today().isoformat()
242+
243+
244+
def test_backup_same_day_changed_content(tmp_path):
245+
"""Changed graph.json on same day overwrites the existing backup in place."""
246+
from graphify.export import backup_if_protected
247+
from datetime import date
248+
(tmp_path / "graph.json").write_text('{"nodes":[],"links":[]}')
249+
(tmp_path / ".graphify_semantic_marker").write_text("{}")
250+
b1 = backup_if_protected(tmp_path)
251+
(tmp_path / "graph.json").write_text('{"nodes":[{"id":"x"}],"links":[]}')
252+
b2 = backup_if_protected(tmp_path)
253+
assert b1 == b2 # still one folder per day
254+
assert (b2 / "graph.json").read_text() == '{"nodes":[{"id":"x"}],"links":[]}'
242255

243256

244257
def test_backup_env_disable(tmp_path, monkeypatch):

0 commit comments

Comments
 (0)