From 65484a6296b41c8da1adf491745bcbf6fd58a7eb Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 20 Oct 2026 21:01:21 +0200 Subject: [PATCH 1/3] fix(scm): Fall back to the repository's web_base_url in providers Sentry's provider factory builds GitHubProvider for GitHub Enterprise repositories without a web_base_url, so their file, commit and pull request links pointed at github.com. The repository already carries the instance's URL, so the provider now falls back to it before github.com. GitLabProvider built its host from the external ID, which holds only the hostname, so links for an instance served under a path prefix or on another scheme were wrong. It now prefers the repository's web_base_url, which getsentry/sentry#128164 fills with the instance's URL, and keeps the external ID's host as the fallback. Co-Authored-By: Claude Opus 5.5 --- src/scm/providers/github/provider.py | 6 ++++-- src/scm/providers/gitlab/provider.py | 4 +++- tests/unit/provider/test_github.py | 12 ++++++++++++ tests/unit/provider/test_gitlab.py | 7 +++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/scm/providers/github/provider.py b/src/scm/providers/github/provider.py index 64b7952..ae7a9e0 100644 --- a/src/scm/providers/github/provider.py +++ b/src/scm/providers/github/provider.py @@ -437,12 +437,14 @@ def __init__( client: ApiClient, organization_id: int, repository: Repository, - web_base_url: str = GITHUB_WEB_BASE_URL, + web_base_url: str | None = None, ) -> None: self.client = client self.organization_id = organization_id self.repository = repository - self._web_base_url = web_base_url + # A GitHub Enterprise repository carries its instance's URL. Falling back to it keeps a caller that builds + # the provider without one from pointing web links at github.com. + self._web_base_url = web_base_url or repository["web_base_url"] or GITHUB_WEB_BASE_URL def request( self, diff --git a/src/scm/providers/gitlab/provider.py b/src/scm/providers/gitlab/provider.py index 7f7e62c..3f0ae52 100644 --- a/src/scm/providers/gitlab/provider.py +++ b/src/scm/providers/gitlab/provider.py @@ -240,7 +240,9 @@ def __init__(self, client: ApiClient, organization_id: int, repository: Reposito raise MalformedExternalId() netloc, self.project_id = repository["external_id"].rsplit(":", maxsplit=1) - self.web_base_url = f"https://{netloc}" + # Sentry sends the instance's URL with the scheme and any path prefix it is served under. The external ID + # holds only the host, which is enough for an instance served at the root over https. + self.web_base_url = repository["web_base_url"] or f"https://{netloc}" @property def web_repository_path(self) -> str: diff --git a/tests/unit/provider/test_github.py b/tests/unit/provider/test_github.py index 299797d..bb30961 100644 --- a/tests/unit/provider/test_github.py +++ b/tests/unit/provider/test_github.py @@ -2081,6 +2081,18 @@ def test_get_pull_request_url_builds_pr_url() -> None: assert provider.get_pull_request_url("42") == "https://github.com/test-org/test-repo/pull/42" +def test_ghe_web_base_url_defaults_to_the_repositorys() -> None: + # Sentry's provider factory builds GitHub Enterprise providers without a web_base_url. + repository = { + **make_repository(), + "provider_name": "github_enterprise", + "web_base_url": "https://github.example.com", + } + provider = GitHubProvider(MagicMock(spec=ApiClient), organization_id=1, repository=repository) # type: ignore[arg-type] + + assert provider.get_pull_request_url("42") == "https://github.example.com/test-org/test-repo/pull/42" + + def test_ghe_web_base_url_used_in_url_methods() -> None: provider = GitHubProvider( MagicMock(spec=ApiClient), diff --git a/tests/unit/provider/test_gitlab.py b/tests/unit/provider/test_gitlab.py index 35c560c..f7a3842 100644 --- a/tests/unit/provider/test_gitlab.py +++ b/tests/unit/provider/test_gitlab.py @@ -13969,6 +13969,13 @@ def test_web_repository_path_used_in_commit_and_mr_urls(client: ApiClient) -> No ) +def test_web_links_keep_the_instances_path_prefix(client: ApiClient) -> None: + repository = {**_make_gitlab_provider(client).repository, "web_base_url": "https://example.com/gitlab"} + provider = GitLabProvider(client=client, organization_id=1, repository=repository) # type: ignore[arg-type] + + assert provider.get_commit_url("abc123") == "https://example.com/gitlab/test-repo/-/commit/abc123" + + def test_get_file_url_builds_blob_url(provider: GitLabProvider): assert provider.get_file_url("src/main.py", "abc123") == ("https://gitlab.com/test-repo/-/blob/abc123/src/main.py") assert provider.get_file_url("src/main.py", "abc123", start_line=10) == ( From ce28433d0fafb4f6b838a9812f4079ccdb6c38d8 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 20 Oct 2026 21:01:30 +0200 Subject: [PATCH 2/3] feat(scm): Ask providers for a repository's git remote Sentry's git endpoint relays git smart HTTP to a repository's provider with a token, so it needs the repository's clone URL and the basic-auth username its provider pairs with a token. The protocol is the same for every provider; these two values are what differ, and the providers already build every other URL for a repository. GetGitRemoteProtocol puts them next to the web-link protocols: - GitHub and GitHub Enterprise: //.git with x-access-token. - GitLab: /.git with oauth2. - Cursor Origin: https://origin.cursor.com//.git with x-access-token, per Origin's docs (cursor.com/docs/api/origin), whose git endpoint refuses a bearer header. Refs CW-2221 Co-Authored-By: Claude Opus 5.5 --- bin/github-client | 8 +++++ bin/gitlab-client | 8 +++++ src/scm/actions.py | 8 +++++ src/scm/providers/cursor_origin/provider.py | 7 ++++ src/scm/providers/github/provider.py | 6 +++- src/scm/providers/gitlab/provider.py | 4 +++ src/scm/test_fixtures.py | 4 +++ src/scm/types.py | 15 +++++++++ tests/unit/provider/test_cursor_origin.py | 7 ++++ tests/unit/provider/test_github.py | 26 ++++++++++++++- tests/unit/provider/test_gitlab.py | 36 +++++++++++++++++++++ tests/unit/test_actions.py | 7 ++++ tests/unit/test_rpc_integration.py | 5 +++ 13 files changed, 139 insertions(+), 2 deletions(-) diff --git a/bin/github-client b/bin/github-client index 8b96aa8..38ce0da 100755 --- a/bin/github-client +++ b/bin/github-client @@ -21,6 +21,7 @@ Commands: get-pull-request-review get-review-comments [--cursor ] [--per-page ] get-pull-request-url + get-git-remote mark-pull-request-ready-for-review mark-pull-request-as-draft get-issue @@ -129,6 +130,7 @@ from scm.types import ( GetFileContentProtocol, GetFileUrlProtocol, GetGitRefProtocol, + GetGitRemoteProtocol, GetIssueCommentsProtocol, GetIssueProtocol, GetPullRequestCommentsProtocol, @@ -283,6 +285,8 @@ def main() -> None: p = sub.add_parser("get-pull-request-url") p.add_argument("id") + sub.add_parser("get-git-remote") + p = sub.add_parser("mark-pull-request-ready-for-review") p.add_argument("id") @@ -567,6 +571,10 @@ def main() -> None: assert isinstance(scm, GetPullRequestUrlProtocol) dump(scm.get_pull_request_url(args.id)) + elif args.command == "get-git-remote": + assert isinstance(scm, GetGitRemoteProtocol) + dump(scm.get_git_remote()) + elif args.command == "mark-pull-request-ready-for-review": assert isinstance(scm, MarkPullRequestDraftStateProtocol) scm.mark_pull_request_ready_for_review(args.id) diff --git a/bin/gitlab-client b/bin/gitlab-client index d9fcb90..4a5a52f 100755 --- a/bin/gitlab-client +++ b/bin/gitlab-client @@ -23,6 +23,7 @@ Commands: get-pull-request-diff get-pull-request-review-threads [--include-reactions] get-pull-request-url + get-git-remote get-issue create-issue <body> [--assignee <user_id> ...] [--label <name> ...] update-issue <issue_id> [--state open|closed] [--assignee <user_id> ...] [--label <name> ...] @@ -117,6 +118,7 @@ from scm.types import ( GetDirectoryContentsProtocol, GetFileContentProtocol, GetFileUrlProtocol, + GetGitRemoteProtocol, GetIssueCommentsProtocol, GetIssueProtocol, GetPullRequestCommentsProtocol, @@ -248,6 +250,8 @@ def main(): p = sub.add_parser("get-pull-request-url") p.add_argument("id") + sub.add_parser("get-git-remote") + p = sub.add_parser("get-issue") p.add_argument("id") @@ -453,6 +457,10 @@ def main(): assert isinstance(scm, GetPullRequestUrlProtocol) dump(scm.get_pull_request_url(args.id)) + elif args.command == "get-git-remote": + assert isinstance(scm, GetGitRemoteProtocol) + dump(scm.get_git_remote()) + elif args.command == "get-issue": assert isinstance(scm, GetIssueProtocol) dump(scm.get_issue(args.id)) diff --git a/src/scm/actions.py b/src/scm/actions.py index dfa2071..1ed9706 100644 --- a/src/scm/actions.py +++ b/src/scm/actions.py @@ -78,6 +78,7 @@ GetFullTreeProtocol, GetGitCommitProtocol, GetGitRefProtocol, + GetGitRemoteProtocol, GetIssueCommentReactionsProtocol, GetIssueCommentsProtocol, GetIssueProtocol, @@ -106,6 +107,7 @@ GitBlob, GitCommitObject, GitRef, + GitRemote, GitRepository, GitTree, InputTreeEntry, @@ -528,6 +530,11 @@ def get_pull_request_url(scm: GetPullRequestUrlProtocol, pull_request_id: str) - return scm.get_pull_request_url(pull_request_id) +def get_git_remote(scm: GetGitRemoteProtocol) -> GitRemote: + """Return the repository's git smart-HTTP endpoint and the basic-auth username its provider pairs with a token.""" + return scm.get_git_remote() + + def create_git_blob(scm: CreateGitBlobProtocol, content: str, encoding: str) -> ActionResult[GitBlob]: """Create a git blob object.""" return scm.create_git_blob(content, encoding) @@ -1202,6 +1209,7 @@ def download_archive( "get_file_url", "get_git_commit", "get_git_ref", + "get_git_remote", "get_issue_comment_reactions", "get_issue_comments", "get_issue_reactions", diff --git a/src/scm/providers/cursor_origin/provider.py b/src/scm/providers/cursor_origin/provider.py index 6606f2c..f0299dc 100644 --- a/src/scm/providers/cursor_origin/provider.py +++ b/src/scm/providers/cursor_origin/provider.py @@ -48,6 +48,7 @@ GitCommitObject, GitCommitTree, GitRef, + GitRemote, GitRepository, GitTree, MoveCommitAction, @@ -79,6 +80,8 @@ PULL_REQUEST_TEMPLATE_FILENAME = "pull_request_template.md" PULL_REQUEST_TEMPLATE_DIRNAME = "pull_request_template" CURSOR_ORIGIN_WEB_BASE_URL = "https://cursor.com/codebase" +# Git over HTTPS is served from its own host, not the web UI's. +CURSOR_ORIGIN_GIT_BASE_URL = "https://origin.cursor.com" # Origin has no no-reply address for an app. CURSOR_ORIGIN_APP_COMMIT_EMAIL = "noreply@sentry.io" PAGE_TOKEN_PARAM = "pageToken" @@ -354,6 +357,10 @@ def get_commits_url( def get_pull_request_url(self, pull_request_id: str) -> str: return f"{self._web_base_url}/{self.repository_path}/pull/{pull_request_id}" + def get_git_remote(self) -> GitRemote: + # Origin's git endpoint takes an installation token as the basic-auth password and refuses a bearer header. + return GitRemote(url=f"{CURSOR_ORIGIN_GIT_BASE_URL}/{self.repository_path}.git", username="x-access-token") + def get_file_content( self, path: str, diff --git a/src/scm/providers/github/provider.py b/src/scm/providers/github/provider.py index ae7a9e0..b133dec 100644 --- a/src/scm/providers/github/provider.py +++ b/src/scm/providers/github/provider.py @@ -54,6 +54,7 @@ GitCommitObject, GitCommitTree, GitRef, + GitRemote, GitRepository, GitTree, InputTreeEntry, @@ -443,7 +444,7 @@ def __init__( self.organization_id = organization_id self.repository = repository # A GitHub Enterprise repository carries its instance's URL. Falling back to it keeps a caller that builds - # the provider without one from pointing web links at github.com. + # the provider without one from pointing web links, and the git remote a token is sent to, at github.com. self._web_base_url = web_base_url or repository["web_base_url"] or GITHUB_WEB_BASE_URL def request( @@ -1033,6 +1034,9 @@ def get_commits_url( def get_pull_request_url(self, pull_request_id: str) -> str: return f"{self._web_base_url}/{self.repository['name']}/pull/{pull_request_id}" + def get_git_remote(self) -> GitRemote: + return GitRemote(url=f"{self._web_base_url}/{self.repository['name']}.git", username="x-access-token") + def create_git_blob(self, content: str, encoding: str) -> ActionResult[GitBlob]: response = self.post( f"/repos/{self.repository['name']}/git/blobs", diff --git a/src/scm/providers/gitlab/provider.py b/src/scm/providers/gitlab/provider.py index 3f0ae52..91bdf81 100644 --- a/src/scm/providers/gitlab/provider.py +++ b/src/scm/providers/gitlab/provider.py @@ -50,6 +50,7 @@ GitCommitObject, GitCommitTree, GitRef, + GitRemote, GitRepository, GitTree, Issue, @@ -746,6 +747,9 @@ def get_commits_url( def get_pull_request_url(self, pull_request_id: str) -> str: return f"{self.web_base_url}/{self.web_repository_path}/-/merge_requests/{pull_request_id}" + def get_git_remote(self) -> GitRemote: + return GitRemote(url=f"{self.web_base_url}/{self.web_repository_path}.git", username="oauth2") + def get_tree( self, tree_sha: SHA, diff --git a/src/scm/test_fixtures.py b/src/scm/test_fixtures.py index ad6c0bc..d0c92b5 100644 --- a/src/scm/test_fixtures.py +++ b/src/scm/test_fixtures.py @@ -28,6 +28,7 @@ GitCommitObject, GitCommitTree, GitRef, + GitRemote, GitTree, InputTreeEntry, Issue, @@ -1190,6 +1191,9 @@ def get_commits_url( def get_pull_request_url(self, pull_request_id: str) -> str: return f"https://github.com/test-org/test-repo/pull/{pull_request_id}" + def get_git_remote(self) -> GitRemote: + return GitRemote(url="https://github.com/test-org/test-repo.git", username="x-access-token") + # Git blob operations def create_git_blob(self, content: str, encoding: str) -> ActionResult[GitBlob]: diff --git a/src/scm/types.py b/src/scm/types.py index 5a17f0b..35c84f9 100644 --- a/src/scm/types.py +++ b/src/scm/types.py @@ -65,6 +65,15 @@ class ArchiveLink(TypedDict): headers: dict[str, str] +class GitRemote(TypedDict): + """Where a repository's git smart-HTTP endpoint lives and how a token authenticates against it.""" + + url: str + """The repository's clone URL, e.g. ``https://github.com/getsentry/sentry.git``.""" + username: str + """The basic-auth username the provider pairs with a token, e.g. ``x-access-token`` on GitHub.""" + + type BuildStatus = Literal["pending", "running", "completed"] """The lifecycle stage of a CI build. @@ -1100,6 +1109,11 @@ class GetPullRequestUrlProtocol(Protocol): def get_pull_request_url(self, pull_request_id: str) -> str: ... +@runtime_checkable +class GetGitRemoteProtocol(Protocol): + def get_git_remote(self) -> GitRemote: ... + + # Commit Protocols @@ -1675,6 +1689,7 @@ def update_and_collapse_pull_request_comment( GetFileUrlProtocol, GetGitCommitProtocol, GetGitRefProtocol, + GetGitRemoteProtocol, GetIssueCommentReactionsProtocol, GetIssueCommentsProtocol, GetIssueProtocol, diff --git a/tests/unit/provider/test_cursor_origin.py b/tests/unit/provider/test_cursor_origin.py index a317a74..d4be281 100644 --- a/tests/unit/provider/test_cursor_origin.py +++ b/tests/unit/provider/test_cursor_origin.py @@ -34,6 +34,7 @@ ChmodCommitAction, CommitAuthorParam, DeleteCommitAction, + GitRemote, MoveCommitAction, Repository, WriteCommitAction, @@ -1083,6 +1084,12 @@ def test_a_pull_request_url(self, provider: CursorOriginProvider) -> None: def test_a_commit_url(self, provider: CursorOriginProvider) -> None: assert provider.get_commit_url("abc123") == f"https://cursor.com/codebase/{REPO}/commit/abc123" + def test_a_git_remote(self, provider: CursorOriginProvider) -> None: + # Git is served from origin.cursor.com rather than the web UI's cursor.com/codebase. + assert provider.get_git_remote() == GitRemote( + url=f"https://origin.cursor.com/{REPO}.git", username="x-access-token" + ) + def _commit_raw(sha: str, date: str = "2026-08-01T09:30:00Z") -> dict[str, Any]: identity = {"name": "Jane Doe", "email": "jane@example.com", "date": date} diff --git a/tests/unit/provider/test_github.py b/tests/unit/provider/test_github.py index bb30961..1fb61c7 100644 --- a/tests/unit/provider/test_github.py +++ b/tests/unit/provider/test_github.py @@ -74,6 +74,7 @@ CredentialsSet, DeleteCommitAction, DiffLine, + GitRemote, MoveCommitAction, Repository, WriteCommitAction, @@ -2081,8 +2082,29 @@ def test_get_pull_request_url_builds_pr_url() -> None: assert provider.get_pull_request_url("42") == "https://github.com/test-org/test-repo/pull/42" +def test_get_git_remote_builds_clone_url() -> None: + provider = GitHubProvider(MagicMock(spec=ApiClient), organization_id=1, repository=make_repository()) + + assert provider.get_git_remote() == GitRemote( + url="https://github.com/test-org/test-repo.git", username="x-access-token" + ) + + +def test_ghe_web_base_url_used_in_git_remote() -> None: + provider = GitHubProvider( + MagicMock(spec=ApiClient), + organization_id=1, + repository=make_repository(), + web_base_url="https://github.example.com", + ) + + assert provider.get_git_remote() == GitRemote( + url="https://github.example.com/test-org/test-repo.git", username="x-access-token" + ) + + def test_ghe_web_base_url_defaults_to_the_repositorys() -> None: - # Sentry's provider factory builds GitHub Enterprise providers without a web_base_url. + # Built without a web_base_url, a GitHub Enterprise provider would otherwise send its token to github.com. repository = { **make_repository(), "provider_name": "github_enterprise", @@ -2090,6 +2112,7 @@ def test_ghe_web_base_url_defaults_to_the_repositorys() -> None: } provider = GitHubProvider(MagicMock(spec=ApiClient), organization_id=1, repository=repository) # type: ignore[arg-type] + assert provider.get_git_remote()["url"] == "https://github.example.com/test-org/test-repo.git" assert provider.get_pull_request_url("42") == "https://github.example.com/test-org/test-repo/pull/42" @@ -3670,6 +3693,7 @@ def test_public_methods_are_accounted_for() -> None: "get_commit_url", "get_commits_url", "get_pull_request_url", + "get_git_remote", "create_commit", "update_issue", "get_thread_id_from_review_comment_unique_id", diff --git a/tests/unit/provider/test_gitlab.py b/tests/unit/provider/test_gitlab.py index f7a3842..a9a6bb9 100644 --- a/tests/unit/provider/test_gitlab.py +++ b/tests/unit/provider/test_gitlab.py @@ -37,6 +37,7 @@ ChmodCommitAction, DeleteCommitAction, DiffLine, + GitRemote, MoveCommitAction, Repository, WriteCommitAction, @@ -13976,6 +13977,41 @@ def test_web_links_keep_the_instances_path_prefix(client: ApiClient) -> None: assert provider.get_commit_url("abc123") == "https://example.com/gitlab/test-repo/-/commit/abc123" +def test_get_git_remote_is_the_repository_path_on_its_instance(client: ApiClient) -> None: + provider = _make_gitlab_provider(client, name="GitLab.org / GitLab FOSS", path="gitlab-org/gitlab-foss") + + assert provider.get_git_remote() == GitRemote( + url="https://gitlab.com/gitlab-org/gitlab-foss.git", username="oauth2" + ) + + +def test_get_git_remote_on_a_self_managed_instance(client: ApiClient) -> None: + provider = GitLabProvider( + client=client, + organization_id=1, + repository={**_make_gitlab_provider(client).repository, "external_id": "gitlab.example.com:8443:7"}, + ) + + assert provider.get_git_remote() == GitRemote( + url="https://gitlab.example.com:8443/test-repo.git", username="oauth2" + ) + + +def test_get_git_remote_keeps_the_instances_path_prefix(client: ApiClient) -> None: + repository = {**_make_gitlab_provider(client).repository, "web_base_url": "https://example.com/gitlab"} + provider = GitLabProvider(client=client, organization_id=1, repository=repository) # type: ignore[arg-type] + + assert provider.get_git_remote()["url"] == "https://example.com/gitlab/test-repo.git" + + +@pytest.mark.parametrize("path", [None, ""]) +def test_get_git_remote_needs_the_repository_path(client: ApiClient, path: str | None) -> None: + provider = _make_gitlab_provider(client, name="GitLab.org / GitLab FOSS", path=path) + + with pytest.raises(MissingRepositoryPath): + provider.get_git_remote() + + def test_get_file_url_builds_blob_url(provider: GitLabProvider): assert provider.get_file_url("src/main.py", "abc123") == ("https://gitlab.com/test-repo/-/blob/abc123/src/main.py") assert provider.get_file_url("src/main.py", "abc123", start_line=10) == ( diff --git a/tests/unit/test_actions.py b/tests/unit/test_actions.py index 19706e5..e368728 100644 --- a/tests/unit/test_actions.py +++ b/tests/unit/test_actions.py @@ -53,6 +53,7 @@ get_full_tree, get_git_commit, get_git_ref, + get_git_remote, get_issue, get_issue_comment_reactions, get_issue_comments, @@ -194,6 +195,7 @@ def fetch_repository(oid, rid) -> Repository: (get_commit_url, {"commit_sha": "abc123"}), (get_commits_url, {"commit_sha": "abc123"}), (get_pull_request_url, {"pull_request_id": "1"}), + (get_git_remote, {}), # Git blob operations (create_git_blob, {"content": "hello", "encoding": "utf-8"}), # File content operations @@ -475,6 +477,10 @@ def _check_get_pull_request_url(result: Any) -> None: assert result == "https://github.com/test-org/test-repo/pull/1" +def _check_get_git_remote(result: Any) -> None: + assert result == {"url": "https://github.com/test-org/test-repo.git", "username": "x-access-token"} + + def _check_create_git_blob(result: Any) -> None: assert result["data"]["sha"] == "blob123abc" assert result["type"] == "github" @@ -831,6 +837,7 @@ def _check_download_archive(result: Any) -> None: (get_commit_url, {"commit_sha": "abc123"}, _check_get_commit_url), (get_commits_url, {"commit_sha": "abc123"}, _check_get_commits_url), (get_pull_request_url, {"pull_request_id": "1"}, _check_get_pull_request_url), + (get_git_remote, {}, _check_get_git_remote), ( create_git_blob, {"content": "hello", "encoding": "utf-8"}, diff --git a/tests/unit/test_rpc_integration.py b/tests/unit/test_rpc_integration.py index ec26670..f2caa52 100644 --- a/tests/unit/test_rpc_integration.py +++ b/tests/unit/test_rpc_integration.py @@ -965,6 +965,11 @@ def make_client_scm(organization_id, repository_id, server): lambda scm: actions.get_pull_request_url(scm, "42"), "https://github.com/org/repo/pull/42", ), + ( + "get_git_remote", + lambda scm: actions.get_git_remote(scm), + {"url": "https://github.com/org/repo.git", "username": "x-access-token"}, + ), ] From 2f705f2e44888a41fe1ed9ebfc226b2698d477c6 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <dlugos.ivan@gmail.com> Date: Wed, 21 Oct 2026 08:27:25 +0200 Subject: [PATCH 3/3] ref(scm): Rename GitRemote.username to basic_auth_username Out of the Basic auth context, "username" read like a person's account. It is the fixed name a provider expects alongside a token, such as x-access-token or oauth2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --- src/scm/providers/cursor_origin/provider.py | 4 +++- src/scm/providers/github/provider.py | 4 +++- src/scm/providers/gitlab/provider.py | 2 +- src/scm/test_fixtures.py | 2 +- src/scm/types.py | 4 ++-- tests/unit/provider/test_cursor_origin.py | 2 +- tests/unit/provider/test_github.py | 4 ++-- tests/unit/provider/test_gitlab.py | 4 ++-- tests/unit/test_actions.py | 2 +- tests/unit/test_rpc_integration.py | 2 +- 10 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/scm/providers/cursor_origin/provider.py b/src/scm/providers/cursor_origin/provider.py index f0299dc..0e3c569 100644 --- a/src/scm/providers/cursor_origin/provider.py +++ b/src/scm/providers/cursor_origin/provider.py @@ -359,7 +359,9 @@ def get_pull_request_url(self, pull_request_id: str) -> str: def get_git_remote(self) -> GitRemote: # Origin's git endpoint takes an installation token as the basic-auth password and refuses a bearer header. - return GitRemote(url=f"{CURSOR_ORIGIN_GIT_BASE_URL}/{self.repository_path}.git", username="x-access-token") + return GitRemote( + url=f"{CURSOR_ORIGIN_GIT_BASE_URL}/{self.repository_path}.git", basic_auth_username="x-access-token" + ) def get_file_content( self, diff --git a/src/scm/providers/github/provider.py b/src/scm/providers/github/provider.py index b133dec..b359174 100644 --- a/src/scm/providers/github/provider.py +++ b/src/scm/providers/github/provider.py @@ -1035,7 +1035,9 @@ def get_pull_request_url(self, pull_request_id: str) -> str: return f"{self._web_base_url}/{self.repository['name']}/pull/{pull_request_id}" def get_git_remote(self) -> GitRemote: - return GitRemote(url=f"{self._web_base_url}/{self.repository['name']}.git", username="x-access-token") + return GitRemote( + url=f"{self._web_base_url}/{self.repository['name']}.git", basic_auth_username="x-access-token" + ) def create_git_blob(self, content: str, encoding: str) -> ActionResult[GitBlob]: response = self.post( diff --git a/src/scm/providers/gitlab/provider.py b/src/scm/providers/gitlab/provider.py index 91bdf81..d1e592c 100644 --- a/src/scm/providers/gitlab/provider.py +++ b/src/scm/providers/gitlab/provider.py @@ -748,7 +748,7 @@ def get_pull_request_url(self, pull_request_id: str) -> str: return f"{self.web_base_url}/{self.web_repository_path}/-/merge_requests/{pull_request_id}" def get_git_remote(self) -> GitRemote: - return GitRemote(url=f"{self.web_base_url}/{self.web_repository_path}.git", username="oauth2") + return GitRemote(url=f"{self.web_base_url}/{self.web_repository_path}.git", basic_auth_username="oauth2") def get_tree( self, diff --git a/src/scm/test_fixtures.py b/src/scm/test_fixtures.py index d0c92b5..5050004 100644 --- a/src/scm/test_fixtures.py +++ b/src/scm/test_fixtures.py @@ -1192,7 +1192,7 @@ def get_pull_request_url(self, pull_request_id: str) -> str: return f"https://github.com/test-org/test-repo/pull/{pull_request_id}" def get_git_remote(self) -> GitRemote: - return GitRemote(url="https://github.com/test-org/test-repo.git", username="x-access-token") + return GitRemote(url="https://github.com/test-org/test-repo.git", basic_auth_username="x-access-token") # Git blob operations diff --git a/src/scm/types.py b/src/scm/types.py index 35c84f9..a4e789e 100644 --- a/src/scm/types.py +++ b/src/scm/types.py @@ -70,8 +70,8 @@ class GitRemote(TypedDict): url: str """The repository's clone URL, e.g. ``https://github.com/getsentry/sentry.git``.""" - username: str - """The basic-auth username the provider pairs with a token, e.g. ``x-access-token`` on GitHub.""" + basic_auth_username: str + """Sent with the token as the Basic auth username, e.g. ``x-access-token`` on GitHub.""" type BuildStatus = Literal["pending", "running", "completed"] diff --git a/tests/unit/provider/test_cursor_origin.py b/tests/unit/provider/test_cursor_origin.py index d4be281..9da8184 100644 --- a/tests/unit/provider/test_cursor_origin.py +++ b/tests/unit/provider/test_cursor_origin.py @@ -1087,7 +1087,7 @@ def test_a_commit_url(self, provider: CursorOriginProvider) -> None: def test_a_git_remote(self, provider: CursorOriginProvider) -> None: # Git is served from origin.cursor.com rather than the web UI's cursor.com/codebase. assert provider.get_git_remote() == GitRemote( - url=f"https://origin.cursor.com/{REPO}.git", username="x-access-token" + url=f"https://origin.cursor.com/{REPO}.git", basic_auth_username="x-access-token" ) diff --git a/tests/unit/provider/test_github.py b/tests/unit/provider/test_github.py index 1fb61c7..738722f 100644 --- a/tests/unit/provider/test_github.py +++ b/tests/unit/provider/test_github.py @@ -2086,7 +2086,7 @@ def test_get_git_remote_builds_clone_url() -> None: provider = GitHubProvider(MagicMock(spec=ApiClient), organization_id=1, repository=make_repository()) assert provider.get_git_remote() == GitRemote( - url="https://github.com/test-org/test-repo.git", username="x-access-token" + url="https://github.com/test-org/test-repo.git", basic_auth_username="x-access-token" ) @@ -2099,7 +2099,7 @@ def test_ghe_web_base_url_used_in_git_remote() -> None: ) assert provider.get_git_remote() == GitRemote( - url="https://github.example.com/test-org/test-repo.git", username="x-access-token" + url="https://github.example.com/test-org/test-repo.git", basic_auth_username="x-access-token" ) diff --git a/tests/unit/provider/test_gitlab.py b/tests/unit/provider/test_gitlab.py index a9a6bb9..711c002 100644 --- a/tests/unit/provider/test_gitlab.py +++ b/tests/unit/provider/test_gitlab.py @@ -13981,7 +13981,7 @@ def test_get_git_remote_is_the_repository_path_on_its_instance(client: ApiClient provider = _make_gitlab_provider(client, name="GitLab.org / GitLab FOSS", path="gitlab-org/gitlab-foss") assert provider.get_git_remote() == GitRemote( - url="https://gitlab.com/gitlab-org/gitlab-foss.git", username="oauth2" + url="https://gitlab.com/gitlab-org/gitlab-foss.git", basic_auth_username="oauth2" ) @@ -13993,7 +13993,7 @@ def test_get_git_remote_on_a_self_managed_instance(client: ApiClient) -> None: ) assert provider.get_git_remote() == GitRemote( - url="https://gitlab.example.com:8443/test-repo.git", username="oauth2" + url="https://gitlab.example.com:8443/test-repo.git", basic_auth_username="oauth2" ) diff --git a/tests/unit/test_actions.py b/tests/unit/test_actions.py index e368728..51b4729 100644 --- a/tests/unit/test_actions.py +++ b/tests/unit/test_actions.py @@ -478,7 +478,7 @@ def _check_get_pull_request_url(result: Any) -> None: def _check_get_git_remote(result: Any) -> None: - assert result == {"url": "https://github.com/test-org/test-repo.git", "username": "x-access-token"} + assert result == {"url": "https://github.com/test-org/test-repo.git", "basic_auth_username": "x-access-token"} def _check_create_git_blob(result: Any) -> None: diff --git a/tests/unit/test_rpc_integration.py b/tests/unit/test_rpc_integration.py index f2caa52..d17c05b 100644 --- a/tests/unit/test_rpc_integration.py +++ b/tests/unit/test_rpc_integration.py @@ -968,7 +968,7 @@ def make_client_scm(organization_id, repository_id, server): ( "get_git_remote", lambda scm: actions.get_git_remote(scm), - {"url": "https://github.com/org/repo.git", "username": "x-access-token"}, + {"url": "https://github.com/org/repo.git", "basic_auth_username": "x-access-token"}, ), ]