refactor(mirror): probe remotes with detailed reasons and provision all git mirrors
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / lint-shell (push) Has been cancelled
Mark stable commit / lint-python (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / lint-shell (push) Has been cancelled
Mark stable commit / lint-python (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled
- Add probe_remote_reachable_detail and improved GitRunError metadata - Print short failure reasons for unreachable remotes - Provision each git mirror URL via ensure_remote_repository_for_url https://chatgpt.com/share/6946956e-f738-800f-a446-e2c8bf5595f4
This commit is contained in:
@@ -38,7 +38,6 @@ class TestMirrorSetupCmd(unittest.TestCase):
|
||||
ensure_remote=False,
|
||||
)
|
||||
|
||||
# ensure_origin_remote(repo, ctx, preview) is called positionally in your code
|
||||
m_ensure.assert_called_once()
|
||||
args, kwargs = m_ensure.call_args
|
||||
|
||||
@@ -50,13 +49,13 @@ class TestMirrorSetupCmd(unittest.TestCase):
|
||||
|
||||
@patch("pkgmgr.actions.mirror.setup_cmd.build_context")
|
||||
@patch("pkgmgr.actions.mirror.setup_cmd.determine_primary_remote_url")
|
||||
@patch("pkgmgr.actions.mirror.setup_cmd.probe_remote_reachable")
|
||||
@patch("pkgmgr.actions.mirror.setup_cmd.probe_remote_reachable_detail")
|
||||
def test_setup_mirrors_remote_no_mirrors_probes_primary(
|
||||
self, m_probe, m_primary, m_ctx
|
||||
self, m_probe_detail, m_primary, m_ctx
|
||||
) -> None:
|
||||
m_ctx.return_value = self._ctx(repo_dir="/tmp/repo", resolved={})
|
||||
m_primary.return_value = "git@github.com:alice/repo.git"
|
||||
m_probe.return_value = True
|
||||
m_probe_detail.return_value = (True, "")
|
||||
|
||||
repos = [{"provider": "github.com", "account": "alice", "repository": "repo"}]
|
||||
setup_mirrors(
|
||||
@@ -70,14 +69,14 @@ class TestMirrorSetupCmd(unittest.TestCase):
|
||||
)
|
||||
|
||||
m_primary.assert_called()
|
||||
m_probe.assert_called_once_with(
|
||||
m_probe_detail.assert_called_once_with(
|
||||
"git@github.com:alice/repo.git", cwd="/tmp/repo"
|
||||
)
|
||||
|
||||
@patch("pkgmgr.actions.mirror.setup_cmd.build_context")
|
||||
@patch("pkgmgr.actions.mirror.setup_cmd.probe_remote_reachable")
|
||||
@patch("pkgmgr.actions.mirror.setup_cmd.probe_remote_reachable_detail")
|
||||
def test_setup_mirrors_remote_with_mirrors_probes_each(
|
||||
self, m_probe, m_ctx
|
||||
self, m_probe_detail, m_ctx
|
||||
) -> None:
|
||||
m_ctx.return_value = self._ctx(
|
||||
repo_dir="/tmp/repo",
|
||||
@@ -86,7 +85,7 @@ class TestMirrorSetupCmd(unittest.TestCase):
|
||||
"backup": "ssh://git@git.veen.world:2201/alice/repo.git",
|
||||
},
|
||||
)
|
||||
m_probe.return_value = True
|
||||
m_probe_detail.return_value = (True, "")
|
||||
|
||||
repos = [{"provider": "github.com", "account": "alice", "repository": "repo"}]
|
||||
setup_mirrors(
|
||||
@@ -99,12 +98,105 @@ class TestMirrorSetupCmd(unittest.TestCase):
|
||||
ensure_remote=False,
|
||||
)
|
||||
|
||||
self.assertEqual(m_probe.call_count, 2)
|
||||
m_probe.assert_any_call("git@github.com:alice/repo.git", cwd="/tmp/repo")
|
||||
m_probe.assert_any_call(
|
||||
# Should probe BOTH git mirror URLs
|
||||
self.assertEqual(m_probe_detail.call_count, 2)
|
||||
m_probe_detail.assert_any_call("git@github.com:alice/repo.git", cwd="/tmp/repo")
|
||||
m_probe_detail.assert_any_call(
|
||||
"ssh://git@git.veen.world:2201/alice/repo.git", cwd="/tmp/repo"
|
||||
)
|
||||
|
||||
@patch("pkgmgr.actions.mirror.setup_cmd.build_context")
|
||||
@patch("pkgmgr.actions.mirror.setup_cmd.probe_remote_reachable_detail")
|
||||
@patch("pkgmgr.actions.mirror.setup_cmd.ensure_remote_repository_for_url")
|
||||
def test_setup_mirrors_remote_with_mirrors_ensure_remote_provisions_each(
|
||||
self, m_ensure_url, m_probe_detail, m_ctx
|
||||
) -> None:
|
||||
m_ctx.return_value = self._ctx(
|
||||
repo_dir="/tmp/repo",
|
||||
resolved={
|
||||
"origin": "git@github.com:alice/repo.git",
|
||||
"backup": "ssh://git@git.veen.world:2201/alice/repo.git",
|
||||
},
|
||||
)
|
||||
m_probe_detail.return_value = (True, "")
|
||||
|
||||
repos = [
|
||||
{
|
||||
"provider": "github.com",
|
||||
"account": "alice",
|
||||
"repository": "repo",
|
||||
"private": True,
|
||||
"description": "desc",
|
||||
}
|
||||
]
|
||||
setup_mirrors(
|
||||
selected_repos=repos,
|
||||
repositories_base_dir="/tmp",
|
||||
all_repos=repos,
|
||||
preview=True,
|
||||
local=False,
|
||||
remote=True,
|
||||
ensure_remote=True,
|
||||
)
|
||||
|
||||
# Provision both mirrors
|
||||
self.assertEqual(m_ensure_url.call_count, 2)
|
||||
m_ensure_url.assert_any_call(
|
||||
url="git@github.com:alice/repo.git",
|
||||
private_default=True,
|
||||
description="desc",
|
||||
preview=True,
|
||||
)
|
||||
m_ensure_url.assert_any_call(
|
||||
url="ssh://git@git.veen.world:2201/alice/repo.git",
|
||||
private_default=True,
|
||||
description="desc",
|
||||
preview=True,
|
||||
)
|
||||
|
||||
# Still probes both
|
||||
self.assertEqual(m_probe_detail.call_count, 2)
|
||||
|
||||
@patch("pkgmgr.actions.mirror.setup_cmd.build_context")
|
||||
@patch("pkgmgr.actions.mirror.setup_cmd.determine_primary_remote_url")
|
||||
@patch("pkgmgr.actions.mirror.setup_cmd.ensure_remote_repository_for_url")
|
||||
@patch("pkgmgr.actions.mirror.setup_cmd.probe_remote_reachable_detail")
|
||||
def test_setup_mirrors_remote_no_mirrors_ensure_remote_provisions_primary(
|
||||
self, m_probe_detail, m_ensure_url, m_primary, m_ctx
|
||||
) -> None:
|
||||
m_ctx.return_value = self._ctx(repo_dir="/tmp/repo", resolved={})
|
||||
m_primary.return_value = "git@github.com:alice/repo.git"
|
||||
m_probe_detail.return_value = (True, "")
|
||||
|
||||
repos = [
|
||||
{
|
||||
"provider": "github.com",
|
||||
"account": "alice",
|
||||
"repository": "repo",
|
||||
"private": False,
|
||||
"description": "desc",
|
||||
}
|
||||
]
|
||||
setup_mirrors(
|
||||
selected_repos=repos,
|
||||
repositories_base_dir="/tmp",
|
||||
all_repos=repos,
|
||||
preview=True,
|
||||
local=False,
|
||||
remote=True,
|
||||
ensure_remote=True,
|
||||
)
|
||||
|
||||
m_ensure_url.assert_called_once_with(
|
||||
url="git@github.com:alice/repo.git",
|
||||
private_default=False,
|
||||
description="desc",
|
||||
preview=True,
|
||||
)
|
||||
m_probe_detail.assert_called_once_with(
|
||||
"git@github.com:alice/repo.git", cwd="/tmp/repo"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from pkgmgr.core.git.errors import GitRunError
|
||||
|
||||
# IMPORTANT:
|
||||
# Import the MODULE, not the function exported by pkgmgr.core.git.queries.__init__.
|
||||
pr = importlib.import_module("pkgmgr.core.git.queries.probe_remote_reachable")
|
||||
|
||||
|
||||
def _git_error(
|
||||
*,
|
||||
returncode: int,
|
||||
stderr: str = "",
|
||||
stdout: str = "",
|
||||
message: str = "git failed",
|
||||
) -> GitRunError:
|
||||
"""
|
||||
Create a GitRunError that mimics what pkgmgr.core.git.run attaches.
|
||||
"""
|
||||
exc = GitRunError(message)
|
||||
exc.returncode = returncode
|
||||
exc.stderr = stderr
|
||||
exc.stdout = stdout
|
||||
return exc
|
||||
|
||||
|
||||
class TestProbeRemoteReachableHelpers(unittest.TestCase):
|
||||
def test_first_useful_line_prefers_keyword_lines(self) -> None:
|
||||
text = "\nerror:\n \nFATAL: Could not read from remote repository.\nmore\n"
|
||||
self.assertEqual(
|
||||
pr._first_useful_line(text),
|
||||
"FATAL: Could not read from remote repository.",
|
||||
)
|
||||
|
||||
def test_first_useful_line_skips_plain_error_if_possible(self) -> None:
|
||||
text = "error:\nsome other info\n"
|
||||
self.assertEqual(pr._first_useful_line(text), "some other info")
|
||||
|
||||
def test_first_useful_line_returns_empty_for_empty(self) -> None:
|
||||
self.assertEqual(pr._first_useful_line(" \n\n"), "")
|
||||
|
||||
def test_looks_like_real_transport_error_true(self) -> None:
|
||||
self.assertTrue(
|
||||
pr._looks_like_real_transport_error(
|
||||
"fatal: Could not read from remote repository."
|
||||
)
|
||||
)
|
||||
|
||||
def test_looks_like_real_transport_error_false(self) -> None:
|
||||
self.assertFalse(pr._looks_like_real_transport_error("some harmless output"))
|
||||
|
||||
|
||||
class TestProbeRemoteReachableDetail(unittest.TestCase):
|
||||
@patch.object(pr, "run", return_value="")
|
||||
def test_detail_success_returns_true_empty_reason(self, m_run) -> None:
|
||||
ok, reason = pr.probe_remote_reachable_detail(
|
||||
"git@github.com:alice/repo.git",
|
||||
cwd="/tmp",
|
||||
)
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual(reason, "")
|
||||
m_run.assert_called_once()
|
||||
|
||||
@patch.object(pr, "run")
|
||||
def test_detail_rc2_without_transport_indicators_treated_as_reachable(
|
||||
self, m_run
|
||||
) -> None:
|
||||
# rc=2 but no transport/auth indicators => treat as reachable (empty repo)
|
||||
m_run.side_effect = _git_error(
|
||||
returncode=2,
|
||||
stderr="",
|
||||
stdout="",
|
||||
message="Git command failed (exit 2)",
|
||||
)
|
||||
|
||||
ok, reason = pr.probe_remote_reachable_detail(
|
||||
"git@github.com:alice/empty.git",
|
||||
cwd="/tmp",
|
||||
)
|
||||
self.assertTrue(ok)
|
||||
self.assertIn("empty repository", reason.lower())
|
||||
|
||||
@patch.object(pr, "run")
|
||||
def test_detail_rc2_with_transport_indicators_is_not_reachable(self, m_run) -> None:
|
||||
# rc=2 but stderr indicates transport/auth problem => NOT reachable
|
||||
m_run.side_effect = _git_error(
|
||||
returncode=2,
|
||||
stderr="ERROR: Repository not found.",
|
||||
stdout="",
|
||||
message="Git command failed (exit 2)",
|
||||
)
|
||||
|
||||
ok, reason = pr.probe_remote_reachable_detail(
|
||||
"git@github.com:alice/missing.git",
|
||||
cwd="/tmp",
|
||||
)
|
||||
self.assertFalse(ok)
|
||||
self.assertIn("repository not found", reason.lower())
|
||||
|
||||
@patch.object(pr, "run")
|
||||
def test_detail_rc128_reports_reason(self, m_run) -> None:
|
||||
m_run.side_effect = _git_error(
|
||||
returncode=128,
|
||||
stderr="fatal: Could not read from remote repository.",
|
||||
stdout="",
|
||||
message="Git command failed (exit 128)",
|
||||
)
|
||||
|
||||
ok, reason = pr.probe_remote_reachable_detail(
|
||||
"ssh://git@host:2201/a/b.git",
|
||||
cwd="/tmp",
|
||||
)
|
||||
self.assertFalse(ok)
|
||||
self.assertIn("(exit 128)", reason.lower())
|
||||
self.assertIn("could not read from remote repository", reason.lower())
|
||||
|
||||
@patch.object(pr, "run")
|
||||
def test_detail_adds_hint_if_reason_is_generic(self, m_run) -> None:
|
||||
# Generic failure: rc=128 but no stderr/stdout => should append hint
|
||||
m_run.side_effect = _git_error(
|
||||
returncode=128,
|
||||
stderr="",
|
||||
stdout="",
|
||||
message="",
|
||||
)
|
||||
|
||||
url = "git@github.com:alice/repo.git"
|
||||
ok, reason = pr.probe_remote_reachable_detail(url, cwd="/tmp")
|
||||
|
||||
self.assertFalse(ok)
|
||||
self.assertIn("hint:", reason.lower())
|
||||
self.assertIn("git ls-remote --exit-code", reason.lower())
|
||||
|
||||
@patch.object(pr, "probe_remote_reachable_detail", return_value=(True, ""))
|
||||
def test_probe_remote_reachable_delegates_to_detail(self, m_detail) -> None:
|
||||
self.assertTrue(pr.probe_remote_reachable("x", cwd="/tmp"))
|
||||
m_detail.assert_called_once_with("x", cwd="/tmp")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user