From b3130412e05509e27432329f4e7d88edefc495c3 Mon Sep 17 00:00:00 2001 From: Giovanni M Guidini Date: Mon, 2 Nov 2026 15:37:36 +0100 Subject: [PATCH] fix(repository): Bound repository sync lookups Pass known external IDs through repository creation, removal, disable, and restore paths so RPC workers do not serialize every repository in an organization. Fixes SCM-327 --- .../github/tasks/sync_repos_on_install_change.py | 1 + .../source_code_management/sync_repos.py | 2 ++ .../plugins/providers/integration_repository.py | 9 ++++++++- .../sentry/plugins/test_integration_repository.py | 15 +++++++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/sentry/integrations/github/tasks/sync_repos_on_install_change.py b/src/sentry/integrations/github/tasks/sync_repos_on_install_change.py index 4c6140376d06..5d7c84c070b8 100644 --- a/src/sentry/integrations/github/tasks/sync_repos_on_install_change.py +++ b/src/sentry/integrations/github/tasks/sync_repos_on_install_change.py @@ -193,6 +193,7 @@ def _sync_repos_for_org( organization_id=rpc_org.id, integration_id=integration.id, providers=[provider], + external_ids=safe_to_disable, ) repo_by_eid = { r.external_id: r diff --git a/src/sentry/integrations/source_code_management/sync_repos.py b/src/sentry/integrations/source_code_management/sync_repos.py index 2c726ece2614..6edbc18ca30b 100644 --- a/src/sentry/integrations/source_code_management/sync_repos.py +++ b/src/sentry/integrations/source_code_management/sync_repos.py @@ -481,6 +481,7 @@ def disable_repos_batch( organization_id=rpc_org.id, integration_id=integration.id, providers=[provider], + external_ids=external_ids, ) repo_by_external_id = {r.external_id: r for r in all_repos} @@ -517,6 +518,7 @@ def restore_repos_batch( organization_id=rpc_org.id, integration_id=integration.id, providers=[provider], + external_ids=external_ids, ) integration_repo_provider = get_integration_repository_provider(integration) restore_set = set(external_ids) diff --git a/src/sentry/plugins/providers/integration_repository.py b/src/sentry/plugins/providers/integration_repository.py index 972dea83eaeb..4f95a5e4c0b5 100644 --- a/src/sentry/plugins/providers/integration_repository.py +++ b/src/sentry/plugins/providers/integration_repository.py @@ -182,6 +182,7 @@ def _adoptable_repositories(self, organization_id: int, **filters: Any) -> list[ organization_id=organization_id, providers=[self.id], external_id=filters.get("external_id"), + external_ids=filters.get("external_ids"), ) } return [ @@ -378,11 +379,17 @@ def _find_repositories( Includes unlinked rows this provider may adopt: plugin-era ones and ones with no provider (see ``_adoptable_repositories``). """ + external_id_list = sorted(external_ids) provider_repos = repository_service.get_repositories( organization_id=organization.id, providers=[self.id], + external_ids=external_id_list, + ) + legacy_repos = self._adoptable_repositories( + organization.id, + external_ids=external_id_list, + has_integration=False, ) - legacy_repos = self._adoptable_repositories(organization.id, has_integration=False) found: dict[str, RpcRepository] = {} for repo in provider_repos + legacy_repos: diff --git a/tests/sentry/plugins/test_integration_repository.py b/tests/sentry/plugins/test_integration_repository.py index 05676b7b51b9..fa2a60439740 100644 --- a/tests/sentry/plugins/test_integration_repository.py +++ b/tests/sentry/plugins/test_integration_repository.py @@ -7,6 +7,7 @@ from sentry.constants import ObjectStatus from sentry.integrations.github.repository import GitHubRepositoryProvider +from sentry.integrations.services.repository import repository_service from sentry.models.repository import REPOSITORY_NAME_LENGTH, REPOSITORY_URL_LENGTH, Repository from sentry.plugins.providers.integration_repository import RepoExistsError from sentry.shared_integrations.exceptions import IntegrationError @@ -325,6 +326,20 @@ def test_create_repositories__adopts_own_and_provider_less_rows( assert repo.provider == "integrations:github" assert repo.integration_id == self.integration.id + def test_create_repositories__scopes_lookups_to_external_ids(self, get_jwt: MagicMock) -> None: + other_config = {**self.config, "external_id": "999", "identifier": "getsentry/other"} + + with patch.object( + repository_service, + "get_repositories", + wraps=repository_service.get_repositories, + ) as get_repositories: + self.provider.create_repositories([self.config, other_config], self.organization) + + assert get_repositories.call_count == 4 + for call in get_repositories.call_args_list: + assert set(call.kwargs["external_ids"]) == {"654321", "999"} + def test_create_repository__does_not_adopt_over_an_existing_row( self, get_jwt: MagicMock ) -> None: