From 861746d7624f3effd9623993b3b0529dd786ae88 Mon Sep 17 00:00:00 2001 From: ZKDplz <2646995705@qq.com> Date: Fri, 6 Nov 2026 10:35:12 +0800 Subject: [PATCH 1/2] fix(registry): reject unknown tracking in strict checks Signed-off-by: ZKDplz <2646995705@qq.com> --- loopx/cli_commands/support_control.py | 13 ++++++----- loopx/registry.py | 6 +++-- tests/test_registry_git_probe.py | 33 +++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/loopx/cli_commands/support_control.py b/loopx/cli_commands/support_control.py index 38f4641701..82a6ce426d 100644 --- a/loopx/cli_commands/support_control.py +++ b/loopx/cli_commands/support_control.py @@ -120,7 +120,10 @@ def register_support_control_commands( registry_boundary_parser.add_argument( "--require-not-tracked", action="store_true", - help="Return non-zero if the registry is tracked while publication policy disallows pushing it.", + help=( + "For registries that cannot be pushed, return non-zero if tracked " + "or if Git cannot verify tracking inside a worktree." + ), ) registry_boundary_parser.add_argument( "--require-gitignored", @@ -309,14 +312,12 @@ def handle_support_control_command( if ( args.require_not_tracked and payload.get("ok") - and git.get("tracked") and not payload.get("github_push_allowed") + and git.get("inside_worktree") + and git.get("probe_status") == "git_unavailable" ): - payload = dict(payload) payload["ok"] = False - payload.setdefault("risks", []).append( - "registry_tracked_but_not_push_allowed" - ) + payload["error"] = "Could not determine whether the registry is tracked by Git." if ( args.require_gitignored and payload.get("ok") diff --git a/loopx/registry.py b/loopx/registry.py index d944d9f3e3..f3e46cdf9c 100644 --- a/loopx/registry.py +++ b/loopx/registry.py @@ -198,6 +198,7 @@ def run_git(*args: str, cwd: Path = probe_dir) -> subprocess.CompletedProcess[st if root is None: return unavailable() inside = root.returncode == 0 + tracking_available = True tracked = False ignored = False if inside: @@ -208,11 +209,12 @@ def run_git(*args: str, cwd: Path = probe_dir) -> subprocess.CompletedProcess[st rel_path = path.name tracked_result = run_git("ls-files", "--error-unmatch", "--", rel_path, cwd=root_path) ignored_result = run_git("check-ignore", "-q", "--", rel_path, cwd=root_path) + tracking_available = tracked_result is not None and tracked_result.returncode in (0, 1) tracked = tracked_result is not None and tracked_result.returncode == 0 ignored = ignored_result is not None and ignored_result.returncode == 0 return { - "available": True, - "probe_status": "ok", + "available": tracking_available, + "probe_status": "ok" if tracking_available else "git_unavailable", "inside_worktree": inside, "tracked": tracked, "ignored": ignored, diff --git a/tests/test_registry_git_probe.py b/tests/test_registry_git_probe.py index 24721dd692..dd1dd68682 100644 --- a/tests/test_registry_git_probe.py +++ b/tests/test_registry_git_probe.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json import os import signal import subprocess @@ -118,6 +119,38 @@ def unexpected_git(*_: object, **__: object) -> None: } +def test_registry_boundary_require_not_tracked_rejects_unreadable_index( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + from loopx.cli import main + + for variable in ("GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"): + monkeypatch.delenv(variable, raising=False) + repo = tmp_path / "repo" + repo.mkdir() + run_git(repo, "init", "--quiet") + registry = repo / ".loopx" / "registry.json" + registry.parent.mkdir() + registry.write_text('{"goals": []}\n', encoding="utf-8") + run_git(repo, "add", "--force", "--", ".loopx/registry.json") + bad_index = tmp_path / "bad-index" + bad_index.write_bytes(b"DIRC") + monkeypatch.setenv("GIT_INDEX_FILE", str(bad_index)) + + exit_code = main([ + "--format", "json", "--registry", str(registry), + "--runtime-root", str(tmp_path / "runtime"), "registry-boundary", "--require-not-tracked", + ]) + payload = json.loads(capsys.readouterr().out) + assert exit_code == 1, payload + assert payload["ok"] is False + assert payload["git"]["inside_worktree"] is True + assert payload["git"]["probe_status"] == "git_unavailable" + assert "tracked by Git" in payload["error"] + + @pytest.mark.skipif(os.name != "posix", reason="the probe's fake git is a POSIX shell script") def test_registry_git_probe_retires_the_process_tree_on_timeout( tmp_path: Path, From 92168b73461a2501369a16a4143a91680da3d642 Mon Sep 17 00:00:00 2001 From: ZKDplz <2646995705@qq.com> Date: Fri, 6 Nov 2026 14:13:16 +0800 Subject: [PATCH 2/2] fix(registry): reject failed Git root discovery Signed-off-by: ZKDplz <2646995705@qq.com> --- loopx/cli_commands/support_control.py | 4 ++-- loopx/registry.py | 34 +++++++++++---------------- tests/test_registry_git_probe.py | 17 ++++++++++---- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/loopx/cli_commands/support_control.py b/loopx/cli_commands/support_control.py index 82a6ce426d..f42fed65b5 100644 --- a/loopx/cli_commands/support_control.py +++ b/loopx/cli_commands/support_control.py @@ -122,7 +122,7 @@ def register_support_control_commands( action="store_true", help=( "For registries that cannot be pushed, return non-zero if tracked " - "or if Git cannot verify tracking inside a worktree." + "or if Git cannot verify the tracking state." ), ) registry_boundary_parser.add_argument( @@ -313,8 +313,8 @@ def handle_support_control_command( args.require_not_tracked and payload.get("ok") and not payload.get("github_push_allowed") - and git.get("inside_worktree") and git.get("probe_status") == "git_unavailable" + and (git.get("inside_worktree") or git.get("worktree_discovery_unknown")) ): payload["ok"] = False payload["error"] = "Could not determine whether the registry is tracked by Git." diff --git a/loopx/registry.py b/loopx/registry.py index f3e46cdf9c..3fcc199448 100644 --- a/loopx/registry.py +++ b/loopx/registry.py @@ -195,29 +195,23 @@ def run_git(*args: str, cwd: Path = probe_dir) -> subprocess.CompletedProcess[st } root = run_git("rev-parse", "--show-toplevel") - if root is None: - return unavailable() - inside = root.returncode == 0 - tracking_available = True - tracked = False - ignored = False - if inside: - root_path = Path(root.stdout.strip()) - try: - rel_path = os.path.relpath(path.resolve(), root_path) - except (OSError, ValueError): - rel_path = path.name - tracked_result = run_git("ls-files", "--error-unmatch", "--", rel_path, cwd=root_path) - ignored_result = run_git("check-ignore", "-q", "--", rel_path, cwd=root_path) - tracking_available = tracked_result is not None and tracked_result.returncode in (0, 1) - tracked = tracked_result is not None and tracked_result.returncode == 0 - ignored = ignored_result is not None and ignored_result.returncode == 0 + if root is None or root.returncode != 0: + return {**unavailable(), "worktree_discovery_unknown": True} + + root_path = Path(root.stdout.strip()) + try: + rel_path = os.path.relpath(path.resolve(), root_path) + except (OSError, ValueError): + rel_path = path.name + tracked_result = run_git("ls-files", "--error-unmatch", "--", rel_path, cwd=root_path) + ignored_result = run_git("check-ignore", "-q", "--", rel_path, cwd=root_path) + tracking_available = tracked_result is not None and tracked_result.returncode in (0, 1) return { "available": tracking_available, "probe_status": "ok" if tracking_available else "git_unavailable", - "inside_worktree": inside, - "tracked": tracked, - "ignored": ignored, + "inside_worktree": True, + "tracked": tracked_result is not None and tracked_result.returncode == 0, + "ignored": ignored_result is not None and ignored_result.returncode == 0, "worktree_root_recorded": False, } diff --git a/tests/test_registry_git_probe.py b/tests/test_registry_git_probe.py index dd1dd68682..20fd9e1170 100644 --- a/tests/test_registry_git_probe.py +++ b/tests/test_registry_git_probe.py @@ -119,10 +119,12 @@ def unexpected_git(*_: object, **__: object) -> None: } -def test_registry_boundary_require_not_tracked_rejects_unreadable_index( +@pytest.mark.parametrize("root_failure", [False, True], ids=["bad-index", "unknown-root"]) +def test_registry_boundary_require_not_tracked_rejects_unavailable_git( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], + root_failure: bool, ) -> None: from loopx.cli import main @@ -135,9 +137,12 @@ def test_registry_boundary_require_not_tracked_rejects_unreadable_index( registry.parent.mkdir() registry.write_text('{"goals": []}\n', encoding="utf-8") run_git(repo, "add", "--force", "--", ".loopx/registry.json") - bad_index = tmp_path / "bad-index" - bad_index.write_bytes(b"DIRC") - monkeypatch.setenv("GIT_INDEX_FILE", str(bad_index)) + if root_failure: + monkeypatch.setenv("GIT_DIR", str(tmp_path / "missing.git")) + else: + bad_index = tmp_path / "bad-index" + bad_index.write_bytes(b"DIRC") + monkeypatch.setenv("GIT_INDEX_FILE", str(bad_index)) exit_code = main([ "--format", "json", "--registry", str(registry), @@ -146,7 +151,8 @@ def test_registry_boundary_require_not_tracked_rejects_unreadable_index( payload = json.loads(capsys.readouterr().out) assert exit_code == 1, payload assert payload["ok"] is False - assert payload["git"]["inside_worktree"] is True + assert payload["git"]["inside_worktree"] is (not root_failure) + assert payload["git"].get("worktree_discovery_unknown", False) is root_failure assert payload["git"]["probe_status"] == "git_unavailable" assert "tracked by Git" in payload["error"] @@ -177,6 +183,7 @@ def test_registry_git_probe_retires_the_process_tree_on_timeout( observed = registry_module._registry_git_probe(probe / "registry.json") assert observed["probe_status"] == "git_unavailable" + assert observed["worktree_discovery_unknown"] is True pid = int(grandchild.read_text(encoding="utf-8").strip()) deadline = time.monotonic() + 10