diff --git a/hermes_cli/source_releases.py b/hermes_cli/source_releases.py index ef11d7c536..14b9dcab55 100644 --- a/hermes_cli/source_releases.py +++ b/hermes_cli/source_releases.py @@ -64,7 +64,8 @@ def _resolve_channel(name: str, repository: str): ChannelReader owns HTTPS, authority and digests. The source adapter below admits its retirement constraints before any checkout operation. No legacy - GitHub fallback is allowed when a record is unavailable. + GitHub fallback is allowed when a record is unavailable; the one exception + is an unpublished ``main`` record, which resolves to the main branch. """ from hermes_cli.release_channels import ChannelReader @@ -73,11 +74,18 @@ def _resolve_channel(name: str, repository: str): def resolve_source_target(channel: str, git_cmd=None, cwd=None, *, repository=None) -> SourceTarget: """Resolve every subscription, including default labels, through R2.""" - from hermes_cli.release_channels import validate_name + from hermes_cli.release_channels import ChannelNotFound, validate_name validate_name(channel) repository = repository or source_repository(git_cmd, cwd) - resolved = _resolve_channel(channel, repository) + try: + resolved = _resolve_channel(channel, repository) + except ChannelNotFound: + if channel != "main": + raise + # main IS the source branch; its record can only add a retirement. + # Until one is published, a checkout keeps following the branch via git. + return SourceTarget(channel, channel, repository, branch="main") terminal = resolved.terminal if terminal["repository"].lower() != repository.lower(): raise ValueError("Channel repository does not match this source installation") diff --git a/tests/hermes_cli/test_source_channel_integration.py b/tests/hermes_cli/test_source_channel_integration.py index 071ea9c29d..5695dc06c0 100644 --- a/tests/hermes_cli/test_source_channel_integration.py +++ b/tests/hermes_cli/test_source_channel_integration.py @@ -153,6 +153,25 @@ def test_missing_channel_cannot_fall_back_to_main(source, monkeypatch, channel): assert git(source.root, "rev-parse", "HEAD") == before +def test_unpublished_main_record_keeps_following_the_git_branch(source, monkeypatch): + """main IS the source branch: until R2 publishes its record, a checkout + still updates via git instead of failing on a missing channel object.""" + from hermes_cli import source_check + from hermes_cli.release_channels import ChannelNotFound + + set_install_channel("main", source.root) + def unpublished(name, repository): + raise ChannelNotFound(f"Channel object not found: releases/channels/{name}.json") + monkeypatch.setattr(source_releases, "_resolve_channel", unpublished) + target = source_releases.resolve_source_target("main", ["git"], source.root) + assert target.branch == "main" and target.commit is None + status = source_check.check_for_updates(install_root=source.root, home=source.home, force=True) + assert "error" not in status, status + assert status["targetSha"] == source.commits[2] + with pytest.raises(ChannelNotFound): + source_releases.resolve_source_target("stable", ["git"], source.root) + + def test_passive_check_reports_retirement_without_adopting_it(source, monkeypatch): from hermes_cli import source_check, banner