diff --git a/lib/modules/platform/azure/__snapshots__/index.spec.ts.snap b/lib/modules/platform/azure/__snapshots__/index.spec.ts.snap index fc549c070797dd5a474cc2f1524617fd92275a47..ecd7882d4abbc23c13db948eabeb9eac5a5189a4 100644 --- a/lib/modules/platform/azure/__snapshots__/index.spec.ts.snap +++ b/lib/modules/platform/azure/__snapshots__/index.spec.ts.snap @@ -135,78 +135,6 @@ content", ] `; -exports[`modules/platform/azure/index findPr(branchName, prTitle, state) returns pr if found it all state 1`] = ` -{ - "bodyStruct": { - "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - }, - "createdAt": undefined, - "number": 1, - "pullRequestId": 1, - "sourceBranch": "branch-a", - "sourceRefName": "refs/heads/branch-a", - "state": "closed", - "status": 2, - "targetBranch": "branch-b", - "targetRefName": "refs/heads/branch-b", - "title": "branch a pr", -} -`; - -exports[`modules/platform/azure/index findPr(branchName, prTitle, state) returns pr if found it close 1`] = ` -{ - "bodyStruct": { - "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - }, - "createdAt": undefined, - "number": 1, - "pullRequestId": 1, - "sourceBranch": "branch-a", - "sourceRefName": "refs/heads/branch-a", - "state": "closed", - "status": 2, - "targetBranch": "branch-b", - "targetRefName": "refs/heads/branch-b", - "title": "branch a pr", -} -`; - -exports[`modules/platform/azure/index findPr(branchName, prTitle, state) returns pr if found it open 1`] = ` -{ - "bodyStruct": { - "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - }, - "createdAt": undefined, - "number": 1, - "pullRequestId": 1, - "sourceBranch": "branch-a", - "sourceRefName": "refs/heads/branch-a", - "state": "open", - "status": 1, - "targetBranch": "branch-b", - "targetRefName": "refs/heads/branch-b", - "title": "branch a pr", -} -`; - -exports[`modules/platform/azure/index findPr(branchName, prTitle, state) returns pr if found not open 1`] = ` -{ - "bodyStruct": { - "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - }, - "createdAt": undefined, - "number": 1, - "pullRequestId": 1, - "sourceBranch": "branch-a", - "sourceRefName": "refs/heads/branch-a", - "state": "merged", - "status": 3, - "targetBranch": "branch-b", - "targetRefName": "refs/heads/branch-b", - "title": "branch a pr", -} -`; - exports[`modules/platform/azure/index getJsonFile() supports fetch from another repo 1`] = ` [ [ diff --git a/lib/modules/platform/azure/index.spec.ts b/lib/modules/platform/azure/index.spec.ts index 29405f450156d3a1b8dcf8942942579dba7cd4fa..dbe82259e4924fd852376f42dc8b3a1fb40801ce 100644 --- a/lib/modules/platform/azure/index.spec.ts +++ b/lib/modules/platform/azure/index.spec.ts @@ -208,7 +208,7 @@ describe('modules/platform/azure/index', () => { }); }); - describe('findPr(branchName, prTitle, state)', () => { + describe('findPr(branchName, prTitle, state, targetBranch)', () => { it('returns pr if found it open', async () => { azureApi.gitApi.mockImplementationOnce( () => @@ -233,7 +233,21 @@ describe('modules/platform/azure/index', () => { prTitle: 'branch a pr', state: 'open', }); - expect(res).toMatchSnapshot(); + expect(res).toEqual({ + bodyStruct: { + hash: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + createdAt: undefined, + number: 1, + pullRequestId: 1, + sourceBranch: 'branch-a', + sourceRefName: 'refs/heads/branch-a', + state: 'open', + status: 1, + targetBranch: 'branch-b', + targetRefName: 'refs/heads/branch-b', + title: 'branch a pr', + }); }); it('returns pr if found not open', async () => { @@ -260,7 +274,21 @@ describe('modules/platform/azure/index', () => { prTitle: 'branch a pr', state: '!open', }); - expect(res).toMatchSnapshot(); + expect(res).toEqual({ + bodyStruct: { + hash: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + createdAt: undefined, + number: 1, + pullRequestId: 1, + sourceBranch: 'branch-a', + sourceRefName: 'refs/heads/branch-a', + state: 'merged', + status: 3, + targetBranch: 'branch-b', + targetRefName: 'refs/heads/branch-b', + title: 'branch a pr', + }); }); it('returns pr if found it close', async () => { @@ -287,7 +315,21 @@ describe('modules/platform/azure/index', () => { prTitle: 'branch a pr', state: 'closed', }); - expect(res).toMatchSnapshot(); + expect(res).toEqual({ + bodyStruct: { + hash: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + createdAt: undefined, + number: 1, + pullRequestId: 1, + sourceBranch: 'branch-a', + sourceRefName: 'refs/heads/branch-a', + state: 'closed', + status: 2, + targetBranch: 'branch-b', + targetRefName: 'refs/heads/branch-b', + title: 'branch a pr', + }); }); it('returns pr if found it all state', async () => { @@ -313,7 +355,117 @@ describe('modules/platform/azure/index', () => { branchName: 'branch-a', prTitle: 'branch a pr', }); - expect(res).toMatchSnapshot(); + expect(res).toEqual({ + bodyStruct: { + hash: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + createdAt: undefined, + number: 1, + pullRequestId: 1, + sourceBranch: 'branch-a', + sourceRefName: 'refs/heads/branch-a', + state: 'closed', + status: 2, + targetBranch: 'branch-b', + targetRefName: 'refs/heads/branch-b', + title: 'branch a pr', + }); + }); + + it('returns pr if found matches targetBranch', async () => { + azureApi.gitApi.mockImplementationOnce(() => + partial<IGitApi>({ + getPullRequests: jest + .fn() + .mockReturnValue([]) + .mockReturnValueOnce([ + { + pullRequestId: 1, + sourceRefName: 'refs/heads/branch-a', + targetRefName: 'refs/heads/branch-b', + title: 'branch a pr', + status: PullRequestStatus.Active, + }, + { + pullRequestId: 2, + sourceRefName: 'refs/heads/branch-a', + targetRefName: 'refs/heads/branch-c', + title: 'branch a pr', + status: PullRequestStatus.Active, + }, + ]), + getPullRequestCommits: jest.fn().mockReturnValue([]), + }) + ); + const res = await azure.findPr({ + branchName: 'branch-a', + prTitle: 'branch a pr', + state: 'open', + targetBranch: 'branch-c', + }); + expect(res).toEqual({ + bodyStruct: { + hash: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + createdAt: undefined, + number: 2, + pullRequestId: 2, + sourceBranch: 'branch-a', + sourceRefName: 'refs/heads/branch-a', + state: 'open', + status: 1, + targetBranch: 'branch-c', + targetRefName: 'refs/heads/branch-c', + title: 'branch a pr', + }); + }); + + it('returns first pr if found does not match targetBranch', async () => { + azureApi.gitApi.mockImplementationOnce(() => + partial<IGitApi>({ + getPullRequests: jest + .fn() + .mockReturnValue([]) + .mockReturnValueOnce([ + { + pullRequestId: 1, + sourceRefName: 'refs/heads/branch-a', + targetRefName: 'refs/heads/branch-b', + title: 'branch a pr', + status: PullRequestStatus.Active, + }, + { + pullRequestId: 2, + sourceRefName: 'refs/heads/branch-a', + targetRefName: 'refs/heads/branch-c', + title: 'branch a pr', + status: PullRequestStatus.Active, + }, + ]), + getPullRequestCommits: jest.fn().mockReturnValue([]), + }) + ); + const res = await azure.findPr({ + branchName: 'branch-a', + prTitle: 'branch a pr', + state: 'open', + targetBranch: 'branch-d', + }); + expect(res).toEqual({ + bodyStruct: { + hash: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + createdAt: undefined, + number: 1, + pullRequestId: 1, + sourceBranch: 'branch-a', + sourceRefName: 'refs/heads/branch-a', + state: 'open', + status: 1, + targetBranch: 'branch-b', + targetRefName: 'refs/heads/branch-b', + title: 'branch a pr', + }); }); it('catches errors', async () => { @@ -342,7 +494,7 @@ describe('modules/platform/azure/index', () => { }); }); - describe('getBranchPr(branchName)', () => { + describe('getBranchPr(branchName, targetBranch)', () => { it('should return null if no PR exists', async () => { await initRepo({ repository: 'some/repo' }); azureApi.gitApi.mockResolvedValue( @@ -364,6 +516,7 @@ describe('modules/platform/azure/index', () => { { pullRequestId: 1, sourceRefName: 'refs/heads/branch-a', + targetRefName: 'refs/heads/branch-b', title: 'branch a pr', status: 1, }, @@ -372,7 +525,7 @@ describe('modules/platform/azure/index', () => { getPullRequestLabels: jest.fn().mockResolvedValue([]), }) ); - const pr = await azure.getBranchPr('branch-a'); + const pr = await azure.getBranchPr('branch-a', 'branch-b'); expect(pr).toEqual({ bodyStruct: { hash: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', @@ -385,7 +538,8 @@ describe('modules/platform/azure/index', () => { sourceRefName: 'refs/heads/branch-a', state: 'open', status: 1, - targetBranch: undefined, + targetBranch: 'branch-b', + targetRefName: 'refs/heads/branch-b', title: 'branch a pr', }); }); diff --git a/lib/modules/platform/azure/index.ts b/lib/modules/platform/azure/index.ts index 4f4ad1f3eb6d7010a09f0997f9837c9c0d1b4dfb..e2f60bef7e021fdcc9daaeb43782b250396c3759 100644 --- a/lib/modules/platform/azure/index.ts +++ b/lib/modules/platform/azure/index.ts @@ -298,6 +298,7 @@ export async function findPr({ branchName, prTitle, state = 'all', + targetBranch, }: FindPRConfig): Promise<Pr | null> { let prsFiltered: Pr[] = []; try { @@ -330,14 +331,24 @@ export async function findPr({ if (prsFiltered.length === 0) { return null; } + if (targetBranch && prsFiltered.length > 1) { + const pr = prsFiltered.find((item) => item.targetBranch === targetBranch); + if (pr) { + return pr; + } + } return prsFiltered[0]; } -export async function getBranchPr(branchName: string): Promise<Pr | null> { - logger.debug(`getBranchPr(${branchName})`); +export async function getBranchPr( + branchName: string, + targetBranch?: string +): Promise<Pr | null> { + logger.debug(`getBranchPr(${branchName}, ${targetBranch})`); const existingPr = await findPr({ branchName, state: 'open', + targetBranch, }); return existingPr ? getPr(existingPr.number) : null; } diff --git a/lib/modules/platform/types.ts b/lib/modules/platform/types.ts index bab9f39a061528df156a3360aab2b1e34fae34f1..684005d9c944b8be42585dbb2c8849360e32a745 100644 --- a/lib/modules/platform/types.ts +++ b/lib/modules/platform/types.ts @@ -137,6 +137,7 @@ export interface FindPRConfig { prTitle?: string | null; state?: 'open' | 'closed' | '!open' | 'all'; refreshCache?: boolean; + targetBranch?: string | null; } export interface MergePRConfig { branchName?: string; @@ -219,7 +220,7 @@ export interface Platform { branchName: string, internalChecksAsSuccess: boolean ): Promise<BranchStatus>; - getBranchPr(branchName: string): Promise<Pr | null>; + getBranchPr(branchName: string, targetBranch?: string): Promise<Pr | null>; initPlatform(config: PlatformParams): Promise<PlatformResult>; filterUnavailableUsers?(users: string[]): Promise<string[]>; commitFiles?(config: CommitFilesConfig): Promise<CommitSha | null>; diff --git a/lib/workers/repository/cache.ts b/lib/workers/repository/cache.ts index 300e4e874c88d0db47efabcbc4c1f6f3ab2031a9..1f21b0c5d97d6ae9a258d0e10593b676709faf6f 100644 --- a/lib/workers/repository/cache.ts +++ b/lib/workers/repository/cache.ts @@ -73,7 +73,7 @@ async function generateBranchCache( let isBehindBase: boolean | undefined; let isConflicted: boolean | undefined; if (baseBranchSha && branchSha) { - const branchPr = await platform.getBranchPr(branchName); + const branchPr = await platform.getBranchPr(branchName, baseBranch); if (branchPr) { prNo = branchPr.number; } diff --git a/lib/workers/repository/config-migration/branch/index.ts b/lib/workers/repository/config-migration/branch/index.ts index 24651bf9422a79f9fd6f000314c997196470b889..c5827006fc5696bd4ce494f14aaa488a503b73f4 100644 --- a/lib/workers/repository/config-migration/branch/index.ts +++ b/lib/workers/repository/config-migration/branch/index.ts @@ -21,7 +21,10 @@ export async function checkConfigMigrationBranch( } const configMigrationBranch = getMigrationBranchName(config); - const branchPr = await migrationPrExists(configMigrationBranch); // handles open/autoClosed PRs + const branchPr = await migrationPrExists( + configMigrationBranch, + config.baseBranch + ); // handles open/autoClosed PRs if (!branchPr) { const commitMessageFactory = new ConfigMigrationCommitMessageFactory( @@ -33,6 +36,7 @@ export async function checkConfigMigrationBranch( branchName: configMigrationBranch, prTitle, state: 'closed', + targetBranch: config.baseBranch, }; // handles closed PR @@ -54,7 +58,8 @@ export async function checkConfigMigrationBranch( await rebaseMigrationBranch(config, migratedConfigData); if (platform.refreshPr) { const configMigrationPr = await platform.getBranchPr( - configMigrationBranch + configMigrationBranch, + config.baseBranch ); if (configMigrationPr) { await platform.refreshPr(configMigrationPr.number); @@ -71,8 +76,11 @@ export async function checkConfigMigrationBranch( return configMigrationBranch; } -export async function migrationPrExists(branchName: string): Promise<boolean> { - return !!(await platform.getBranchPr(branchName)); +export async function migrationPrExists( + branchName: string, + targetBranch?: string +): Promise<boolean> { + return !!(await platform.getBranchPr(branchName, targetBranch)); } async function handlePr(config: RenovateConfig, pr: Pr): Promise<void> { diff --git a/lib/workers/repository/config-migration/pr/index.ts b/lib/workers/repository/config-migration/pr/index.ts index 53fe6427a9d0b3821a2f0a65eabd001e81bfdbe7..3d11d730af11ce8656fa7689075c16ee70a2bd00 100644 --- a/lib/workers/repository/config-migration/pr/index.ts +++ b/lib/workers/repository/config-migration/pr/index.ts @@ -31,7 +31,7 @@ export async function ensureConfigMigrationPr( ); const prTitle = commitMessageFactory.getPrTitle(); - const existingPr = await platform.getBranchPr(branchName); + const existingPr = await platform.getBranchPr(branchName, config.baseBranch); const filename = migratedConfigData.filename; logger.debug('Filling in config migration PR template'); let prBody = `The Renovate config in this repository needs migrating. Typically this is because one or more configuration options you are using have been renamed. diff --git a/lib/workers/repository/error-config.ts b/lib/workers/repository/error-config.ts index 38ba2b17e4bfb21f0d9bca0fbff8558df334e62a..f7b62dda01d5beb07798b6a2319f613c9088264f 100644 --- a/lib/workers/repository/error-config.ts +++ b/lib/workers/repository/error-config.ts @@ -48,7 +48,10 @@ async function raiseWarningIssue( )}\`\n`; } - const pr = await platform.getBranchPr(config.onboardingBranch!); + const pr = await platform.getBranchPr( + config.onboardingBranch!, + config.baseBranch + ); if (pr?.state === 'open') { await handleOnboardingPr(pr, body); return; diff --git a/lib/workers/repository/finalize/prune.ts b/lib/workers/repository/finalize/prune.ts index 22cb17f7af56f21f75f655d4b4db0056f6877506..87cb4fb4d0cf18600e339c5de5cf2fc9a00655c3 100644 --- a/lib/workers/repository/finalize/prune.ts +++ b/lib/workers/repository/finalize/prune.ts @@ -22,6 +22,7 @@ async function cleanUpBranches( const pr = await platform.findPr({ branchName, state: 'open', + targetBranch: config.baseBranch, }); const branchIsModified = await scm.isBranchModified(branchName); if (pr) { diff --git a/lib/workers/repository/onboarding/branch/check.ts b/lib/workers/repository/onboarding/branch/check.ts index 5f7c0e2f4c78ae91040d4e91687d1a876991bb1b..540e848aac5a2c70571c4eaeb0eebf3f61872b50 100644 --- a/lib/workers/repository/onboarding/branch/check.ts +++ b/lib/workers/repository/onboarding/branch/check.ts @@ -46,6 +46,7 @@ function closedPrExists(config: RenovateConfig): Promise<Pr | null> { branchName: config.onboardingBranch!, prTitle: config.onboardingPrTitle, state: '!open', + targetBranch: config.baseBranch, }); } @@ -150,5 +151,8 @@ export async function isOnboarded(config: RenovateConfig): Promise<boolean> { export async function getOnboardingPr( config: RenovateConfig ): Promise<Pr | null> { - return await platform.getBranchPr(config.onboardingBranch!); + return await platform.getBranchPr( + config.onboardingBranch!, + config.baseBranch + ); } diff --git a/lib/workers/repository/onboarding/pr/index.ts b/lib/workers/repository/onboarding/pr/index.ts index e82ace88124301070f957c0327efd88e01f06705..d11dca54ca081ce17b46302c614123cda97fac58 100644 --- a/lib/workers/repository/onboarding/pr/index.ts +++ b/lib/workers/repository/onboarding/pr/index.ts @@ -41,7 +41,10 @@ export async function ensureOnboardingPr( logger.debug('ensureOnboardingPr()'); logger.trace({ config }); // TODO #22198 - const existingPr = await platform.getBranchPr(config.onboardingBranch!); + const existingPr = await platform.getBranchPr( + config.onboardingBranch!, + config.defaultBranch + ); if (existingPr) { // skip pr-update if branch is conflicted if ( diff --git a/lib/workers/repository/process/limits.ts b/lib/workers/repository/process/limits.ts index 2dcfe15a4a5844c14e9db49919ab54a1ea4682cd..24423a225f3938ee22406610f4bed1f5f084a1f5 100644 --- a/lib/workers/repository/process/limits.ts +++ b/lib/workers/repository/process/limits.ts @@ -49,7 +49,7 @@ export async function getConcurrentPrsRemaining( const openPrs: Pr[] = []; for (const { branchName } of branches) { try { - const pr = await platform.getBranchPr(branchName); + const pr = await platform.getBranchPr(branchName, config.baseBranch); if ( pr && pr.sourceBranch !== config.onboardingBranch && diff --git a/lib/workers/repository/update/branch/automerge.ts b/lib/workers/repository/update/branch/automerge.ts index e6242838401a45316dc91dd9866fabc755179741..3ea24e8ee7711530feb28b09ddd754cece6616ff 100644 --- a/lib/workers/repository/update/branch/automerge.ts +++ b/lib/workers/repository/update/branch/automerge.ts @@ -26,7 +26,10 @@ export async function tryBranchAutomerge( if (!isScheduledNow(config, 'automergeSchedule')) { return 'off schedule'; } - const existingPr = await platform.getBranchPr(config.branchName!); + const existingPr = await platform.getBranchPr( + config.branchName!, + config.baseBranch + ); if (existingPr) { return 'automerge aborted - PR exists'; } diff --git a/lib/workers/repository/update/branch/check-existing.ts b/lib/workers/repository/update/branch/check-existing.ts index 0892c608684e8f61f88a6b8e77ca22a15043486a..1e042068d57e08024f9497b166043d6e8fe2fca2 100644 --- a/lib/workers/repository/update/branch/check-existing.ts +++ b/lib/workers/repository/update/branch/check-existing.ts @@ -20,6 +20,7 @@ export async function prAlreadyExisted( branchName: config.branchName, prTitle: config.prTitle, state: '!open', + targetBranch: config.baseBranch, }); if (!pr && config.branchPrefix !== config.branchPrefixOld) { @@ -30,6 +31,7 @@ export async function prAlreadyExisted( ), prTitle: config.prTitle, state: '!open', + targetBranch: config.baseBranch, }); if (pr) { logger.debug('Found closed PR with branchPrefixOld'); diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index 24176316c90d1e211d6a18d35352893538e88089..54abd336fc9d51af60913e5f33bdbe29a9d7e8b3 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -132,7 +132,10 @@ export async function processBranch( } } - let branchPr = await platform.getBranchPr(config.branchName); + let branchPr = await platform.getBranchPr( + config.branchName, + config.baseBranch + ); logger.debug(`branchExists=${branchExists}`); const dependencyDashboardCheck = config.dependencyDashboardChecks?.[config.branchName]; @@ -262,6 +265,7 @@ export async function processBranch( const oldPr = await platform.findPr({ branchName: config.branchName, state: '!open', + targetBranch: config.baseBranch, }); if (!oldPr) { logger.debug('Branch has been edited but found no PR - skipping'); diff --git a/lib/workers/repository/update/pr/index.ts b/lib/workers/repository/update/pr/index.ts index 18ff29263b15389d6c168f7f78272909c7c0b9f0..705f6b0951545ba89d4cd4aae655f1fd11971943 100644 --- a/lib/workers/repository/update/pr/index.ts +++ b/lib/workers/repository/update/pr/index.ts @@ -124,7 +124,7 @@ export async function ensurePr( const dependencyDashboardCheck = config.dependencyDashboardChecks?.[config.branchName]; // Check if PR already exists - const existingPr = await platform.getBranchPr(branchName); + const existingPr = await platform.getBranchPr(branchName, config.baseBranch); const prCache = getPrCache(branchName); if (existingPr) { logger.debug('Found existing PR');