Skip to content

Commit 6d8ea8a

Browse files
authored
feat: add paginatantion support for compareCommits and compareCommitsWithBasehead (#678)
1 parent 8ec2713 commit 6d8ea8a

File tree

8 files changed

+166
-6
lines changed

8 files changed

+166
-6
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ Most of GitHub's paginating REST API endpoints return an array, but there are a
183183
- [List check suites for a specific ref](https://developer.github.com/v3/checks/suites/#response-1) (key: `check_suites`)
184184
- [List repositories](https://developer.github.com/v3/apps/installations/#list-repositories) for an installation (key: `repositories`)
185185
- [List installations for a user](https://developer.github.com/v3/apps/installations/#response-1) (key `installations`)
186+
- [Compare commits](https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#compare-two-commits) (key `commits`)
186187

187188
`octokit.paginate()` is working around these inconsistencies so you don't have to worry about it.
188189

scripts/update-endpoints/typescript.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ const ENDPOINTS_WITH_PER_PAGE_ATTRIBUTE_THAT_BEHAVE_DIFFERENTLY = [
2828
// Only the `files` key inside the commit is paginated. The rest is duplicated across
2929
// all pages. Handling this case properly requires custom code.
3030
{ scope: "repos", id: "get-commit" },
31-
// The [docs](https://docs.github.com/en/rest/commits/commits#compare-two-commits) make
32-
// these ones sound like a special case too - they must be because they support pagination
33-
// but doesn't return an array.
34-
{ scope: "repos", id: "compare-commits" },
35-
{ scope: "repos", id: "compare-commits-with-basehead" },
3631
];
3732

3833
const hasMatchingEndpoint = (list, id, scope) =>

src/generated/paginating-endpoints.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,26 @@ export interface PaginatingEndpoints {
11951195
response: Endpoints["GET /repos/{owner}/{repo}/commits/{ref}/statuses"]["response"];
11961196
};
11971197

1198+
/**
1199+
* @see https://docs.github.com/rest/commits/commits#compare-two-commits
1200+
*/
1201+
"GET /repos/{owner}/{repo}/compare/{basehead}": {
1202+
parameters: Endpoints["GET /repos/{owner}/{repo}/compare/{basehead}"]["parameters"];
1203+
response: Endpoints["GET /repos/{owner}/{repo}/compare/{basehead}"]["response"] & {
1204+
data: Endpoints["GET /repos/{owner}/{repo}/compare/{basehead}"]["response"]["data"]["commits"];
1205+
};
1206+
};
1207+
1208+
/**
1209+
* @see https://docs.github.com/rest/reference/repos#compare-two-commits
1210+
*/
1211+
"GET /repos/{owner}/{repo}/compare/{base}...{head}": {
1212+
parameters: Endpoints["GET /repos/{owner}/{repo}/compare/{base}...{head}"]["parameters"];
1213+
response: Endpoints["GET /repos/{owner}/{repo}/compare/{base}...{head}"]["response"] & {
1214+
data: Endpoints["GET /repos/{owner}/{repo}/compare/{base}...{head}"]["response"]["data"]["commits"];
1215+
};
1216+
};
1217+
11981218
/**
11991219
* @see https://docs.github.com/rest/repos/repos#list-repository-contributors
12001220
*/
@@ -2325,6 +2345,8 @@ export const paginatingEndpoints: (keyof PaginatingEndpoints)[] = [
23252345
"GET /repos/{owner}/{repo}/commits/{ref}/check-suites",
23262346
"GET /repos/{owner}/{repo}/commits/{ref}/status",
23272347
"GET /repos/{owner}/{repo}/commits/{ref}/statuses",
2348+
"GET /repos/{owner}/{repo}/compare/{basehead}",
2349+
"GET /repos/{owner}/{repo}/compare/{base}...{head}",
23282350
"GET /repos/{owner}/{repo}/contributors",
23292351
"GET /repos/{owner}/{repo}/dependabot/alerts",
23302352
"GET /repos/{owner}/{repo}/dependabot/secrets",

src/iterator.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ export function iterator(
4040
/<([^<>]+)>;\s*rel="next"/,
4141
) || [])[1];
4242

43+
if (!url && "total_commits" in normalizedResponse.data) {
44+
const parsedUrl = new URL(normalizedResponse.url);
45+
const params = parsedUrl.searchParams;
46+
const page = parseInt(params.get("page") || "1", 10);
47+
/* v8 ignore next */
48+
const per_page = parseInt(params.get("per_page") || "250", 10);
49+
if (page * per_page < normalizedResponse.data.total_commits) {
50+
params.set("page", String(page + 1));
51+
url = parsedUrl.toString();
52+
}
53+
}
54+
4355
return { value: normalizedResponse };
4456
} catch (error: any) {
4557
// `GET /repos/{owner}/{repo}/commits` throws a `409 Conflict` error for empty repositories

src/normalize-paginated-list-response.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,20 @@ export function normalizePaginatedListResponse(
2828
};
2929
}
3030
const responseNeedsNormalization =
31-
"total_count" in response.data && !("url" in response.data);
31+
("total_count" in response.data && !("url" in response.data)) ||
32+
"total_commits" in response.data;
3233
if (!responseNeedsNormalization) return response;
3334

3435
// keep the additional properties intact as there is currently no other way
3536
// to retrieve the same information.
3637
const incompleteResults = response.data.incomplete_results;
3738
const repositorySelection = response.data.repository_selection;
3839
const totalCount = response.data.total_count;
40+
const totalCommits = response.data.total_commits;
3941
delete response.data.incomplete_results;
4042
delete response.data.repository_selection;
4143
delete response.data.total_count;
44+
delete response.data.total_commits;
4245

4346
const namespaceKey = Object.keys(response.data)[0];
4447
const data = response.data[namespaceKey];
@@ -53,5 +56,6 @@ export function normalizePaginatedListResponse(
5356
}
5457

5558
response.data.total_count = totalCount;
59+
response.data.total_commits = totalCommits;
5660
return response;
5761
}

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import type { PaginatingEndpoints } from "./generated/paginating-endpoints.js";
3131
type PaginationMetadataKeys =
3232
| "repository_selection"
3333
| "total_count"
34+
| "total_commits"
3435
| "incomplete_results";
3536

3637
// https://stackoverflow.com/a/58980331/206879

test/issues.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,74 @@ describe("https://github.com/octokit/plugin-paginate-rest.js/issues/158", () =>
5757
expect(result.length).toEqual(0);
5858
});
5959
});
60+
61+
describe("https://github.com/octokit/plugin-paginate-rest.js/issues/647", () => {
62+
test("paginate compareCommits when link header is missing", async () => {
63+
const mock = fetchMock
64+
.createInstance()
65+
.get(
66+
"https://api.github.com/repos/owner/repo/compare/main...feature?per_page=1",
67+
{
68+
body: {
69+
total_commits: 3,
70+
commits: [
71+
{
72+
sha: "abc123",
73+
},
74+
],
75+
},
76+
headers: {}, // missing link header
77+
},
78+
)
79+
.get(
80+
"https://api.github.com/repos/owner/repo/compare/main...feature?per_page=1&page=2",
81+
{
82+
body: {
83+
total_commits: 3,
84+
commits: [
85+
{
86+
sha: "def456",
87+
},
88+
],
89+
},
90+
headers: {},
91+
},
92+
)
93+
.get(
94+
"https://api.github.com/repos/owner/repo/compare/main...feature?per_page=1&page=3",
95+
{
96+
body: {
97+
total_commits: 3,
98+
commits: [
99+
{
100+
sha: "ghi789",
101+
},
102+
],
103+
},
104+
headers: {},
105+
},
106+
);
107+
108+
const TestOctokit = Octokit.plugin(paginateRest);
109+
const octokit = new TestOctokit({
110+
request: {
111+
fetch: mock.fetchHandler,
112+
},
113+
});
114+
115+
const result = await octokit.paginate(
116+
"GET /repos/{owner}/{repo}/compare/{basehead}",
117+
{
118+
owner: "owner",
119+
repo: "repo",
120+
basehead: "main...feature",
121+
per_page: 1,
122+
},
123+
);
124+
125+
expect(result.length).toEqual(3);
126+
expect(result[0].sha).toEqual("abc123");
127+
expect(result[1].sha).toEqual("def456");
128+
expect(result[2].sha).toEqual("ghi789");
129+
});
130+
});

test/paginate.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,60 @@ describe("pagination", () => {
998998
]);
999999
});
10001000
});
1001+
1002+
it(".paginate() with results namespace (GET /repos/{owner}/{repo}/compare/{basehead})", () => {
1003+
const result1 = {
1004+
total_commits: 2,
1005+
commits: [
1006+
{
1007+
sha: "f3b573e4d60a079d154018d2e2d04aff4d26fc41",
1008+
},
1009+
],
1010+
};
1011+
const result2 = {
1012+
total_commits: 2,
1013+
commits: [
1014+
{
1015+
sha: "a740e83052aea45a4cbcdf2954a3a9e47b5d530d",
1016+
},
1017+
],
1018+
};
1019+
1020+
const mock = fetchMock
1021+
.createInstance()
1022+
.get(
1023+
"https://api.github.com/repos/octocat/hello-world/compare/1.0.0...1.0.1?per_page=1",
1024+
{
1025+
body: result1,
1026+
},
1027+
)
1028+
.get(
1029+
"https://api.github.com/repos/octocat/hello-world/compare/1.0.0...1.0.1?per_page=1&page=2",
1030+
{
1031+
body: result2,
1032+
},
1033+
);
1034+
1035+
const octokit = new TestOctokit({
1036+
request: {
1037+
fetch: mock.fetchHandler,
1038+
},
1039+
});
1040+
1041+
return octokit
1042+
.paginate({
1043+
method: "GET",
1044+
url: "/repos/{owner}/{repo}/compare/{basehead}",
1045+
owner: "octocat",
1046+
repo: "hello-world",
1047+
basehead: "1.0.0...1.0.1",
1048+
per_page: 1,
1049+
})
1050+
.then((results) => {
1051+
expect(results).toEqual([...result1.commits, ...result2.commits]);
1052+
});
1053+
});
1054+
10011055
it(".paginate() with results namespace (GET /repos/{owner}/{repo}/actions/runs)", () => {
10021056
const result1 = {
10031057
total_count: 2,

0 commit comments

Comments
 (0)