diff --git a/lib/platform/github/index.js b/lib/platform/github/index.js index 8e578561fa0bd48c1135321cbc1a412c08928166..57ec2ea20bcf0d03d08a648c24c846b8053430f1 100644 --- a/lib/platform/github/index.js +++ b/lib/platform/github/index.js @@ -915,16 +915,45 @@ async function getPr(prNo) { pr.canMerge = true; } if (pr.mergeable_state === 'dirty') { - logger.debug('PR state is dirty so unmergeable'); + logger.debug({ prNo }, 'PR state is dirty so unmergeable'); pr.isUnmergeable = true; } if (pr.commits === 1) { - // Only one commit was made - must have been renovate - logger.debug('Only 1 commit in PR so rebase is possible'); - pr.canRebase = true; + if (config.gitAuthor) { + // Check against gitAuthor + logger.debug('Checking all commits'); + try { + const commitAuthorEmail = (await get( + `repos/${config.parentRepo || + config.repository}/pulls/${prNo}/commits` + )).body[0].commit.author.email; + if (commitAuthorEmail === config.gitAuthor.address) { + logger.debug( + { prNo }, + '1 commit matches configured gitAuthor so can rebase' + ); + pr.canRebase = true; + } else { + logger.debug( + { prNo }, + '1 commit and not by configured gitAuthor so cannot rebase' + ); + pr.canRebase = false; + } + } catch (err) { + logger.warn({ prNo }, 'Error checking branch commits for rebase'); + pr.canRebase = false; + } + } else { + logger.debug( + { prNo }, + '1 commit and no configured gitAuthor so can rebase' + ); + pr.canRebase = true; + } } else { // Check if only one author of all commits - logger.debug('Checking all commits'); + logger.debug({ prNo }, 'Checking all commits'); const prCommits = (await get( `repos/${config.parentRepo || config.repository}/pulls/${prNo}/commits` )).body; diff --git a/test/platform/github/__snapshots__/index.spec.js.snap b/test/platform/github/__snapshots__/index.spec.js.snap index bc03550a8cfdaadcb433bd4cd45e7d95e4f90041..dc96b8188a6e8780763a764043b817c771eb8faf 100644 --- a/test/platform/github/__snapshots__/index.spec.js.snap +++ b/test/platform/github/__snapshots__/index.spec.js.snap @@ -370,6 +370,40 @@ Object { } `; +exports[`platform/github getPr(prNo) should return a not rebaseable PR if gitAuthor and error 1`] = ` +Object { + "base": Object { + "sha": "1234", + }, + "branchName": undefined, + "canRebase": false, + "commits": 1, + "displayNumber": "Pull Request #1", + "isUnmergeable": true, + "mergeable_state": "dirty", + "number": 1, + "sha": undefined, + "state": "open", +} +`; + +exports[`platform/github getPr(prNo) should return a not rebaseable PR if gitAuthor does not match 1 commit 1`] = ` +Object { + "base": Object { + "sha": "1234", + }, + "branchName": undefined, + "canRebase": false, + "commits": 1, + "displayNumber": "Pull Request #1", + "isUnmergeable": true, + "mergeable_state": "dirty", + "number": 1, + "sha": undefined, + "state": "open", +} +`; + exports[`platform/github getPr(prNo) should return a rebaseable PR despite multiple commits 1`] = ` Object { "base": Object { @@ -387,6 +421,23 @@ Object { } `; +exports[`platform/github getPr(prNo) should return a rebaseable PR if gitAuthor matches 1 commit 1`] = ` +Object { + "base": Object { + "sha": "1234", + }, + "branchName": undefined, + "canRebase": true, + "commits": 1, + "displayNumber": "Pull Request #1", + "isUnmergeable": true, + "mergeable_state": "dirty", + "number": 1, + "sha": undefined, + "state": "open", +} +`; + exports[`platform/github getPr(prNo) should return a rebaseable PR if web-flow is second author 1`] = ` Object { "base": Object { diff --git a/test/platform/github/index.spec.js b/test/platform/github/index.spec.js index a6417cd652aee0efa1ea8812e84ea246afa88cc5..5d0e4cd20b72e836d25fd58235c81b322b3aa647 100644 --- a/test/platform/github/index.spec.js +++ b/test/platform/github/index.spec.js @@ -1348,6 +1348,116 @@ describe('platform/github', () => { expect(pr.canRebase).toBe(true); expect(pr).toMatchSnapshot(); }); + it('should return a rebaseable PR if gitAuthor matches 1 commit', async () => { + await initRepo({ + repository: 'some/repo', + token: 'token', + gitAuthor: 'Renovate Bot <bot@renovateapp.com>', + }); + get.mockImplementationOnce(() => ({ + body: { + number: 1, + state: 'open', + mergeable_state: 'dirty', + base: { sha: '1234' }, + commits: 1, + }, + })); + get.mockImplementationOnce(() => ({ + body: [ + { + commit: { + author: { + email: 'bot@renovateapp.com', + }, + }, + }, + ], + })); + // getBranchCommit + get.mockImplementationOnce(() => ({ + body: { + object: { + sha: '1234', + }, + }, + })); + const pr = await github.getPr(1234); + expect(pr.canRebase).toBe(true); + expect(pr).toMatchSnapshot(); + }); + it('should return a not rebaseable PR if gitAuthor does not match 1 commit', async () => { + await initRepo({ + repository: 'some/repo', + token: 'token', + gitAuthor: 'Renovate Bot <bot@renovateapp.com>', + }); + get.mockImplementationOnce(() => ({ + body: { + number: 1, + state: 'open', + mergeable_state: 'dirty', + base: { sha: '1234' }, + commits: 1, + }, + })); + get.mockImplementationOnce(() => ({ + body: [ + { + commit: { + author: { + email: 'foo@bar.com', + }, + }, + }, + ], + })); + // getBranchCommit + get.mockImplementationOnce(() => ({ + body: { + object: { + sha: '1234', + }, + }, + })); + const pr = await github.getPr(1234); + expect(pr.canRebase).toBe(false); + expect(pr).toMatchSnapshot(); + }); + it('should return a not rebaseable PR if gitAuthor and error', async () => { + await initRepo({ + repository: 'some/repo', + token: 'token', + gitAuthor: 'Renovate Bot <bot@renovateapp.com>', + }); + get.mockImplementationOnce(() => ({ + body: { + number: 1, + state: 'open', + mergeable_state: 'dirty', + base: { sha: '1234' }, + commits: 1, + }, + })); + get.mockImplementationOnce(() => ({ + body: [ + { + commit: {}, + }, + ], + })); + // getBranchCommit + get.mockImplementationOnce(() => ({ + body: { + object: { + sha: '1234', + }, + }, + })); + const pr = await github.getPr(1234); + expect(pr.canRebase).toBe(false); + expect(pr).toMatchSnapshot(); + }); }); describe('getPrFiles()', () => { it('should return empty if no prNo is passed', async () => {