Skip to content

Commit f054550

Browse files
authored
Merge pull request #1440 from fabianehlert/feature/github-api-pagination
GitHub: use files API with pagination
2 parents 75438bc + bf158b8 commit f054550

File tree

3 files changed

+93
-28
lines changed

3 files changed

+93
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<!-- Your comment below this -->
1818

1919
- Adds a log when you run on GitHub Actions without being a pull_request - [@orta]
20+
- GitHub: Move to 'List pull request files' API with pagination support [@fabianehlert]
2021

2122
<!-- Your comment above this -->
2223

source/platforms/github/GitHubAPI.ts

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ export type APIToken = string
1717

1818
const limit = pLimit(25)
1919

20+
// Structure of files returned by the 'List pull request files' API
21+
export interface GitHubFile {
22+
filename: string
23+
patch: string
24+
}
25+
2026
// Note that there are parts of this class which don't seem to be
2127
// used by Danger, they are exposed for Peril support.
2228

@@ -329,13 +335,60 @@ export class GitHubAPI {
329335
})
330336
}
331337

332-
getPullRequestDiff = async () => {
338+
getPullRequestFiles = async (page: number = 1): Promise<GitHubFile[]> => {
333339
const repo = this.repoMetadata.repoSlug
334340
const prID = this.repoMetadata.pullRequestID
335-
const res = await this.get(`repos/${repo}/pulls/${prID}`, {
336-
Accept: "application/vnd.github.v3.diff",
337-
})
338-
return res.ok ? res.text() : ""
341+
const perPage = 100
342+
const url = `repos/${repo}/pulls/${prID}/files?page=${page}&per_page=${perPage}`
343+
344+
try {
345+
const response = await this.get(url, {
346+
Accept: "application/vnd.github.v3.diff",
347+
})
348+
const data = await response.json()
349+
350+
if (!response.ok) {
351+
throw new Error(`GitHub 'List pull request files' API returned an error: ${data.message}`)
352+
}
353+
354+
// Check for pagination using the Link header
355+
const linkHeader = response.headers.get("Link")
356+
const hasNextPage = linkHeader && linkHeader.includes('rel="next"')
357+
358+
// If there's a next page, recursively fetch it and concatenate the results
359+
if (hasNextPage) {
360+
const nextPageNumber = page + 1
361+
const nextPageFiles = await this.getPullRequestFiles(nextPageNumber)
362+
return [...data, ...nextPageFiles]
363+
}
364+
365+
return data as GitHubFile[]
366+
} catch (error) {
367+
console.error("Failed to fetch GitHub pull request files:", error)
368+
throw error
369+
}
370+
}
371+
372+
getPullRequestDiff = async (): Promise<string> => {
373+
// This is a hack to get the file patch into a format that parse-diff accepts
374+
// as the GitHub API for listing pull request files is missing file names in the patch.
375+
function prefixedPatch(file: GitHubFile): string {
376+
return `
377+
diff --git a/${file.filename} b/${file.filename}
378+
--- a/${file.filename}
379+
+++ b/${file.filename}
380+
${file.patch}
381+
`
382+
}
383+
384+
try {
385+
const files = await this.getPullRequestFiles()
386+
const diff = files.map(prefixedPatch).join("\n")
387+
return diff
388+
} catch (error) {
389+
console.error("Failed to fetch pull request diff:", error)
390+
return ""
391+
}
339392
}
340393

341394
getFileContents = async (path: string, repoSlug: string, ref: string): Promise<any> => {

source/platforms/github/_tests/_github_api.test.ts

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { FakeCI } from "../../../ci_source/providers/Fake"
22
import { GitHubAPI } from "../GitHubAPI"
33
import { requestWithFixturedJSON } from "../../_tests/_github.test"
44
import { GitHubUser } from "../../../dsl/GitHubDSL"
5+
import { GitHubFile } from "../GitHubAPI"
56

67
const fetchJSON = (api: any, params: any): Promise<any> => {
78
return Promise.resolve({
@@ -25,19 +26,6 @@ const fetchErrorJSON = (api: any, params: any): Promise<any> => {
2526
})
2627
}
2728

28-
const fetchText = (api: any, params: any): Promise<any> => {
29-
return Promise.resolve({
30-
ok: true,
31-
text: () =>
32-
Promise.resolve(
33-
JSON.stringify({
34-
api,
35-
...params,
36-
})
37-
),
38-
})
39-
}
40-
4129
const fetch = (api: any, params: any): Promise<any> => {
4230
return Promise.resolve({
4331
api,
@@ -86,17 +74,40 @@ describe("API testing", () => {
8674
})
8775

8876
it("getPullRequestDiff", async () => {
89-
api.getPullRequestInfo = await requestWithFixturedJSON("github_pr.json")
90-
api.fetch = fetchText
91-
let text = await api.getPullRequestDiff()
92-
expect(JSON.parse(text)).toMatchObject({
93-
api: "https://api.github.com/repos/artsy/emission/pulls/1",
94-
headers: {
95-
Authorization: "token ABCDE",
96-
Accept: "application/vnd.github.v3.diff",
97-
"Content-Type": "application/json",
98-
},
77+
api.fetch = jest.fn().mockReturnValue({
78+
ok: true,
79+
headers: new Headers({}),
80+
json: jest
81+
.fn()
82+
.mockImplementation(() =>
83+
Promise.resolve<GitHubFile[]>(JSON.parse('[{"filename": "file.txt", "patch": "+ hello"}]'))
84+
),
9985
})
86+
87+
let diff = await api.getPullRequestDiff()
88+
89+
let expected = `
90+
diff --git a/file.txt b/file.txt
91+
--- a/file.txt
92+
+++ b/file.txt
93+
+ hello
94+
`
95+
96+
expect(diff).toEqual(expected)
97+
98+
expect(api.fetch).toHaveBeenCalledWith(
99+
"https://api.github.com/repos/artsy/emission/pulls/1/files?page=1&per_page=100",
100+
{
101+
body: null,
102+
headers: {
103+
Accept: "application/vnd.github.v3.diff",
104+
Authorization: "token ABCDE",
105+
"Content-Type": "application/json",
106+
},
107+
method: "GET",
108+
},
109+
undefined
110+
)
100111
})
101112

102113
it("getDangerCommentIDs ignores comments not marked as generated", async () => {

0 commit comments

Comments
 (0)