From c593ab4a680c3eada4a276ce7c32663d2056a156 Mon Sep 17 00:00:00 2001 From: RahulGautamSingh <rahultesnik@gmail.com> Date: Wed, 7 Sep 2022 20:44:51 +0530 Subject: [PATCH] feat: recreate merged PRs when updates are available (#16898) --- .../repository/update/branch/commit.ts | 2 +- .../update/branch/handle-existing.ts | 2 - .../repository/update/branch/index.spec.ts | 69 +++++++++++++++++-- lib/workers/repository/update/branch/index.ts | 57 ++++++++++++--- lib/workers/types.ts | 1 + 5 files changed, 112 insertions(+), 19 deletions(-) diff --git a/lib/workers/repository/update/branch/commit.ts b/lib/workers/repository/update/branch/commit.ts index 827d03c5ee..78ccab0d79 100644 --- a/lib/workers/repository/update/branch/commit.ts +++ b/lib/workers/repository/update/branch/commit.ts @@ -55,7 +55,7 @@ export function commitFilesToBranch( branchName: config.branchName, files: updatedFiles, message: config.commitMessage!, - force: !!config.forceCommit, + force: config.recreateMergedPr ?? !!config.forceCommit, platformCommit: !!config.platformCommit, }); } diff --git a/lib/workers/repository/update/branch/handle-existing.ts b/lib/workers/repository/update/branch/handle-existing.ts index 735ce2b242..c031be82fa 100644 --- a/lib/workers/repository/update/branch/handle-existing.ts +++ b/lib/workers/repository/update/branch/handle-existing.ts @@ -41,7 +41,5 @@ export async function handlepr(config: BranchConfig, pr: Pr): Promise<void> { await deleteBranch(config.branchName); } } - } else if (pr.state === PrState.Merged) { - logger.debug({ pr: pr.number }, 'Merged PR is blocking this branch'); } } diff --git a/lib/workers/repository/update/branch/index.spec.ts b/lib/workers/repository/update/branch/index.spec.ts index 4da4fa0c7d..e2b5dbc6c5 100644 --- a/lib/workers/repository/update/branch/index.spec.ts +++ b/lib/workers/repository/update/branch/index.spec.ts @@ -281,22 +281,81 @@ describe('workers/repository/update/branch/index', () => { expect(reuse.shouldReuseExistingBranch).toHaveBeenCalledTimes(0); }); - it('skips branch if merged PR found', async () => { - schedule.isScheduledNow.mockReturnValueOnce(false); + it('recreates pr using old branch when it exists for a merged pr', async () => { + schedule.isScheduledNow.mockReturnValueOnce(true); git.branchExists.mockReturnValue(true); checkExisting.prAlreadyExisted.mockResolvedValueOnce({ number: 13, state: PrState.Merged, } as Pr); - await branchWorker.processBranch(config); - expect(reuse.shouldReuseExistingBranch).toHaveBeenCalledTimes(0); + git.getBranchCommit.mockReturnValue('target_branch_sha'); + getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ + updatedPackageFiles: [{}], + } as PackageFilesResult); + npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({ + artifactErrors: [{}], + updatedArtifacts: [{}], + } as WriteExistingFilesResult); + platform.getBranchPr.mockResolvedValueOnce({ + state: PrState.Open, + } as Pr); + checkExisting.prAlreadyExisted.mockResolvedValueOnce({ + state: PrState.Merged, + } as Pr); + config.automerge = true; + const processBranchResult = await branchWorker.processBranch(config); + expect(processBranchResult).toEqual({ + branchExists: true, + commitSha: '123test', + prNo: undefined, + result: 'done', + }); + expect(logger.debug).not.toHaveBeenCalledWith( + { prTitle: config.prTitle }, + 'Merged PR already exists. Creating a fresh PR' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Disable automerge to prevent re-created PRs from being merged automatically.' + ); + }); + + it('recreates pr using new branch when merged pr exists but branch is deleted', async () => { + schedule.isScheduledNow.mockReturnValueOnce(true); + git.branchExists.mockReturnValue(false); + checkExisting.prAlreadyExisted.mockResolvedValueOnce({ + number: 13, + state: PrState.Merged, + } as Pr); + getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ + updatedPackageFiles: [{}], + } as PackageFilesResult); + npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({ + artifactErrors: [{}], + updatedArtifacts: [{}], + } as WriteExistingFilesResult); + git.getBranchCommit.mockReturnValue('target_branch_sha'); + platform.getBranchPr.mockResolvedValueOnce(null); + checkExisting.prAlreadyExisted.mockResolvedValueOnce({ + state: PrState.Merged, + } as Pr); + const processBranchResult = await branchWorker.processBranch(config); + expect(processBranchResult).toEqual({ + branchExists: true, + commitSha: '123test', + prNo: undefined, + result: 'pr-created', + }); + expect(logger.debug).toHaveBeenCalledWith( + { prTitle: config.prTitle }, + 'Merged PR already exists. Creating a fresh PR' + ); }); it('throws error if closed PR found', async () => { schedule.isScheduledNow.mockReturnValueOnce(false); git.branchExists.mockReturnValue(true); platform.getBranchPr.mockResolvedValueOnce({ - state: PrState.Merged, + state: PrState.Closed, } as Pr); git.isBranchModified.mockResolvedValueOnce(true); await expect(branchWorker.processBranch(config)).rejects.toThrow( diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index a0692ecae0..57b0942953 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -83,6 +83,18 @@ export interface ProcessBranchResult { commitSha?: string | null; } +interface RecreateMergedPrConfig { + recreateMergedPr: boolean; + automerge: boolean; +} + +function recreateMergedPrConfig(branchPr: Pr | null): RecreateMergedPrConfig { + return { + recreateMergedPr: !branchPr, + automerge: false, + }; +} + export async function processBranch( branchConfig: BranchConfig ): Promise<ProcessBranchResult> { @@ -114,19 +126,37 @@ export async function processBranch( const artifactErrorTopic = emojify(':warning: Artifact update problem'); try { // Check if branch already existed - const existingPr = branchPr ? undefined : await prAlreadyExisted(config); + const existingPr = await prAlreadyExisted(config); if (existingPr && !dependencyDashboardCheck) { - logger.debug( - { prTitle: config.prTitle }, - 'Closed PR already exists. Skipping branch.' - ); - await handlepr(config, existingPr); - return { - branchExists: false, - prNo: existingPr.number, - result: BranchResult.AlreadyExisted, - }; + // Recreates pr if merged pr already exists + if (existingPr.state === 'merged') { + if (config.automerge) { + logger.debug( + 'Disable automerge to prevent re-created PRs from being merged automatically.' + ); + } + + config = { ...config, ...recreateMergedPrConfig(branchPr) }; + if (!branchPr) { + logger.debug( + { prTitle: config.prTitle }, + 'Merged PR already exists. Creating a fresh PR' + ); + } + } else { + logger.debug( + { prTitle: config.prTitle }, + 'Closed PR already exists. Skipping branch.' + ); + await handlepr(config, existingPr); + return { + branchExists: false, + prNo: existingPr.number, + result: BranchResult.AlreadyExisted, + }; + } } + // istanbul ignore if if (!branchExists && config.dependencyDashboardApproval) { if (dependencyDashboardCheck) { @@ -385,6 +415,11 @@ export async function processBranch( prNo: branchPr?.number, result: BranchResult.NoWork, }; + } else if (config.recreateMergedPr) { + logger.debug( + 'Rebase branch cause the commits from old branch have already been merged.' + ); + config.reuseExistingBranch = false; } else { config = { ...config, ...(await shouldReuseExistingBranch(config)) }; } diff --git a/lib/workers/types.ts b/lib/workers/types.ts index 1da19fe992..ade345c7c3 100644 --- a/lib/workers/types.ts +++ b/lib/workers/types.ts @@ -126,6 +126,7 @@ export interface BranchConfig prNo?: number; stopUpdating?: boolean; isConflicted?: boolean; + recreateMergedPr?: boolean; branchFingerprint?: string; skipBranchUpdate?: boolean; } -- GitLab