diff --git a/lib/api/github.js b/lib/api/github.js index f0dd4555f36c225c6aa206797d9cf349af5533d4..415a2995559c25c2a8c0b6ec928b2dfd7b67e38d 100644 --- a/lib/api/github.js +++ b/lib/api/github.js @@ -225,13 +225,23 @@ async function setBaseBranch(branchName) { // Returns an array of file paths in current repo matching the fileName async function findFilePaths(fileName) { - const res = await ghGotRetry( - `search/code?q=repo:${config.repoName}+filename:${fileName}` - ); - const exactMatches = res.body.items.filter(item => item.name === fileName); - // GitHub seems to return files in the root with a leading `/` - // which then breaks things later on down the line - return exactMatches.map(item => item.path.replace(/^\//, '')); + let results = []; + let url = `search/code?q=repo:${config.repoName}+filename:${fileName}&per_page=100`; + do { + const res = await ghGotRetry(url); + const exactMatches = res.body.items.filter(item => item.name === fileName); + // GitHub seems to return files in the root with a leading `/` + // which then breaks things later on down the line + results = results.concat( + exactMatches.map(item => item.path.replace(/^\//, '')) + ); + const linkHeader = res.headers.link || ''; + const matches = linkHeader.match( + /<https:\/\/api.github\.com\/(.*?)>; rel="next".*/ + ); + url = matches ? matches[1] : null; + } while (url); + return results; } // Branch diff --git a/test/api/__snapshots__/github.spec.js.snap b/test/api/__snapshots__/github.spec.js.snap index 7f734159898869023de5d6417612e08c1d9a1d49..868b7991257a513c95f26dcae20b4eebf23bc4a4 100644 --- a/test/api/__snapshots__/github.spec.js.snap +++ b/test/api/__snapshots__/github.spec.js.snap @@ -452,6 +452,47 @@ Array [ ] `; +exports[`api/github findFilePaths(fileName) paginates 1`] = ` +Array [ + Array [ + "repos/some/repo", + Object { + "headers": Object { + "accept": "application/vnd.github.loki-preview+json", + }, + }, + ], + Array [ + "repos/some/repo/git/refs/heads/master", + undefined, + ], + Array [ + "repos/some/repo/branches/master/protection/required_status_checks", + Object { + "headers": Object { + "accept": "application/vnd.github.loki-preview+json", + }, + }, + ], + Array [ + "search/code?q=repo:some/repo+filename:package.json&per_page=100", + undefined, + ], + Array [ + "search/code?q=repo%3Arenovate-tests%2Fonboarding-1+filename%3Apackage.json&per_page=2&page=2", + undefined, + ], +] +`; + +exports[`api/github findFilePaths(fileName) paginates 2`] = ` +Array [ + "package.json", + "src/app/package.json", + "src/otherapp/package.json", +] +`; + exports[`api/github findFilePaths(fileName) should return empty array if none found 1`] = ` Array [ Array [ @@ -475,7 +516,7 @@ Array [ }, ], Array [ - "search/code?q=repo:some/repo+filename:package.json", + "search/code?q=repo:some/repo+filename:package.json&per_page=100", undefined, ], ] @@ -504,7 +545,7 @@ Array [ }, ], Array [ - "search/code?q=repo:some/repo+filename:package.json", + "search/code?q=repo:some/repo+filename:package.json&per_page=100", undefined, ], ] diff --git a/test/api/github.spec.js b/test/api/github.spec.js index 76727d4a4e3e49239b0f3ed489a9bc41c6a95603..58d1d40ae91d64a404e0f599515603120c8ad3b6 100644 --- a/test/api/github.spec.js +++ b/test/api/github.spec.js @@ -587,6 +587,7 @@ describe('api/github', () => { it('should return empty array if none found', async () => { await initRepo('some/repo', 'token'); ghGot.mockImplementationOnce(() => ({ + headers: { link: '' }, body: { items: [], }, @@ -598,6 +599,7 @@ describe('api/github', () => { it('should return the files matching the fileName', async () => { await initRepo('some/repo', 'token'); ghGot.mockImplementationOnce(() => ({ + headers: { link: '' }, body: { items: [ { name: 'package.json', path: '/package.json' }, @@ -614,6 +616,36 @@ describe('api/github', () => { expect(ghGot.mock.calls).toMatchSnapshot(); expect(files).toMatchSnapshot(); }); + it('paginates', async () => { + await initRepo('some/repo', 'token'); + ghGot.mockImplementationOnce(() => ({ + headers: { + link: + '<https://api.github.com/search/code?q=repo%3Arenovate-tests%2Fonboarding-1+filename%3Apackage.json&per_page=2&page=2>; rel="next", <https://api.github.com/search/code?q=repo%3Arenovate-tests%2Fonboarding-1+filename%3Apackage.json&per_page=2&page=2>; rel="last" <https://api.github.com/search/code?q=repo%3Arenovate-tests%2Fonboarding-1+filename%3Apackage.json&per_page=2&page=1>; rel="first", <https://api.github.com/search/code?q=repo%3Arenovate-tests%2Fonboarding-1+filename%3Apackage.json&per_page=2&page=1>; rel="prev"', + }, + body: { + items: [ + { name: 'package.json', path: '/package.json' }, + { + name: 'package.json.something-else', + path: 'some-dir/package.json.some-thing-else', + }, + ], + }, + })); + ghGot.mockImplementationOnce(() => ({ + headers: { link: '' }, + body: { + items: [ + { name: 'package.json', path: 'src/app/package.json' }, + { name: 'package.json', path: 'src/otherapp/package.json' }, + ], + }, + })); + const files = await github.findFilePaths('package.json'); + expect(ghGot.mock.calls).toMatchSnapshot(); + expect(files).toMatchSnapshot(); + }); }); describe('branchExists(branchName)', () => { it('should return true if the branch exists (one result)', async () => {