diff --git a/src/sentry/scm/private/helpers.py b/src/sentry/scm/private/helpers.py index 6d7cf78bf97c..38a7f8ac89ce 100644 --- a/src/sentry/scm/private/helpers.py +++ b/src/sentry/scm/private/helpers.py @@ -99,20 +99,24 @@ def fetch_repository(organization_id: int, repository_id: RepositoryId) -> Repos if integration: installation_id = integration.external_id - return cast( - Repository, - { - "external_id": repo.external_id, - "id": repo.id, - "integration_id": repo.integration_id, - "is_active": repo.status == ObjectStatus.ACTIVE, - "name": repo.name, - "organization_id": repo.organization_id, - "provider_name": provider_name, - "web_base_url": web_base_url, - "installation_id": installation_id, - }, - ) + repository: dict[str, object] = { + "external_id": repo.external_id, + "id": repo.id, + "integration_id": repo.integration_id, + "is_active": repo.status == ObjectStatus.ACTIVE, + "name": repo.name, + "organization_id": repo.organization_id, + "provider_name": provider_name, + "web_base_url": web_base_url, + "installation_id": installation_id, + } + # A GitLab repository's name is its display name ("Group / Project"), which can't be used to + # address the project on its host, e.g. in a clone URL. The project's real path is only kept + # in its config. + if provider_name == "gitlab": + repository["path"] = repo.config.get("path") + + return cast(Repository, repository) def report_error_to_sentry(e: Exception) -> None: diff --git a/tests/sentry/scm/integration/test_helpers_integration.py b/tests/sentry/scm/integration/test_helpers_integration.py index c4c5c1e1068e..8de4c5c7299b 100644 --- a/tests/sentry/scm/integration/test_helpers_integration.py +++ b/tests/sentry/scm/integration/test_helpers_integration.py @@ -164,6 +164,54 @@ def test_fetch_cursor_origin_repo_populates_installation_id(self) -> None: assert result is not None assert result["installation_id"] == "inst_01example" + def test_fetch_gitlab_repo_populates_path_from_config(self) -> None: + repo = RepositoryModel.objects.create( + organization_id=self.organization.id, + name="GitLab.org / GitLab FOSS", + provider="integrations:gitlab", + external_id="gitlab.example.com:13083", + status=ObjectStatus.ACTIVE, + integration_id=1, + config={"path": "gitlab-org/gitlab-foss", "project_id": 13083}, + ) + + result = fetch_repository(self.organization.id, repo.id) + + assert result is not None + # Sentry's pinned sentry-scm may predate the `path` key, so read it untyped. + assert dict(result)["path"] == "gitlab-org/gitlab-foss" + + def test_fetch_gitlab_repo_without_config_path_has_none_path(self) -> None: + repo = RepositoryModel.objects.create( + organization_id=self.organization.id, + name="GitLab.org / GitLab FOSS", + provider="integrations:gitlab", + external_id="gitlab.example.com:13083", + status=ObjectStatus.ACTIVE, + integration_id=1, + ) + + result = fetch_repository(self.organization.id, repo.id) + + assert result is not None + assert dict(result)["path"] is None + + def test_fetch_non_gitlab_repo_has_no_path(self) -> None: + repo = RepositoryModel.objects.create( + organization_id=self.organization.id, + name="test-org/test-repo", + provider="integrations:github", + external_id="22222", + status=ObjectStatus.ACTIVE, + integration_id=1, + config={"path": "test-org/test-repo"}, + ) + + result = fetch_repository(self.organization.id, repo.id) + + assert result is not None + assert "path" not in result + class TestFetchServiceProvider(TestCase): def test_returns_provider_from_map_to_provider(self) -> None: