diff --git a/lib/api/github.js b/lib/api/github.js index 13e727057fd8909c70ebc0d1d22a0474b709bc60..da169d0b3d70a01b404c090c3380a8832a012b48 100644 --- a/lib/api/github.js +++ b/lib/api/github.js @@ -226,29 +226,32 @@ async function setBaseBranch(branchName) { // Search -// Returns an array of file paths in current repo matching the fileName -async function findFilePaths(fileName, content) { - let results = []; - let url = `search/code?q=`; - if (content) { - url += `${content}+`; - } - url += `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".*/ +// Get full file list +async function getFileList(branchName) { + if (config.fileList) { + return config.fileList; + } + const res = await ghGotRetry( + `repos/${config.repoName}/git/trees/${branchName}?recursive=1` + ); + if (res.body.truncated) { + logger.warn( + { repository: config.repoName }, + 'repository tree is truncated' ); - url = matches ? matches[1] : null; - } while (url); - return results; + } + config.fileList = res.body.tree + .filter(item => item.type === 'blob') + .map(item => item.path) + .sort(); + return config.fileList; +} + +// Return all files in the repository matching the filename +async function findFilePaths(fileName, branchName = config.baseBranch) { + return (await getFileList(branchName)).filter(fullFilePath => + fullFilePath.endsWith(fileName) + ); } // Branch diff --git a/lib/workers/repository/apis.js b/lib/workers/repository/apis.js index d035572fe750ad259aaa4946a19c0c720636cfc1..281b64ce8b797eaabf5d20daba6dcbd70a70ea9e 100644 --- a/lib/workers/repository/apis.js +++ b/lib/workers/repository/apis.js @@ -292,10 +292,7 @@ async function detectPackageFiles(input, detectAllLanguages = false) { } if (detectAllLanguages || config.meteor.enabled) { logger.debug('Detecting meteor package.js files'); - const meteorPackageFiles = await config.api.findFilePaths( - 'package.js', - 'Npm.depends' - ); + const meteorPackageFiles = await config.api.findFilePaths('package.js'); if (meteorPackageFiles.length) { logger.info( { count: meteorPackageFiles.length }, diff --git a/test/api/__snapshots__/github.spec.js.snap b/test/api/__snapshots__/github.spec.js.snap index d291793c825491dbad2a36c9bc56e22290e28a85..4b3c76895105b7729e05831ce619512f2a28b22e 100644 --- a/test/api/__snapshots__/github.spec.js.snap +++ b/test/api/__snapshots__/github.spec.js.snap @@ -452,106 +452,7 @@ 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 [ - "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, - ], -] -`; - exports[`api/github findFilePaths(fileName) should return the files matching the fileName 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=some-content+repo:some/repo+filename:package.json&per_page=100", - undefined, - ], -] -`; - -exports[`api/github findFilePaths(fileName) should return the files matching the fileName 2`] = ` Array [ "package.json", "src/app/package.json", diff --git a/test/api/github.spec.js b/test/api/github.spec.js index 8bbd55995edbee8d578d201586efba54f6dcc90e..a6ea923cceba47fcbb57323908d41e51f3726729 100644 --- a/test/api/github.spec.js +++ b/test/api/github.spec.js @@ -591,66 +591,46 @@ describe('api/github', () => { }); }); describe('findFilePaths(fileName)', () => { - it('should return empty array if none found', async () => { + it('warns if truncated result', async () => { await initRepo('some/repo', 'token'); ghGot.mockImplementationOnce(() => ({ - headers: { link: '' }, body: { - items: [], + truncated: true, + tree: [], }, })); const files = await github.findFilePaths('package.json'); - expect(ghGot.mock.calls).toMatchSnapshot(); expect(files.length).toBe(0); }); - it('should return the files matching the fileName', async () => { + it('caches the result', async () => { await initRepo('some/repo', 'token'); ghGot.mockImplementationOnce(() => ({ - headers: { link: '' }, body: { - items: [ - { name: 'package.json', path: '/package.json' }, - { - name: 'package.json.something-else', - path: 'some-dir/package.json.some-thing-else', - }, - { name: 'package.json', path: 'src/app/package.json' }, - { name: 'package.json', path: 'src/otherapp/package.json' }, - ], + truncated: true, + tree: [], }, })); - const files = await github.findFilePaths('package.json', 'some-content'); - expect(ghGot.mock.calls).toMatchSnapshot(); - expect(files).toMatchSnapshot(); + let files = await github.findFilePaths('package.json'); + expect(files.length).toBe(0); + files = await github.findFilePaths('package.js'); + expect(files.length).toBe(0); }); - it('paginates', async () => { + it('should return the files matching the fileName', 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' }, + tree: [ + { type: 'blob', path: 'package.json' }, { - name: 'package.json.something-else', + type: 'blob', 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' }, + { type: 'blob', path: 'src/app/package.json' }, + { type: 'blob', path: 'src/otherapp/package.json' }, ], }, })); const files = await github.findFilePaths('package.json'); - expect(ghGot.mock.calls).toMatchSnapshot(); expect(files).toMatchSnapshot(); }); });