From 66c1485051f8a0accea0757cff7c71831b70c4b8 Mon Sep 17 00:00:00 2001 From: Lihua <1017343802@qq.com> Date: Sun, 13 Sep 2026 02:18:48 -0700 Subject: [PATCH 1/3] fix(doctor): decode Git output explicitly as UTF-8 Signed-off-by: Lihua <1017343802@qq.com> --- loopx/doctor.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/loopx/doctor.py b/loopx/doctor.py index 1426a202f4..9d963a303b 100644 --- a/loopx/doctor.py +++ b/loopx/doctor.py @@ -283,6 +283,8 @@ def _run(args: list[str]) -> str | None: check=False, capture_output=True, text=True, + encoding="utf-8", + errors="replace", ) except OSError: return None @@ -343,6 +345,8 @@ def _is_ancestor(ancestor: str, descendant: str) -> bool | None: check=False, capture_output=True, text=True, + encoding="utf-8", + errors="replace", ) except OSError: return None @@ -395,6 +399,8 @@ def trusted_release_ref_for_root( check=False, capture_output=True, text=True, + encoding="utf-8", + errors="replace", ) except OSError: return None @@ -411,6 +417,8 @@ def trusted_release_ref_for_root( check=False, capture_output=True, text=True, + encoding="utf-8", + errors="replace", ) except OSError: continue @@ -425,6 +433,8 @@ def trusted_release_ref_for_root( check=False, capture_output=True, text=True, + encoding="utf-8", + errors="replace", ) commit = resolved.stdout.strip() if resolved.returncode == 0 else "" if commit: From d7b19786f52628855261ac4b353bf5b00e78a863 Mon Sep 17 00:00:00 2001 From: Lihua <1017343802@qq.com> Date: Sun, 13 Sep 2026 02:18:51 -0700 Subject: [PATCH 2/3] test(doctor): cover Git decoding on non-UTF-8 hosts Signed-off-by: Lihua <1017343802@qq.com> --- .github/workflows/python-tests.yml | 1 + tests/test_doctor_git_encoding.py | 170 +++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 tests/test_doctor_git_encoding.py diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 979ba6c19a..6931eda01c 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -474,6 +474,7 @@ jobs: run: >- python -m pytest -q tests/test_command_invocation.py + tests/test_doctor_git_encoding.py tests/test_doctor_install_freshness.py tests/test_file_lock.py tests/test_file_lock_cross_process.py diff --git a/tests/test_doctor_git_encoding.py b/tests/test_doctor_git_encoding.py new file mode 100644 index 0000000000..7c8d436919 --- /dev/null +++ b/tests/test_doctor_git_encoding.py @@ -0,0 +1,170 @@ +"""Doctor's Git reads must survive non-UTF-8 host locales.""" + +from __future__ import annotations + +import subprocess +import sys +from types import SimpleNamespace + +import pytest + +from loopx import doctor + +pytestmark = pytest.mark.filterwarnings( + "error::pytest.PytestUnhandledThreadExceptionWarning" +) + + +@pytest.fixture +def git_repo(tmp_path): + def git(*args): + return subprocess.run( + ["git", "-C", str(tmp_path), *args], + check=True, + capture_output=True, + encoding="utf-8", + ).stdout.strip() + + git("init") + git("config", "user.email", "loopx@example.invalid") + git("config", "user.name", "LoopX Test") + git("commit", "--allow-empty", "-m", "fixture") + return tmp_path, git + + +@pytest.fixture +def gbk_host(monkeypatch): + # Same decoder selection used by Windows cp936, with real subprocess pipes. + monkeypatch.setattr(subprocess, "_text_encoding", lambda: "gbk") + + +@pytest.mark.parametrize("detached", [False, True]) +def test_git_metadata_preserves_unicode_refs(git_repo, gbk_host, detached): + root, git = git_repo + ref = "修复中文🚀" + if detached: + git("tag", ref) + git("checkout", "--detach") + else: + git("branch", "-m", ref) + + metadata = doctor.git_metadata_for_root(root) + assert metadata["git_commit"] == git("rev-parse", "HEAD") + assert metadata["git_ref"] == ref + assert metadata["git_dirty"] is False + (root / "中文.txt").write_text("fixture", encoding="utf-8") + assert doctor.git_metadata_for_root(root)["git_dirty"] is True + + +def test_trusted_release_preserves_unicode_remote_and_ref(git_repo, gbk_host): + root, git = git_repo + commit = git("rev-parse", "HEAD") + git("remote", "add", "上游🚀", "https://github.com/example/project.git") + git("update-ref", "refs/remotes/上游🚀/发布", commit) + + trusted = doctor.trusted_release_ref_for_root( + root, repository="example/project", ref="发布" + ) + assert trusted is not None + assert trusted["git_commit"] == commit + assert trusted["git_ref"] == "上游🚀/发布" + assert ( + doctor.trusted_release_ref_for_root( + root, repository="someone-else/project", ref="发布" + ) + is None + ) + + +@pytest.fixture +def git_output(monkeypatch, gbk_host): + def install(*, returncode=0, fail_at=None): + calls = [] + + def run(command, **kwargs): + args = command[3:] + calls.append(args) + code = ( + returncode[len(calls) - 1] + if isinstance(returncode, tuple) + else returncode + ) + if fail_at is not None and args[: len(fail_at)] != fail_at: + code = 0 + if args == ["remote"]: + stdout = b"origin\n" + elif args[:2] == ["remote", "get-url"]: + stdout = b"https://github.com/example/project.git\n" + elif args[0] == "symbolic-ref": + stdout = b"before\xffafter\n" + elif args[0] == "rev-parse": + stdout = b"a" * 40 + b"\n" + else: + stdout = b"" + script = ( + "import sys; " + f"sys.stdout.buffer.write({stdout!r}); " + "sys.stderr.buffer.write(b'warning: \\xff'); " + f"sys.exit({code})" + ) + return subprocess.run( + [sys.executable, "-c", script], check=kwargs.pop("check"), **kwargs + ) + + monkeypatch.setattr(doctor, "subprocess", SimpleNamespace(run=run)) + return calls + + return install + + +def test_metadata_replaces_malformed_bytes(git_output, tmp_path): + git_output() + metadata = doctor.git_metadata_for_root(tmp_path) + assert metadata["git_commit"] == "a" * 40 + assert metadata["git_ref"] == "before\ufffdafter" + assert metadata["git_dirty"] is False + + +@pytest.mark.parametrize( + ("returncodes", "expected"), + [ + ((0, 1), "installed_ahead"), + ((1, 0), "installed_behind"), + ((1, 1), "diverged"), + ((128, 1), "unknown"), + ], +) +def test_revision_relation_tolerates_malformed_stderr( + git_output, tmp_path, returncodes, expected +): + calls = git_output(returncode=returncodes) + relation = doctor.git_revision_relation( + tmp_path, installed_commit="a" * 40, comparison_commit="b" * 40 + ) + assert relation == expected + assert len(calls) == 2 + + +@pytest.mark.parametrize( + "fail_at", [None, ["remote"], ["remote", "get-url"], ["rev-parse"]] +) +def test_trusted_release_tolerates_malformed_stderr(git_output, tmp_path, fail_at): + calls = git_output(returncode=128 if fail_at else 0, fail_at=fail_at) + trusted = doctor.trusted_release_ref_for_root( + tmp_path, repository="example/project", ref="main" + ) + if fail_at: + assert trusted is None + else: + assert trusted is not None + assert trusted["git_commit"] == "a" * 40 + assert trusted["git_ref"] == "origin/main" + assert len(calls) == 3 + + +def test_metadata_failure_remains_unavailable(git_output, tmp_path): + git_output(returncode=128) + metadata = doctor.git_metadata_for_root(tmp_path) + assert metadata["git_commit"] is None + assert metadata["git_ref"] is None + assert metadata["git_dirty"] is None From 7245a1ac609c0ff40521e4c3f48e724d1a7fc6ae Mon Sep 17 00:00:00 2001 From: Lihua <1017343802@qq.com> Date: Sun, 13 Sep 2026 07:38:50 -0700 Subject: [PATCH 3/3] refactor(doctor): isolate Git diagnostics within module budget Signed-off-by: Lihua <1017343802@qq.com> --- loopx/doctor.py | 219 ++---------------------- loopx/doctor_git.py | 222 +++++++++++++++++++++++++ tests/test_doctor_git_encoding.py | 20 +-- tests/test_doctor_install_freshness.py | 3 +- 4 files changed, 243 insertions(+), 221 deletions(-) create mode 100644 loopx/doctor_git.py diff --git a/loopx/doctor.py b/loopx/doctor.py index 9d963a303b..c3610b2a00 100644 --- a/loopx/doctor.py +++ b/loopx/doctor.py @@ -1,18 +1,16 @@ from __future__ import annotations from datetime import datetime, timezone -from enum import Enum from importlib.metadata import PackageNotFoundError, distribution import json import os import re import shlex -import subprocess import sys from pathlib import Path from typing import Any -from . import __version__ +from . import __version__, doctor_git from .command_invocation import resolve_command_path from .control_plane.runtime.promotion_readiness import ( PROMOTION_READINESS_CLASSIFICATION, @@ -81,14 +79,6 @@ } -class GitRevisionRelation(str, Enum): - SAME = "same" - INSTALLED_AHEAD = "installed_ahead" - INSTALLED_BEHIND = "installed_behind" - DIVERGED = "diverged" - UNKNOWN = "unknown" - - def _powershell_literal(value: str | Path) -> str: return "'" + str(value).replace("'", "''") + "'" @@ -256,197 +246,6 @@ def short_revision(value: Any, *, length: int = 12) -> str | None: return text[:length] if len(text) > length else text -def git_metadata_for_root(root: Path | None) -> dict[str, Any]: - if root is None: - return { - "root": None, - "git_commit": None, - "git_ref": None, - "git_dirty": None, - } - try: - source_root = root.expanduser().resolve() - except OSError: - source_root = root.expanduser() - if not source_root.exists(): - return { - "root": str(source_root), - "git_commit": None, - "git_ref": None, - "git_dirty": None, - } - - def _run(args: list[str]) -> str | None: - try: - result = subprocess.run( - ["git", "-C", str(source_root), *args], - check=False, - capture_output=True, - text=True, - encoding="utf-8", - errors="replace", - ) - except OSError: - return None - if result.returncode != 0: - return None - return result.stdout.strip() or None - - commit = _run(["rev-parse", "HEAD"]) - branch = _run(["symbolic-ref", "--quiet", "--short", "HEAD"]) - tag = _run(["describe", "--tags", "--exact-match"]) - status = _run(["status", "--porcelain"]) - dirty = None if commit is None and branch is None and tag is None and status is None else bool(status) - return { - "root": str(source_root), - "git_commit": commit, - "git_ref": branch or tag, - "git_dirty": dirty, - } - - -def git_revision_relation( - root: Path | None, - *, - installed_commit: Any, - comparison_commit: Any, -) -> GitRevisionRelation: - """Classify installed vs comparison revisions in one Git object graph.""" - if not isinstance(installed_commit, str) or not installed_commit.strip(): - return GitRevisionRelation.UNKNOWN - if not isinstance(comparison_commit, str) or not comparison_commit.strip(): - return GitRevisionRelation.UNKNOWN - installed_commit = installed_commit.strip() - comparison_commit = comparison_commit.strip() - if installed_commit == comparison_commit: - return GitRevisionRelation.SAME - if root is None: - return GitRevisionRelation.UNKNOWN - - try: - source_root = root.expanduser().resolve() - except OSError: - source_root = root.expanduser() - if not source_root.exists(): - return GitRevisionRelation.UNKNOWN - - def _is_ancestor(ancestor: str, descendant: str) -> bool | None: - try: - result = subprocess.run( - [ - "git", - "-C", - str(source_root), - "merge-base", - "--is-ancestor", - ancestor, - descendant, - ], - check=False, - capture_output=True, - text=True, - encoding="utf-8", - errors="replace", - ) - except OSError: - return None - if result.returncode == 0: - return True - if result.returncode == 1: - return False - return None - - comparison_is_ancestor = _is_ancestor(comparison_commit, installed_commit) - installed_is_ancestor = _is_ancestor(installed_commit, comparison_commit) - if comparison_is_ancestor is None or installed_is_ancestor is None: - return GitRevisionRelation.UNKNOWN - if comparison_is_ancestor: - return GitRevisionRelation.INSTALLED_AHEAD - if installed_is_ancestor: - return GitRevisionRelation.INSTALLED_BEHIND - return GitRevisionRelation.DIVERGED - - -def _github_repository_from_remote_url(value: Any) -> str | None: - text = str(value or "").strip().removesuffix(".git") - match = re.search(r"github\.com(?::|/)([^/\s]+/[^/\s]+)$", text, flags=re.IGNORECASE) - return match.group(1).lower() if match else None - - -def trusted_release_ref_for_root( - root: Path | None, - *, - repository: Any, - ref: Any, -) -> dict[str, Any] | None: - """Resolve the manifest repository's fetched ref without trusting canary HEAD.""" - expected_repository = _github_repository_from_remote_url(repository) or ( - str(repository or "").strip().removesuffix(".git").lower() - ) - expected_ref = str(ref or "").strip().removeprefix("refs/heads/") - if root is None or not expected_repository or not expected_ref: - return None - try: - source_root = root.expanduser().resolve() - except OSError: - source_root = root.expanduser() - if not source_root.exists(): - return None - - try: - remotes = subprocess.run( - ["git", "-C", str(source_root), "remote"], - check=False, - capture_output=True, - text=True, - encoding="utf-8", - errors="replace", - ) - except OSError: - return None - if remotes.returncode != 0: - return None - - for remote in remotes.stdout.splitlines(): - remote = remote.strip() - if not remote: - continue - try: - remote_url = subprocess.run( - ["git", "-C", str(source_root), "remote", "get-url", remote], - check=False, - capture_output=True, - text=True, - encoding="utf-8", - errors="replace", - ) - except OSError: - continue - if ( - remote_url.returncode != 0 - or _github_repository_from_remote_url(remote_url.stdout) != expected_repository - ): - continue - trusted_ref = f"refs/remotes/{remote}/{expected_ref}" - resolved = subprocess.run( - ["git", "-C", str(source_root), "rev-parse", "--verify", f"{trusted_ref}^{{commit}}"], - check=False, - capture_output=True, - text=True, - encoding="utf-8", - errors="replace", - ) - commit = resolved.stdout.strip() if resolved.returncode == 0 else "" - if commit: - return { - "label": f"{expected_repository}@{expected_ref}", - "root": str(source_root), - "git_commit": commit, - "git_ref": f"{remote}/{expected_ref}", - } - return None - - def build_install_freshness( *, command_path: Path | None, @@ -581,7 +380,7 @@ def build_install_freshness( ) source_commit_is_behind = ( manifest_source_matches_freshness_source is False - and freshness_revision_relation == GitRevisionRelation.INSTALLED_BEHIND + and freshness_revision_relation == doctor_git.GitRevisionRelation.INSTALLED_BEHIND ) if ( @@ -660,7 +459,7 @@ def build_install_freshness( "manifest_source_matches_comparison": manifest_source_matches_comparison, "manifest_source_comparison_relation": ( comparison_revision_relation.value - if isinstance(comparison_revision_relation, GitRevisionRelation) + if isinstance(comparison_revision_relation, doctor_git.GitRevisionRelation) else comparison_revision_relation ), "freshness_source_label": freshness_source_label if trusted else None, @@ -671,7 +470,7 @@ def build_install_freshness( "manifest_source_matches_freshness_source": manifest_source_matches_freshness_source, "manifest_source_freshness_relation": ( freshness_revision_relation.value - if isinstance(freshness_revision_relation, GitRevisionRelation) + if isinstance(freshness_revision_relation, doctor_git.GitRevisionRelation) else freshness_revision_relation ), "manifest_archive_sha256": manifest_source.get("archive_sha256"), @@ -946,7 +745,9 @@ def collect_doctor( release_manifest = load_release_manifest(release_root) comparison_source = None if canary_realpath and command_realpath and canary_realpath != command_realpath: - comparison_source = git_metadata_for_root(command_release_root(canary_realpath)) + comparison_source = doctor_git.git_metadata_for_root( + command_release_root(canary_realpath) + ) comparison_source["label"] = "loopx-canary" path_entries = os.environ.get("PATH", "").split(os.pathsep) local_bin = user_local_bin() @@ -981,12 +782,12 @@ def collect_doctor( ) if comparison_source: comparison_root = comparison_source.get("root") - comparison_source["revision_relation"] = git_revision_relation( + comparison_source["revision_relation"] = doctor_git.git_revision_relation( Path(str(comparison_root)) if comparison_root else None, installed_commit=release_manifest_source.get("git_commit"), comparison_commit=comparison_source.get("git_commit"), ) - freshness_source = trusted_release_ref_for_root( + freshness_source = doctor_git.trusted_release_ref_for_root( Path(str(comparison_source.get("root"))) if comparison_source and comparison_source.get("root") else None, @@ -994,7 +795,7 @@ def collect_doctor( ref=release_manifest_source.get("ref"), ) if freshness_source: - freshness_source["revision_relation"] = git_revision_relation( + freshness_source["revision_relation"] = doctor_git.git_revision_relation( Path(str(freshness_source.get("root"))), installed_commit=release_manifest_source.get("git_commit"), comparison_commit=freshness_source.get("git_commit"), diff --git a/loopx/doctor_git.py b/loopx/doctor_git.py new file mode 100644 index 0000000000..ef18f4d055 --- /dev/null +++ b/loopx/doctor_git.py @@ -0,0 +1,222 @@ +"""Git source and revision diagnostics for the installation doctor.""" + +from __future__ import annotations + +import re +import subprocess +from enum import Enum +from pathlib import Path +from typing import Any + + +class GitRevisionRelation(str, Enum): + SAME = "same" + INSTALLED_AHEAD = "installed_ahead" + INSTALLED_BEHIND = "installed_behind" + DIVERGED = "diverged" + UNKNOWN = "unknown" + + +def git_metadata_for_root(root: Path | None) -> dict[str, Any]: + if root is None: + return { + "root": None, + "git_commit": None, + "git_ref": None, + "git_dirty": None, + } + try: + source_root = root.expanduser().resolve() + except OSError: + source_root = root.expanduser() + if not source_root.exists(): + return { + "root": str(source_root), + "git_commit": None, + "git_ref": None, + "git_dirty": None, + } + + def _run(args: list[str]) -> str | None: + try: + result = subprocess.run( + ["git", "-C", str(source_root), *args], + check=False, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + ) + except OSError: + return None + if result.returncode != 0: + return None + return result.stdout.strip() or None + + commit = _run(["rev-parse", "HEAD"]) + branch = _run(["symbolic-ref", "--quiet", "--short", "HEAD"]) + tag = _run(["describe", "--tags", "--exact-match"]) + status = _run(["status", "--porcelain"]) + dirty = ( + None + if commit is None and branch is None and tag is None and status is None + else bool(status) + ) + return { + "root": str(source_root), + "git_commit": commit, + "git_ref": branch or tag, + "git_dirty": dirty, + } + + +def git_revision_relation( + root: Path | None, + *, + installed_commit: Any, + comparison_commit: Any, +) -> GitRevisionRelation: + """Classify installed vs comparison revisions in one Git object graph.""" + if not isinstance(installed_commit, str) or not installed_commit.strip(): + return GitRevisionRelation.UNKNOWN + if not isinstance(comparison_commit, str) or not comparison_commit.strip(): + return GitRevisionRelation.UNKNOWN + installed_commit = installed_commit.strip() + comparison_commit = comparison_commit.strip() + if installed_commit == comparison_commit: + return GitRevisionRelation.SAME + if root is None: + return GitRevisionRelation.UNKNOWN + + try: + source_root = root.expanduser().resolve() + except OSError: + source_root = root.expanduser() + if not source_root.exists(): + return GitRevisionRelation.UNKNOWN + + def _is_ancestor(ancestor: str, descendant: str) -> bool | None: + try: + result = subprocess.run( + [ + "git", + "-C", + str(source_root), + "merge-base", + "--is-ancestor", + ancestor, + descendant, + ], + check=False, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + ) + except OSError: + return None + if result.returncode == 0: + return True + if result.returncode == 1: + return False + return None + + comparison_is_ancestor = _is_ancestor(comparison_commit, installed_commit) + installed_is_ancestor = _is_ancestor(installed_commit, comparison_commit) + if comparison_is_ancestor is None or installed_is_ancestor is None: + return GitRevisionRelation.UNKNOWN + if comparison_is_ancestor: + return GitRevisionRelation.INSTALLED_AHEAD + if installed_is_ancestor: + return GitRevisionRelation.INSTALLED_BEHIND + return GitRevisionRelation.DIVERGED + + +def _github_repository_from_remote_url(value: Any) -> str | None: + text = str(value or "").strip().removesuffix(".git") + match = re.search( + r"github\.com(?::|/)([^/\s]+/[^/\s]+)$", text, flags=re.IGNORECASE + ) + return match.group(1).lower() if match else None + + +def trusted_release_ref_for_root( + root: Path | None, + *, + repository: Any, + ref: Any, +) -> dict[str, Any] | None: + """Resolve the manifest repository's fetched ref without trusting canary HEAD.""" + expected_repository = _github_repository_from_remote_url(repository) or ( + str(repository or "").strip().removesuffix(".git").lower() + ) + expected_ref = str(ref or "").strip().removeprefix("refs/heads/") + if root is None or not expected_repository or not expected_ref: + return None + try: + source_root = root.expanduser().resolve() + except OSError: + source_root = root.expanduser() + if not source_root.exists(): + return None + + try: + remotes = subprocess.run( + ["git", "-C", str(source_root), "remote"], + check=False, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + ) + except OSError: + return None + if remotes.returncode != 0: + return None + + for remote in remotes.stdout.splitlines(): + remote = remote.strip() + if not remote: + continue + try: + remote_url = subprocess.run( + ["git", "-C", str(source_root), "remote", "get-url", remote], + check=False, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + ) + except OSError: + continue + if ( + remote_url.returncode != 0 + or _github_repository_from_remote_url(remote_url.stdout) + != expected_repository + ): + continue + trusted_ref = f"refs/remotes/{remote}/{expected_ref}" + resolved = subprocess.run( + [ + "git", + "-C", + str(source_root), + "rev-parse", + "--verify", + f"{trusted_ref}^{{commit}}", + ], + check=False, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + ) + commit = resolved.stdout.strip() if resolved.returncode == 0 else "" + if commit: + return { + "label": f"{expected_repository}@{expected_ref}", + "root": str(source_root), + "git_commit": commit, + "git_ref": f"{remote}/{expected_ref}", + } + return None diff --git a/tests/test_doctor_git_encoding.py b/tests/test_doctor_git_encoding.py index 7c8d436919..aa7484db12 100644 --- a/tests/test_doctor_git_encoding.py +++ b/tests/test_doctor_git_encoding.py @@ -8,7 +8,7 @@ import pytest -from loopx import doctor +from loopx import doctor_git pytestmark = pytest.mark.filterwarnings( "error::pytest.PytestUnhandledThreadExceptionWarning" @@ -48,12 +48,12 @@ def test_git_metadata_preserves_unicode_refs(git_repo, gbk_host, detached): else: git("branch", "-m", ref) - metadata = doctor.git_metadata_for_root(root) + metadata = doctor_git.git_metadata_for_root(root) assert metadata["git_commit"] == git("rev-parse", "HEAD") assert metadata["git_ref"] == ref assert metadata["git_dirty"] is False (root / "中文.txt").write_text("fixture", encoding="utf-8") - assert doctor.git_metadata_for_root(root)["git_dirty"] is True + assert doctor_git.git_metadata_for_root(root)["git_dirty"] is True def test_trusted_release_preserves_unicode_remote_and_ref(git_repo, gbk_host): @@ -62,14 +62,14 @@ def test_trusted_release_preserves_unicode_remote_and_ref(git_repo, gbk_host): git("remote", "add", "上游🚀", "https://github.com/example/project.git") git("update-ref", "refs/remotes/上游🚀/发布", commit) - trusted = doctor.trusted_release_ref_for_root( + trusted = doctor_git.trusted_release_ref_for_root( root, repository="example/project", ref="发布" ) assert trusted is not None assert trusted["git_commit"] == commit assert trusted["git_ref"] == "上游🚀/发布" assert ( - doctor.trusted_release_ref_for_root( + doctor_git.trusted_release_ref_for_root( root, repository="someone-else/project", ref="发布" ) is None @@ -111,7 +111,7 @@ def run(command, **kwargs): [sys.executable, "-c", script], check=kwargs.pop("check"), **kwargs ) - monkeypatch.setattr(doctor, "subprocess", SimpleNamespace(run=run)) + monkeypatch.setattr(doctor_git, "subprocess", SimpleNamespace(run=run)) return calls return install @@ -119,7 +119,7 @@ def run(command, **kwargs): def test_metadata_replaces_malformed_bytes(git_output, tmp_path): git_output() - metadata = doctor.git_metadata_for_root(tmp_path) + metadata = doctor_git.git_metadata_for_root(tmp_path) assert metadata["git_commit"] == "a" * 40 assert metadata["git_ref"] == "before\ufffdafter" assert metadata["git_dirty"] is False @@ -138,7 +138,7 @@ def test_revision_relation_tolerates_malformed_stderr( git_output, tmp_path, returncodes, expected ): calls = git_output(returncode=returncodes) - relation = doctor.git_revision_relation( + relation = doctor_git.git_revision_relation( tmp_path, installed_commit="a" * 40, comparison_commit="b" * 40 ) assert relation == expected @@ -150,7 +150,7 @@ def test_revision_relation_tolerates_malformed_stderr( ) def test_trusted_release_tolerates_malformed_stderr(git_output, tmp_path, fail_at): calls = git_output(returncode=128 if fail_at else 0, fail_at=fail_at) - trusted = doctor.trusted_release_ref_for_root( + trusted = doctor_git.trusted_release_ref_for_root( tmp_path, repository="example/project", ref="main" ) if fail_at: @@ -164,7 +164,7 @@ def test_trusted_release_tolerates_malformed_stderr(git_output, tmp_path, fail_a def test_metadata_failure_remains_unavailable(git_output, tmp_path): git_output(returncode=128) - metadata = doctor.git_metadata_for_root(tmp_path) + metadata = doctor_git.git_metadata_for_root(tmp_path) assert metadata["git_commit"] is None assert metadata["git_ref"] is None assert metadata["git_dirty"] is None diff --git a/tests/test_doctor_install_freshness.py b/tests/test_doctor_install_freshness.py index e8cd0287ea..1572f08ed6 100644 --- a/tests/test_doctor_install_freshness.py +++ b/tests/test_doctor_install_freshness.py @@ -12,11 +12,10 @@ REQUIRED_INSTALLED_SKILL_PHRASES, build_install_freshness, current_script_invocation_path, - git_revision_relation, installed_skill_summary, python_distribution_install, - trusted_release_ref_for_root, ) +from loopx.doctor_git import git_revision_relation, trusted_release_ref_for_root class _FakeDistributionFile: