From 5f163552a9f52a66bc6c3e5fa86ea80a037bc540 Mon Sep 17 00:00:00 2001 From: RahulGautamSingh Date: Fri, 29 Dec 2023 18:40:11 +0545 Subject: [PATCH] fix(reconfigure/pr): find reconfigure pr separately (#25954) Co-authored-by: Rhys Arkins --- .../platform/bitbucket-server/index.spec.ts | 44 ++++++++++++++++++ .../platform/bitbucket-server/index.ts | 26 +++++++++++ lib/modules/platform/bitbucket/index.spec.ts | 40 +++++++++++++++++ lib/modules/platform/bitbucket/index.ts | 18 ++++++++ lib/modules/platform/gitea/index.ts | 10 ++++- lib/modules/platform/github/index.spec.ts | 45 +++++++++++++++++++ lib/modules/platform/github/index.ts | 18 ++++++++ lib/modules/platform/gitlab/index.spec.ts | 43 ++++++++++++++++++ lib/modules/platform/gitlab/index.ts | 28 ++++++++++++ lib/modules/platform/types.ts | 1 + .../repository/reconfigure/index.spec.ts | 6 +-- lib/workers/repository/reconfigure/index.ts | 8 ++-- 12 files changed, 280 insertions(+), 7 deletions(-) diff --git a/lib/modules/platform/bitbucket-server/index.spec.ts b/lib/modules/platform/bitbucket-server/index.spec.ts index f925b30d298974..117bce84ef4045 100644 --- a/lib/modules/platform/bitbucket-server/index.spec.ts +++ b/lib/modules/platform/bitbucket-server/index.spec.ts @@ -1306,6 +1306,50 @@ describe('modules/platform/bitbucket-server/index', () => { }), ).toBeNull(); }); + + it('finds pr from other authors', async () => { + const scope = await initRepo(); + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests?state=OPEN&direction=outgoing&at=refs/heads/branch&limit=1`, + ) + .reply(200, { + isLastPage: true, + values: [prMock(url, 'SOME', 'repo')], + }); + expect( + await bitbucket.findPr({ + branchName: 'branch', + state: 'open', + includeOtherAuthors: true, + }), + ).toMatchObject({ + number: 5, + sourceBranch: 'userName1/pullRequest5', + targetBranch: 'master', + title: 'title', + state: 'open', + }); + }); + + it('returns null if no pr found - (includeOtherAuthors)', async () => { + const scope = await initRepo(); + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests?state=OPEN&direction=outgoing&at=refs/heads/branch&limit=1`, + ) + .reply(200, { + isLastPage: true, + values: [], + }); + + const pr = await bitbucket.findPr({ + branchName: 'branch', + state: 'open', + includeOtherAuthors: true, + }); + expect(pr).toBeNull(); + }); }); describe('createPr()', () => { diff --git a/lib/modules/platform/bitbucket-server/index.ts b/lib/modules/platform/bitbucket-server/index.ts index bd9a0be19c34c0..aa6bcc2e7a4828 100644 --- a/lib/modules/platform/bitbucket-server/index.ts +++ b/lib/modules/platform/bitbucket-server/index.ts @@ -324,8 +324,34 @@ export async function findPr({ prTitle, state = 'all', refreshCache, + includeOtherAuthors, }: FindPRConfig): Promise { logger.debug(`findPr(${branchName}, "${prTitle!}", "${state}")`); + + if (includeOtherAuthors) { + // PR might have been created by anyone, so don't use the cached Renovate PR list + const searchParams: Record = { + state: 'OPEN', + }; + searchParams['direction'] = 'outgoing'; + searchParams['at'] = `refs/heads/${branchName}`; + + const query = getQueryString(searchParams); + const prs = await utils.accumulateValues( + `./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests?${query}`, + 'get', + {}, + 1, // only fetch the latest pr + ); + + if (!prs.length) { + logger.debug(`No PR found for branch ${branchName}`); + return null; + } + + return utils.prInfo(prs[0]); + } + const prList = await getPrList(refreshCache); const pr = prList.find(isRelevantPr(branchName, prTitle, state)); if (pr) { diff --git a/lib/modules/platform/bitbucket/index.spec.ts b/lib/modules/platform/bitbucket/index.spec.ts index 8e5318b180de1b..b4b8b8782712b9 100644 --- a/lib/modules/platform/bitbucket/index.spec.ts +++ b/lib/modules/platform/bitbucket/index.spec.ts @@ -954,6 +954,46 @@ describe('modules/platform/bitbucket/index', () => { }); expect(pr?.number).toBe(5); }); + + it('finds pr from other authors', async () => { + const scope = await initRepoMock(); + scope + .get( + '/2.0/repositories/some/repo/pullrequests?q=source.branch.name="branch"&state=open', + ) + .reply(200, { values: [pr] }); + expect( + await bitbucket.findPr({ + branchName: 'branch', + state: 'open', + includeOtherAuthors: true, + }), + ).toMatchObject({ + number: 5, + sourceBranch: 'branch', + targetBranch: 'master', + title: 'title', + state: 'open', + }); + }); + + it('returns null if no open pr exists - (includeOtherAuthors)', async () => { + const scope = await initRepoMock(); + scope + .get( + '/2.0/repositories/some/repo/pullrequests?q=source.branch.name="branch"&state=open', + ) + .reply(200, { + values: [], + }); + + const pr = await bitbucket.findPr({ + branchName: 'branch', + state: 'open', + includeOtherAuthors: true, + }); + expect(pr).toBeNull(); + }); }); describe('createPr()', () => { diff --git a/lib/modules/platform/bitbucket/index.ts b/lib/modules/platform/bitbucket/index.ts index ad94a1e098be90..9ddef12de074a9 100644 --- a/lib/modules/platform/bitbucket/index.ts +++ b/lib/modules/platform/bitbucket/index.ts @@ -301,8 +301,26 @@ export async function findPr({ branchName, prTitle, state = 'all', + includeOtherAuthors, }: FindPRConfig): Promise { logger.debug(`findPr(${branchName}, ${prTitle}, ${state})`); + + if (includeOtherAuthors) { + // PR might have been created by anyone, so don't use the cached Renovate PR list + const prs = ( + await bitbucketHttp.getJson>( + `/2.0/repositories/${config.repository}/pullrequests?q=source.branch.name="${branchName}"&state=open`, + ) + ).body.values; + + if (prs.length === 0) { + logger.debug(`No PR found for branch ${branchName}`); + return null; + } + + return utils.prInfo(prs[0]); + } + const prList = await getPrList(); const pr = prList.find( (p) => diff --git a/lib/modules/platform/gitea/index.ts b/lib/modules/platform/gitea/index.ts index 923006afbbf9c5..f26814f268a267 100644 --- a/lib/modules/platform/gitea/index.ts +++ b/lib/modules/platform/gitea/index.ts @@ -13,6 +13,7 @@ import type { BranchStatus } from '../../../types'; import { parseJson } from '../../../util/common'; import * as git from '../../../util/git'; import { setBaseUrl } from '../../../util/http/gitea'; +import { regEx } from '../../../util/regex'; import { sanitize } from '../../../util/sanitize'; import { ensureTrailingSlash } from '../../../util/url'; import { getPrBodyStruct, hashBody } from '../pr-body'; @@ -70,6 +71,7 @@ interface GiteaRepoConfig { export const id = 'gitea'; const DRAFT_PREFIX = 'WIP: '; +const reconfigurePrRegex = regEx(/reconfigure$/g); const defaults = { hostType: 'gitea', @@ -109,7 +111,12 @@ function toRenovatePR(data: PR): Pr | null { } const createdBy = data.user?.username; - if (createdBy && botUserName && createdBy !== botUserName) { + if ( + createdBy && + botUserName && + !reconfigurePrRegex.test(data.head.label) && + createdBy !== botUserName + ) { return null; } @@ -493,6 +500,7 @@ const platform: Platform = { branchName, prTitle: title, state = 'all', + includeOtherAuthors, }: FindPRConfig): Promise { logger.debug(`findPr(${branchName}, ${title!}, ${state})`); const prList = await platform.getPrList(); diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index bdcd315bfc5086..653907f8cc4be5 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -2428,6 +2428,51 @@ describe('modules/platform/github/index', () => { res = await github.findPr({ branchName: 'branch-b' }); expect(res).toBeNull(); }); + + it('finds pr from other authors', async () => { + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + scope + .get('/repos/some/repo/pulls?head=some/repo:branch&state=open') + .reply(200, [ + { + number: 1, + head: { ref: 'branch-a', repo: { full_name: 'some/repo' } }, + title: 'branch a pr', + state: 'open', + }, + ]); + await github.initRepo({ repository: 'some/repo' }); + expect( + await github.findPr({ + branchName: 'branch', + state: 'open', + includeOtherAuthors: true, + }), + ).toMatchObject({ + number: 1, + sourceBranch: 'branch-a', + sourceRepo: 'some/repo', + state: 'open', + title: 'branch a pr', + updated_at: undefined, + }); + }); + + it('returns null if no pr found - (includeOtherAuthors)', async () => { + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + scope + .get('/repos/some/repo/pulls?head=some/repo:branch&state=open') + .reply(200, []); + await github.initRepo({ repository: 'some/repo' }); + const pr = await github.findPr({ + branchName: 'branch', + state: 'open', + includeOtherAuthors: true, + }); + expect(pr).toBeNull(); + }); }); describe('createPr()', () => { diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index fcc2a2d55cd5b5..4117a7be1033ce 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -798,8 +798,26 @@ export async function findPr({ branchName, prTitle, state = 'all', + includeOtherAuthors, }: FindPRConfig): Promise { logger.debug(`findPr(${branchName}, ${prTitle}, ${state})`); + + if (includeOtherAuthors) { + const repo = config.parentRepo ?? config.repository; + // PR might have been created by anyone, so don't use the cached Renovate PR list + const response = await githubApi.getJson( + `repos/${repo}/pulls?head=${repo}:${branchName}&state=open`, + ); + + const { body: prList } = response; + if (!prList.length) { + logger.debug(`No PR found for branch ${branchName}`); + return null; + } + + return coerceRestPr(prList[0]); + } + const prList = await getPrList(); const pr = prList.find((p) => { if (p.sourceBranch !== branchName) { diff --git a/lib/modules/platform/gitlab/index.spec.ts b/lib/modules/platform/gitlab/index.spec.ts index 9a30d26c551e1b..d10b776c062508 100644 --- a/lib/modules/platform/gitlab/index.spec.ts +++ b/lib/modules/platform/gitlab/index.spec.ts @@ -1678,6 +1678,49 @@ describe('modules/platform/gitlab/index', () => { }); expect(res).toBeDefined(); }); + + it('finds pr from other authors', async () => { + httpMock + .scope(gitlabApiHost) + .get( + '/api/v4/projects/undefined/merge_requests?source_branch=branch&state=opened', + ) + .reply(200, [ + { + iid: 1, + source_branch: 'branch', + title: 'branch a pr', + state: 'opened', + }, + ]); + expect( + await gitlab.findPr({ + branchName: 'branch', + state: 'open', + includeOtherAuthors: true, + }), + ).toMatchObject({ + number: 1, + sourceBranch: 'branch', + state: 'open', + title: 'branch a pr', + }); + }); + + it('returns null if no pr found - (includeOtherAuthors)', async () => { + httpMock + .scope(gitlabApiHost) + .get( + '/api/v4/projects/undefined/merge_requests?source_branch=branch&state=opened', + ) + .reply(200, []); + const pr = await gitlab.findPr({ + branchName: 'branch', + state: 'open', + includeOtherAuthors: true, + }); + expect(pr).toBeNull(); + }); }); async function initPlatform(gitlabVersion: string) { diff --git a/lib/modules/platform/gitlab/index.ts b/lib/modules/platform/gitlab/index.ts index be38419752c8f2..de5137f28e7441 100644 --- a/lib/modules/platform/gitlab/index.ts +++ b/lib/modules/platform/gitlab/index.ts @@ -863,8 +863,36 @@ export async function findPr({ branchName, prTitle, state = 'all', + includeOtherAuthors, }: FindPRConfig): Promise { logger.debug(`findPr(${branchName}, ${prTitle!}, ${state})`); + + if (includeOtherAuthors) { + // PR might have been created by anyone, so don't use the cached Renovate MR list + const response = await gitlabApi.getJson( + `projects/${config.repository}/merge_requests?source_branch=${branchName}&state=opened`, + ); + + const { body: mrList } = response; + if (!mrList.length) { + logger.debug(`No MR found for branch ${branchName}`); + return null; + } + + // return the latest merge request + const mr = mrList[0]; + + // only pass necessary info + const pr: GitlabPr = { + sourceBranch: mr.source_branch, + number: mr.iid, + state: 'open', + title: mr.title, + }; + + return massagePr(pr); + } + const prList = await getPrList(); return ( prList.find( diff --git a/lib/modules/platform/types.ts b/lib/modules/platform/types.ts index 78587912fe026f..f0209f6d23334d 100644 --- a/lib/modules/platform/types.ts +++ b/lib/modules/platform/types.ts @@ -143,6 +143,7 @@ export interface FindPRConfig { state?: 'open' | 'closed' | '!open' | 'all'; refreshCache?: boolean; targetBranch?: string | null; + includeOtherAuthors?: boolean; } export interface MergePRConfig { branchName?: string; diff --git a/lib/workers/repository/reconfigure/index.spec.ts b/lib/workers/repository/reconfigure/index.spec.ts index f7c97269ef01ef..4729c1e9ef1561 100644 --- a/lib/workers/repository/reconfigure/index.spec.ts +++ b/lib/workers/repository/reconfigure/index.spec.ts @@ -8,6 +8,7 @@ import { platform, scm, } from '../../../../test/util'; +import { GlobalConfig } from '../../../config/global'; import { logger } from '../../../logger'; import type { Pr } from '../../../modules/platform/types'; import * as _cache from '../../../util/cache/repository'; @@ -39,8 +40,8 @@ describe('workers/repository/reconfigure/index', () => { cache.getCache.mockReturnValue({}); git.getBranchCommit.mockReturnValue('sha' as LongCommitSha); fs.readLocalFile.mockResolvedValue(null); - platform.getBranchPr.mockResolvedValue(null); platform.getBranchStatusCheck.mockResolvedValue(null); + GlobalConfig.reset(); }); it('no effect on repo with no reconfigure branch', async () => { @@ -126,7 +127,7 @@ describe('workers/repository/reconfigure/index', () => { "enabledManagers": ["docker"] } `); - platform.getBranchPr.mockResolvedValueOnce(mock({ number: 1 })); + platform.findPr.mockResolvedValueOnce(mock({ number: 1 })); await validateReconfigureBranch(config); expect(logger.debug).toHaveBeenCalledWith( { errors: expect.any(String) }, @@ -229,7 +230,6 @@ describe('workers/repository/reconfigure/index', () => { "enabledManagers": ["npm",] } `); - platform.getBranchPr.mockResolvedValueOnce(mock({ number: 1 })); await validateReconfigureBranch(config); expect(platform.setBranchStatus).toHaveBeenCalledWith({ branchName: 'prefix/reconfigure', diff --git a/lib/workers/repository/reconfigure/index.ts b/lib/workers/repository/reconfigure/index.ts index 79a7e9f71f842c..0908bbf82c5e83 100644 --- a/lib/workers/repository/reconfigure/index.ts +++ b/lib/workers/repository/reconfigure/index.ts @@ -161,10 +161,11 @@ export async function validateReconfigureBranch( ); // add comment to reconfigure PR if it exists - const branchPr = await platform.getBranchPr( + const branchPr = await platform.findPr({ branchName, - config.defaultBranch, - ); + state: 'open', + includeOtherAuthors: true, + }); if (branchPr) { let body = `There is an error with this repository's Renovate configuration that needs to be fixed.\n\n`; body += `Location: \`${configFileName}\`\n`; @@ -179,6 +180,7 @@ export async function validateReconfigureBranch( content: body, }); } + await setBranchStatus(branchName, 'Validation Failed', 'red', context); setReconfigureBranchCache(branchSha, false); await scm.checkoutBranch(config.baseBranch!);