diff --git a/lib/platform/bitbucket/index.ts b/lib/platform/bitbucket/index.ts index 776b7aa81ea8f32e644bbf92c14e946d0a199dc4..92efc51c7404b35642d1e6c04ff226a604419721 100644 --- a/lib/platform/bitbucket/index.ts +++ b/lib/platform/bitbucket/index.ts @@ -1,4 +1,5 @@ import parseDiff from 'parse-diff'; +import addrs from 'email-addresses'; import { api } from './bb-got-wrapper'; import * as utils from './utils'; import * as hostRules from '../../util/host-rules'; @@ -549,6 +550,9 @@ async function isPrConflicted(prNo: number) { return utils.isConflicted(parseDiff(diff)); } +interface Commit { + author: { raw: string }; +} // Gets details for a PR export async function getPr(prNo: number) { const pr = (await api.get( @@ -567,10 +571,43 @@ export async function getPr(prNo: number) { if (utils.prStates.open.includes(pr.state)) { res.isConflicted = await isPrConflicted(prNo); - const commits = await utils.accumulateValues(pr.links.commits.href); - if (commits.length === 1) { - res.canRebase = true; - res.canMerge = true; + + // TODO: Is that correct? Should we check getBranchStatus like gitlab? + res.canMerge = !res.isConflicted; + + // we only want the first commit, because size tells us the overall number + const { body } = await api.get<utils.PagedResult<Commit>>( + pr.links.commits.href + '?pagelen=1' + ); + + if (body.size === 1) { + if (global.gitAuthor) { + const author = addrs.parseOneAddress( + body.values[0].author.raw + ) as addrs.ParsedMailbox; + if (author.address === global.gitAuthor.email) { + 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; + } + } else { + logger.debug( + { prNo }, + '1 commit and no configured gitAuthor so can rebase' + ); + pr.canRebase = true; + } + } else { + logger.debug({ prNo }, `${body.size} commits so cannot rebase`); + pr.canRebase = false; } } if (await branchExists(pr.source.branch.name)) { diff --git a/lib/platform/bitbucket/utils.ts b/lib/platform/bitbucket/utils.ts index b753a2f45ec3a98f3090cc1121740713669c242e..a9b214a9b49859a62dffeeb715ad5cd44b908b41 100644 --- a/lib/platform/bitbucket/utils.ts +++ b/lib/platform/bitbucket/utils.ts @@ -1,6 +1,7 @@ import url from 'url'; import { api } from './bb-got-wrapper'; import { Storage } from '../git/storage'; +import { GotResponse } from '../common'; export interface Config { baseBranch: string; @@ -17,6 +18,12 @@ export interface Config { username: string; } +export interface PagedResult<T = any> { + size: number; + next?: string; + values: T[]; +} + export function repoInfoTransformer(repoInfoBody: any) { return { privateRepo: repoInfoBody.is_private, @@ -68,7 +75,10 @@ export async function accumulateValues<T = any>( const lowerCaseMethod = method.toLocaleLowerCase(); while (typeof nextUrl !== 'undefined') { - const { body } = await (api as any)[lowerCaseMethod](nextUrl, options); + const { body } = (await api[lowerCaseMethod]( + nextUrl, + options + )) as GotResponse<PagedResult<T>>; accumulator = [...accumulator, ...body.values]; nextUrl = body.next; } diff --git a/lib/platform/common.ts b/lib/platform/common.ts index ac0f4db5dd2a73d8a3816a0bb61ac29851c5276b..8458aabf2cc5fdc3f8cfa7077f480bac00801b52 100644 --- a/lib/platform/common.ts +++ b/lib/platform/common.ts @@ -6,31 +6,33 @@ export interface GotApiOptions { body?: any; } +export type GotResponse<T extends object = any> = got.Response<T>; + export interface GotApi<TOptions extends object = any> { get<T extends object = any>( url: string, options?: GotApiOptions & TOptions - ): Promise<got.Response<T>>; + ): Promise<GotResponse<T>>; post<T extends object = any>( url: string, options?: GotApiOptions & TOptions - ): Promise<got.Response<T>>; + ): Promise<GotResponse<T>>; put<T extends object = any>( url: string, options?: GotApiOptions & TOptions - ): Promise<got.Response<T>>; + ): Promise<GotResponse<T>>; patch<T extends object = any>( url: string, options?: GotApiOptions & TOptions - ): Promise<got.Response<T>>; + ): Promise<GotResponse<T>>; head<T extends object = any>( url: string, options?: GotApiOptions & TOptions - ): Promise<got.Response<T>>; + ): Promise<GotResponse<T>>; delete<T extends object = any>( url: string, options?: GotApiOptions & TOptions - ): Promise<got.Response<T>>; + ): Promise<GotResponse<T>>; reset(): void; diff --git a/test/platform/bitbucket/__snapshots__/index.spec.ts.snap b/test/platform/bitbucket/__snapshots__/index.spec.ts.snap index 75470c880fc44f8340d3fe9b0c3133f19c5f7fab..2eda4163f07e9fccc76af8b3196763feade457fd 100644 --- a/test/platform/bitbucket/__snapshots__/index.spec.ts.snap +++ b/test/platform/bitbucket/__snapshots__/index.spec.ts.snap @@ -147,7 +147,6 @@ Object { "body": "summary", "branchName": "branch", "canMerge": true, - "canRebase": true, "createdAt": "2018-07-02T07:02:25.275030+00:00", "displayNumber": "Pull Request #5", "isConflicted": false, @@ -158,12 +157,97 @@ Object { } `; +exports[`platform/bitbucket getPr() canRebase 1`] = ` +Object { + "body": "summary", + "branchName": "branch", + "canMerge": true, + "createdAt": "2018-07-02T07:02:25.275030+00:00", + "displayNumber": "Pull Request #3", + "isConflicted": false, + "isStale": false, + "number": 3, + "state": "open", + "title": "title", +} +`; + +exports[`platform/bitbucket getPr() canRebase 2`] = ` +Object { + "body": "summary", + "branchName": "branch", + "canMerge": true, + "createdAt": "2018-07-02T07:02:25.275030+00:00", + "displayNumber": "Pull Request #5", + "isConflicted": false, + "isStale": false, + "number": 5, + "state": "open", + "title": "title", +} +`; + +exports[`platform/bitbucket getPr() canRebase 3`] = ` +Object { + "body": "summary", + "branchName": "branch", + "canMerge": true, + "createdAt": "2018-07-02T07:02:25.275030+00:00", + "displayNumber": "Pull Request #5", + "isConflicted": false, + "isStale": false, + "number": 5, + "state": "open", + "title": "title", +} +`; + +exports[`platform/bitbucket getPr() canRebase 4`] = ` +Array [ + Array [ + "/2.0/repositories/some/repo/pullrequests/3", + ], + Array [ + "/2.0/repositories/some/repo/pullrequests/3/diff", + Object { + "json": false, + }, + ], + Array [ + "https://api.bitbucket.org/2.0/repositories/some/repo/pullrequests/3/commits?pagelen=1", + ], + Array [ + "/2.0/repositories/some/repo/pullrequests/5", + ], + Array [ + "/2.0/repositories/some/repo/pullrequests/5/diff", + Object { + "json": false, + }, + ], + Array [ + "https://api.bitbucket.org/2.0/repositories/some/repo/pullrequests/5/commits?pagelen=1", + ], + Array [ + "/2.0/repositories/some/repo/pullrequests/5", + ], + Array [ + "/2.0/repositories/some/repo/pullrequests/5/diff", + Object { + "json": false, + }, + ], + Array [ + "https://api.bitbucket.org/2.0/repositories/some/repo/pullrequests/5/commits?pagelen=1", + ], +] +`; + exports[`platform/bitbucket getPr() exists 1`] = ` Object { "body": "summary", "branchName": "branch", "canMerge": true, - "canRebase": true, "createdAt": "2018-07-02T07:02:25.275030+00:00", "displayNumber": "Pull Request #5", "isConflicted": false, diff --git a/test/platform/bitbucket/_fixtures/responses.js b/test/platform/bitbucket/_fixtures/responses.js index 88142ba1f4715764848122acace1c09c8ee0b51f..d4698f276bee18f07345540ec16a64f915c518f1 100644 --- a/test/platform/bitbucket/_fixtures/responses.js +++ b/test/platform/bitbucket/_fixtures/responses.js @@ -1,5 +1,5 @@ -const pr = { - id: 5, +const pr = id => ({ + id, source: { branch: { name: 'branch' } }, title: 'title', summary: { raw: 'summary' }, @@ -8,10 +8,10 @@ const pr = { links: { commits: { href: - 'https://api.bitbucket.org/2.0/repositories/some/repo/pullrequests/5/commits', + `https://api.bitbucket.org/2.0/repositories/some/repo/pullrequests/${id}/commits`, }, }, -}; +}); const issue = { id: 25, title: 'title', @@ -44,9 +44,14 @@ module.exports = { values: [issue, { ...issue, id: 26 }], }, '/2.0/repositories/some/repo/pullrequests': { - values: [pr], + values: [pr(5)], + }, + '/2.0/repositories/some/repo/pullrequests/3': pr(3), + '/2.0/repositories/some/repo/pullrequests/3/commits': { + size: 2, }, - '/2.0/repositories/some/repo/pullrequests/5': pr, + '/2.0/repositories/some/repo/pullrequests/3/diff': ' ', + '/2.0/repositories/some/repo/pullrequests/5': pr(5), '/2.0/repositories/some/repo/pullrequests/5/diff': ` diff --git a/requirements.txt b/requirements.txt index 7e08d70..f5283ca 100644 @@ -65,7 +70,8 @@ module.exports = { .trim() .replace(/^\s+/g, ''), '/2.0/repositories/some/repo/pullrequests/5/commits': { - values: [{}], + size: 1, + values: [{ author: { raw: 'Renovate Bot <bot@renovateapp.com>' } }], }, '/2.0/repositories/some/repo/pullrequests/5/comments': { values: [ diff --git a/test/platform/bitbucket/index.spec.ts b/test/platform/bitbucket/index.spec.ts index e73c17574a18a3f5fd20c2e6e70e1ab62dd42d19..b69e17bf3369bf52b4cd15997fa70e8be896f88c 100644 --- a/test/platform/bitbucket/index.spec.ts +++ b/test/platform/bitbucket/index.spec.ts @@ -392,6 +392,27 @@ describe('platform/bitbucket', () => { await initRepo(); expect(await getPr(5)).toMatchSnapshot(); }); + + it('canRebase', async () => { + expect.assertions(4); + await initRepo(); + const author = global.gitAuthor; + try { + await mocked(async () => { + expect(await bitbucket.getPr(3)).toMatchSnapshot(); + + global.gitAuthor = { email: 'bot@renovateapp.com', name: 'bot' }; + expect(await bitbucket.getPr(5)).toMatchSnapshot(); + + global.gitAuthor = { email: 'jane@example.com', name: 'jane' }; + expect(await bitbucket.getPr(5)).toMatchSnapshot(); + + expect(api.get.mock.calls).toMatchSnapshot(); + }); + } finally { + global.gitAuthor = author; + } + }); }); describe('getPrFiles()', () => {