From 4589f994dba37da5d8bada310ac124e1cf5cfcb4 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Wed, 21 Oct 2026 15:36:21 +0200 Subject: [PATCH] fix: handle commits unrelated to default branch --- .../collector/reference_integration.py | 28 ++++++-- tests/test_repo_overview.py | 70 +++++++++++++++++++ 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/src/generate_repo_overview/collector/reference_integration.py b/src/generate_repo_overview/collector/reference_integration.py index 9a3e45b..c6d28d2 100644 --- a/src/generate_repo_overview/collector/reference_integration.py +++ b/src/generate_repo_overview/collector/reference_integration.py @@ -9,6 +9,7 @@ from repo_cache import default_cache_directory +from generate_repo_overview.console import print_status from generate_repo_overview.org_config import is_safe_repository_path from .git_checkout import sync_repository_checkout @@ -247,16 +248,27 @@ def compare_git_refs( right_ref: str, ) -> GitRefComparison | None: """Compare refs and count commits unique to each side, including divergence.""" + + # PyGithub runs in lazy mode, so the HTTP request fires on attribute access + # rather than in ``compare()`` itself; those reads therefore live inside the + # same guard as the request so no lazy failure can escape the worker pool. + # A GitHub ``No common ancestor`` 404 is logged as a warning (it is an + # expected divergent-lineage case) and treated as uncomparable. compare = getattr(repository, "compare", None) if not callable(compare): return None try: result = compare(left_ref, right_ref) - except Exception: + right_ahead_by = getattr(result, "ahead_by", None) + left_ahead_by = getattr(result, "behind_by", None) + except Exception as exc: + if "No common ancestor" in str(exc): + print_status( + f"warning: {_repository_label(repository)}: refs {left_ref!r} " + f"and {right_ref!r} share no common ancestor; " + "reference-integration metrics are unavailable.", + ) return None - - right_ahead_by = getattr(result, "ahead_by", None) - left_ahead_by = getattr(result, "behind_by", None) if not isinstance(right_ahead_by, int) or not isinstance(left_ahead_by, int): return None return GitRefComparison( @@ -265,6 +277,14 @@ def compare_git_refs( ) +def _repository_label(repository: object) -> str: + for attribute in ("full_name", "name"): + value = getattr(repository, attribute, None) + if isinstance(value, str) and value: + return value + return "" + + def read_included_module_files(checkout_path: Path) -> dict[Path, str]: pending = [ROOT_MODULE_PATH] seen: set[Path] = set() diff --git a/tests/test_repo_overview.py b/tests/test_repo_overview.py index d9475f5..60664a0 100644 --- a/tests/test_repo_overview.py +++ b/tests/test_repo_overview.py @@ -1237,6 +1237,76 @@ def compare(self, base: str, head: str) -> SimpleNamespace: assert details["default_branch_ahead_of_latest_release_by"] == 7 +def test_compare_git_refs_happy_path_counts_divergence() -> None: + class Repository: + def compare(self, left: str, right: str) -> SimpleNamespace: + return SimpleNamespace(ahead_by=3, behind_by=1) + + comparison = reference_integration.compare_git_refs( + Repository(), + left_ref="main", + right_ref="v1.0.0", + ) + + assert comparison == reference_integration.GitRefComparison( + right_ahead_by=3, + left_ahead_by=1, + ) + + +def test_compare_git_refs_returns_none_when_compare_itself_raises() -> None: + class Repository: + def compare(self, left: str, right: str) -> object: + raise RuntimeError("compare exploded") + + assert ( + reference_integration.compare_git_refs( + Repository(), + left_ref="main", + right_ref="v1.0.0", + ) + is None + ) + + +def test_compare_git_refs_logs_warning_and_returns_none_for_no_common_ancestor( + capsys: pytest.CaptureFixture[str], +) -> None: + """A lazy 404 ``No common ancestor`` must not escape and must be surfaced.""" + from github import GithubException + + class LazyNoCommonAncestor: + @property + def ahead_by(self) -> int: + # Mirrors PyGithub lazy mode: the HTTP error fires on attribute + # access, after ``compare()`` has already returned. + raise GithubException( + 404, + data={}, + message=( + "No common ancestor between archival-branch and " + "6ea826c43f965f9ebe23cdc813566cf8141ebaf1." + ), + ) + + class Repository: + full_name = "example-org/ancient-repo" + + def compare(self, left: str, right: str) -> LazyNoCommonAncestor: + return LazyNoCommonAncestor() + + result = reference_integration.compare_git_refs( + Repository(), + left_ref="archival-branch", + right_ref="6ea826c43f965f9ebe23cdc813566cf8141ebaf1", + ) + + assert result is None + captured = capsys.readouterr() + assert "ancient-repo" in captured.err + assert "archival-branch" in captured.err + + def test_version_pin_uses_resolved_sha_for_commit_link() -> None: snapshot = RepoSnapshot( schema_version=SNAPSHOT_SCHEMA_VERSION,