mirror of
https://github.com/safishamsi/graphify.git
synced 2026-07-11 18:07:31 +00:00
Fix rebuilds from deleted hook cwd
What changed - Stabilize relative rebuild execution before graphify-out queue/lock setup. - Recover from deleted transient hook CWDs when GRAPHIFY_REPO_ROOT points at the repository root. - Fail cleanly when the current working directory is gone and no repo root fallback is available. - Add regression coverage for both fallback and clean-failure paths. Why - Detached post-commit/post-checkout rebuilds can inherit a transient CWD that is deleted before the background process starts. - _rebuild_code previously used relative graphify-out paths before Path.cwd()/watch_path resolution could be handled, causing FileNotFoundError: [Errno 2] No such file or directory. Validation - .venv/bin/pytest tests/test_watch.py -q: 54 passed, 2 skipped. - .venv/bin/ruff check graphify/watch.py tests/test_watch.py: passed. - .venv/bin/python -m py_compile graphify/watch.py tests/test_watch.py: passed. - env -u PYTHONPATH -u PYTHONHOME PYTHONHASHSEED=0 .venv/bin/pytest -q: 2904 passed, 30 skipped. Notes - Full suite without PYTHONHASHSEED=0 exposed an unrelated ordering-sensitive labeling test; with the deterministic hash seed used by graphify hooks, the suite passes.
This commit is contained in:
committed by
safishamsi
parent
6c33e3aad7
commit
e5044c3253
@@ -657,6 +657,37 @@ def _json_text(data: dict) -> str:
|
||||
return json.dumps(data, indent=2, ensure_ascii=False) + "\n"
|
||||
|
||||
|
||||
def _stabilize_rebuild_cwd(watch_path: Path) -> bool:
|
||||
"""Ensure relative rebuild paths have a usable CWD before queue/lock setup.
|
||||
|
||||
Detached git hooks can inherit a transient working directory that is deleted
|
||||
before the background rebuild starts. In that state Path.cwd(),
|
||||
Path('.').resolve(), and relative graphify-out mkdirs raise FileNotFoundError
|
||||
before the normal rebuild error handling can run. Hooks that know the repo
|
||||
root export GRAPHIFY_REPO_ROOT so the rebuild can recover by chdir'ing there.
|
||||
"""
|
||||
if watch_path.is_absolute():
|
||||
return True
|
||||
|
||||
repo_root = os.environ.get("GRAPHIFY_REPO_ROOT", "").strip()
|
||||
if repo_root and Path(repo_root).is_dir():
|
||||
try:
|
||||
os.chdir(repo_root)
|
||||
return True
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
try:
|
||||
Path.cwd()
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
print(
|
||||
"[graphify watch] Rebuild failed: current working directory "
|
||||
"no longer exists and GRAPHIFY_REPO_ROOT is not set."
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def _rebuild_code(
|
||||
watch_path: Path,
|
||||
*,
|
||||
@@ -690,6 +721,9 @@ def _rebuild_code(
|
||||
|
||||
Returns True on success, False on error or skipped-due-to-lock.
|
||||
"""
|
||||
if not _stabilize_rebuild_cwd(watch_path):
|
||||
return False
|
||||
|
||||
out = watch_path / _GRAPHIFY_OUT
|
||||
if acquire_lock:
|
||||
# #1059: incremental (changed_paths is not None) hooks must not drop
|
||||
|
||||
@@ -176,6 +176,56 @@ def test_graphify_root_preserves_absolute_when_user_supplied(tmp_path):
|
||||
)
|
||||
|
||||
|
||||
def test_rebuild_code_deleted_cwd_without_repo_root_returns_false(tmp_path, monkeypatch, capsys):
|
||||
"""Detached hooks can inherit a CWD that no longer exists.
|
||||
|
||||
Without GRAPHIFY_REPO_ROOT, the rebuild should fail cleanly before creating
|
||||
relative graphify-out queue/lock files.
|
||||
"""
|
||||
from graphify.watch import _rebuild_code
|
||||
|
||||
old_cwd = Path.cwd()
|
||||
gone = tmp_path / "gone"
|
||||
gone.mkdir()
|
||||
monkeypatch.delenv("GRAPHIFY_REPO_ROOT", raising=False)
|
||||
|
||||
os.chdir(gone)
|
||||
gone.rmdir()
|
||||
try:
|
||||
assert _rebuild_code(Path("."), changed_paths=[Path("lib.py")]) is False
|
||||
finally:
|
||||
os.chdir(old_cwd)
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert "current working directory no longer exists" in out
|
||||
|
||||
|
||||
def test_rebuild_code_deleted_cwd_uses_graphify_repo_root(tmp_path, monkeypatch):
|
||||
"""GRAPHIFY_REPO_ROOT lets detached hook rebuilds recover from a deleted CWD."""
|
||||
from graphify.watch import _rebuild_code
|
||||
|
||||
old_cwd = Path.cwd()
|
||||
corpus = tmp_path / "corpus"
|
||||
corpus.mkdir()
|
||||
(corpus / "lib.py").write_text("def f(): pass\n", encoding="utf-8")
|
||||
gone = tmp_path / "gone"
|
||||
gone.mkdir()
|
||||
monkeypatch.setenv("GRAPHIFY_REPO_ROOT", str(corpus))
|
||||
|
||||
os.chdir(gone)
|
||||
gone.rmdir()
|
||||
try:
|
||||
assert _rebuild_code(
|
||||
Path("."),
|
||||
changed_paths=[Path("lib.py")],
|
||||
no_cluster=True,
|
||||
) is True
|
||||
assert Path.cwd().resolve() == corpus.resolve()
|
||||
assert (corpus / "graphify-out" / "graph.json").exists()
|
||||
finally:
|
||||
os.chdir(old_cwd)
|
||||
|
||||
|
||||
def test_rebuild_code_evicts_nodes_from_deleted_files(tmp_path):
|
||||
"""#1007: graphify update (_rebuild_code with no changed_paths) must remove
|
||||
nodes and edges from files deleted since the last run."""
|
||||
|
||||
Reference in New Issue
Block a user