diff --git a/mergify_cli/ci/scopes/base_detector.py b/mergify_cli/ci/scopes/base_detector.py index d9bb58a9..a48a8a9f 100644 --- a/mergify_cli/ci/scopes/base_detector.py +++ b/mergify_cli/ci/scopes/base_detector.py @@ -1,5 +1,6 @@ from __future__ import annotations +import dataclasses import json import os import pathlib @@ -64,7 +65,13 @@ def _detect_base_from_event(ev: dict[str, typing.Any]) -> str | None: return None -def detect() -> str: +@dataclasses.dataclass +class Base: + ref: str + is_merge_queue: bool + + +def detect() -> Base: event_path = os.environ.get("GITHUB_EVENT_PATH") event: dict[str, typing.Any] | None = None if event_path and pathlib.Path(event_path).is_file(): @@ -78,17 +85,17 @@ def detect() -> str: # 0) merge-queue PR override mq_sha = _detect_base_from_merge_queue_payload(event) if mq_sha: - return mq_sha + return Base(mq_sha, is_merge_queue=True) # 1) standard event payload event_sha = _detect_base_from_event(event) if event_sha: - return event_sha + return Base(event_sha, is_merge_queue=False) # 2) base ref (e.g., PR target branch) base_ref = os.environ.get("GITHUB_BASE_REF") if base_ref: - return base_ref + return Base(base_ref, is_merge_queue=False) msg = ( "Could not detect base SHA. Ensure checkout has sufficient history " diff --git a/mergify_cli/ci/scopes/cli.py b/mergify_cli/ci/scopes/cli.py index f05d58e2..59eacbbe 100644 --- a/mergify_cli/ci/scopes/cli.py +++ b/mergify_cli/ci/scopes/cli.py @@ -45,6 +45,7 @@ class ScopeConfig(pydantic.BaseModel): class Config(pydantic.BaseModel): scopes: dict[ScopeName, ScopeConfig] + merge_queue_scope: str | None = "merge-queue" @classmethod def from_dict(cls, data: dict[str, typing.Any] | typing.Any) -> Config: # noqa: ANN401 @@ -113,12 +114,18 @@ def detect(config_path: str) -> None: cfg = Config.from_yaml(config_path) base = base_detector.detect() try: - changed = changed_files.git_changed_files(base) + changed = changed_files.git_changed_files(base.ref) except changed_files.ChangedFilesError as e: raise ChangedFilesError(str(e)) scopes_hit, per_scope = match_scopes(cfg, changed) - click.echo(f"Base: {base}") + all_scopes = set(cfg.scopes.keys()) + if cfg.merge_queue_scope is not None: + all_scopes.add(cfg.merge_queue_scope) + if base.is_merge_queue: + scopes_hit.add(cfg.merge_queue_scope) + + click.echo(f"Base: {base.ref}") if scopes_hit: click.echo("Scopes touched:") for s in sorted(scopes_hit): @@ -129,4 +136,4 @@ def detect(config_path: str) -> None: else: click.echo("No scopes matched.") - maybe_write_github_outputs(cfg.scopes.keys(), scopes_hit) + maybe_write_github_outputs(all_scopes, scopes_hit) diff --git a/mergify_cli/tests/ci/scopes/test_base_detector.py b/mergify_cli/tests/ci/scopes/test_base_detector.py index 40ad7d9b..2d893b54 100644 --- a/mergify_cli/tests/ci/scopes/test_base_detector.py +++ b/mergify_cli/tests/ci/scopes/test_base_detector.py @@ -15,7 +15,7 @@ def test_detect_base_github_base_ref( result = base_detector.detect() - assert result == "main" + assert result == base_detector.Base("main", is_merge_queue=False) def test_detect_base_from_event_path( @@ -35,7 +35,7 @@ def test_detect_base_from_event_path( result = base_detector.detect() - assert result == "abc123" + assert result == base_detector.Base("abc123", is_merge_queue=False) def test_detect_base_merge_queue_override( @@ -56,7 +56,7 @@ def test_detect_base_merge_queue_override( result = base_detector.detect() - assert result == "xyz789" + assert result == base_detector.Base("xyz789", is_merge_queue=True) def test_detect_base_no_info(monkeypatch: pytest.MonkeyPatch) -> None: diff --git a/mergify_cli/tests/ci/scopes/test_cli.py b/mergify_cli/tests/ci/scopes/test_cli.py index 1180a832..21db571b 100644 --- a/mergify_cli/tests/ci/scopes/test_cli.py +++ b/mergify_cli/tests/ci/scopes/test_cli.py @@ -4,6 +4,7 @@ import pytest import yaml +from mergify_cli.ci.scopes import base_detector from mergify_cli.ci.scopes import cli @@ -23,6 +24,7 @@ def test_from_yaml_valid(tmp_path: pathlib.Path) -> None: config = cli.Config.from_yaml(str(config_file)) assert config.model_dump() == { + "merge_queue_scope": "merge-queue", "scopes": { "backend": {"include": ("api/**/*.py", "backend/**/*.py"), "exclude": ()}, "frontend": {"include": ("ui/**/*.js", "ui/**/*.tsx"), "exclude": ()}, @@ -223,7 +225,7 @@ def test_detect_with_matches( config_file.write_text(yaml.dump(config_data)) # Setup mocks - mock_detect_base.return_value = "main" + mock_detect_base.return_value = base_detector.Base("main", is_merge_queue=True) mock_git_changed.return_value = ["api/models.py", "other.txt"] # Capture output @@ -240,6 +242,7 @@ def test_detect_with_matches( assert "Base: main" in calls assert "Scopes touched:" in calls assert "- backend" in calls + assert "- merge-queue" in calls @mock.patch("mergify_cli.ci.scopes.cli.base_detector.detect") @@ -257,7 +260,7 @@ def test_detect_no_matches( config_file.write_text(yaml.dump(config_data)) # Setup mocks - mock_detect_base.return_value = "main" + mock_detect_base.return_value = base_detector.Base("main", is_merge_queue=False) mock_git_changed.return_value = ["other.txt"] # Capture output @@ -287,7 +290,7 @@ def test_detect_debug_output( config_file.write_text(yaml.dump(config_data)) # Setup mocks - mock_detect_base.return_value = "main" + mock_detect_base.return_value = base_detector.Base("main", is_merge_queue=False) mock_git_changed.return_value = ["api/models.py", "api/views.py"] # Capture output