From 0bdf035aa9e055bd606005b9717fdad7cf005ed9 Mon Sep 17 00:00:00 2001 From: Dan Fuller Date: Fri, 25 Sep 2026 10:05:09 -0700 Subject: [PATCH] feat(cursor-origin): Read a commit with its changed files Add support for reading a single commit with its stats and changed files, and for listing a commit's changed files. Origin returns files separately, so the commit response includes only the first 100 files. --- src/scm/providers/cursor_origin/provider.py | 41 +++++++++++++ tests/unit/provider/test_cursor_origin.py | 66 +++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/src/scm/providers/cursor_origin/provider.py b/src/scm/providers/cursor_origin/provider.py index b9a15a6..89564c3 100644 --- a/src/scm/providers/cursor_origin/provider.py +++ b/src/scm/providers/cursor_origin/provider.py @@ -36,6 +36,7 @@ CommitAuthorParam, CommitComparison, CommitFile, + CommitWithChanges, CoPilotChatExtension, CredentialsSet, DeleteCommitAction, @@ -565,6 +566,46 @@ def _get_archive_location(self, ref: str, request_options: RequestOptions | None allow_redirects=False, ) + def get_commit( + self, + sha: SHA, + request_options: RequestOptions | None = None, + ) -> ActionResult[CommitWithChanges]: + """Return a commit with its changed files.""" + response = self.get( + f"/repos/{self.repository_path}/commits/{sha}", + request_options=request_options, + ) + files = self.get_commit_changes( + sha, + pagination={"per_page": MAX_PAGE_SIZE}, + request_options=request_options, + )["data"] + return map_action( + response, + lambda raw: CommitWithChanges( + id=raw["sha"], + message=raw["commit"]["message"], + author=map_commit_author(raw["commit"]["author"]), + additions=raw["stats"]["additions"], + deletions=raw["stats"]["deletions"], + files=files, + ), + ) + + def get_commit_changes( + self, + sha: SHA, + pagination: PaginationParams | None = None, + request_options: RequestOptions | None = None, + ) -> PaginatedActionResult[list[CommitFile]]: + response = self.get( + f"/repos/{self.repository_path}/commits/{sha}/files", + pagination=pagination, + request_options=request_options, + ) + return map_paginated_action(response, lambda raw: [map_commit_file(file) for file in raw["files"]]) + def create_check_run( self, name: str, diff --git a/tests/unit/provider/test_cursor_origin.py b/tests/unit/provider/test_cursor_origin.py index d74a326..3f930ea 100644 --- a/tests/unit/provider/test_cursor_origin.py +++ b/tests/unit/provider/test_cursor_origin.py @@ -1115,6 +1115,72 @@ def test_the_counts_and_the_changed_files_are_read( assert result["meta"]["next_cursor"] == "t2" +class TestCommitDetail: + def test_a_commit_is_read_with_its_changed_files( + self, provider: CursorOriginProvider, client: unittest.mock.MagicMock + ) -> None: + client.request.side_effect = [ + _response({**_commit_raw("abc123"), "stats": {"additions": 6, "deletions": 3, "total": 9}}), + _response( + { + "files": [ + { + "filename": "src/app.py", + "status": "modified", + "additions": 6, + "deletions": 3, + "changes": 9, + "patch": "@@ -1 +1 @@", + } + ], + "nextPageToken": "", + } + ), + ] + + result = provider.get_commit("abc123") + + commit, files = client.request.call_args_list + assert commit.kwargs["path"] == f"/repos/{REPO}/commits/abc123" + assert files.kwargs["path"] == f"/repos/{REPO}/commits/abc123/files" + assert files.kwargs["params"] == {"pageSize": "100"} + assert result["data"]["id"] == "abc123" + assert (result["data"]["additions"], result["data"]["deletions"]) == (6, 3) + assert result["data"]["files"] == [ + { + "filename": "src/app.py", + "status": "modified", + "patch": "@@ -1 +1 @@", + "additions": 6, + "deletions": 3, + "previous_filename": None, + } + ] + + def test_the_changed_files_are_paged(self, provider: CursorOriginProvider, client: unittest.mock.MagicMock) -> None: + client.request.return_value = _response( + { + "files": [ + { + "filename": "logo.png", + "status": "added", + "additions": 0, + "deletions": 0, + "changes": 0, + "patch": "", + } + ], + "nextPageToken": "t2", + } + ) + + result = provider.get_commit_changes("abc123", pagination={"cursor": "1", "per_page": 30}) + + assert client.request.call_args.kwargs["params"] == {"pageSize": "30"} + assert result["data"][0]["patch"] is None + assert result["meta"]["next_cursor"] == "t2" + + class TestPullRequestDiff: def test_the_changed_files_are_listed( self, provider: CursorOriginProvider, client: unittest.mock.MagicMock