diff --git a/lib/modules/platform/types.ts b/lib/modules/platform/types.ts index 188e50b125c0417bd9e11902eb79bd1b83432dce..57917c8ca690e36983a35ce12019e37120e7488f 100644 --- a/lib/modules/platform/types.ts +++ b/lib/modules/platform/types.ts @@ -48,6 +48,7 @@ export interface RepoParams { export interface PrDebugData { createdInVer: string; updatedInVer: string; + targetBranch: string; } export interface PrBodyStruct { diff --git a/lib/workers/repository/update/branch/index.spec.ts b/lib/workers/repository/update/branch/index.spec.ts index 0288741eab8ae73b3f966a003fabfc6a9da3332e..7609eab2d2b86b43ffdff53bcc2f4b6b63fffe4c 100644 --- a/lib/workers/repository/update/branch/index.spec.ts +++ b/lib/workers/repository/update/branch/index.spec.ts @@ -20,7 +20,12 @@ import type { ArtifactError, WriteExistingFilesResult, } from '../../../../modules/manager/npm/post-update/types'; -import type { EnsureCommentConfig, Pr } from '../../../../modules/platform'; +import type { + EnsureCommentConfig, + Pr, + PrBodyStruct, + PrDebugData, +} from '../../../../modules/platform'; import { hashBody } from '../../../../modules/platform/pr-body'; import * as _repoCache from '../../../../util/cache/repository'; import * as _exec from '../../../../util/exec'; @@ -376,7 +381,10 @@ describe('workers/repository/update/branch/index', () => { it('skips branch if tagretBranch of update PR is changed by user', async () => { const pr = partial<Pr>({ state: 'open', - targetBranch: 'old_base', + targetBranch: 'new_base', + bodyStruct: partial<PrBodyStruct>({ + debugData: partial<PrDebugData>({ targetBranch: 'old_base' }), + }), }); const ensureCommentConfig = partial<EnsureCommentConfig>({ number: pr.number, @@ -386,7 +394,7 @@ describe('workers/repository/update/branch/index', () => { scm.branchExists.mockResolvedValue(true); scm.isBranchModified.mockResolvedValueOnce(false); platform.getBranchPr.mockResolvedValueOnce(pr); - config.baseBranch = 'new_base'; + config.baseBranch = 'old_base'; const res = await branchWorker.processBranch(config); expect(res).toEqual({ branchExists: true, @@ -439,10 +447,13 @@ describe('workers/repository/update/branch/index', () => { partial<Pr>({ state: 'open', targetBranch: 'v6', + bodyStruct: partial<PrBodyStruct>({ + debugData: partial<PrDebugData>({ targetBranch: 'master' }), + }), }) ); - scm.isBranchModified.mockResolvedValueOnce(false); config.baseBranch = 'master'; + scm.isBranchModified.mockResolvedValueOnce(false); const res = await branchWorker.processBranch(config); expect(res).toEqual({ branchExists: true, @@ -1049,6 +1060,38 @@ describe('workers/repository/update/branch/index', () => { }); }); + it('rebases branch onto new basebranch if baseBranch changed by user', async () => { + const pr = partial<Pr>({ + state: 'open', + targetBranch: 'old_base', + bodyStruct: partial<PrBodyStruct>({ + debugData: partial<PrDebugData>({ targetBranch: 'old_base' }), + }), + }); + getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce( + partial<PackageFilesResult>({ + updatedPackageFiles: [partial<FileChange>()], + }) + ); + npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({ + artifactErrors: [partial<ArtifactError>()], + updatedArtifacts: [partial<FileChange>()], + }); + scm.branchExists.mockResolvedValue(true); + commit.commitFilesToBranch.mockResolvedValueOnce(null); + platform.getBranchPr.mockResolvedValueOnce(pr); + await branchWorker.processBranch({ + ...config, + baseBranch: 'new_base', + skipBranchUpdate: true, + }); + expect(logger.debug).toHaveBeenCalledWith( + 'Base branch changed by user, rebasing the branch onto new base' + ); + expect(commit.commitFilesToBranch).toHaveBeenCalled(); + expect(prWorker.ensurePr).toHaveBeenCalledTimes(1); + }); + it('swallows pr errors', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce( partial<PackageFilesResult>({ diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index 077e15eacb9b9b98ff47125f49f39c77ad2742fa..81c8c7b758752a9d7bd7347e476b3a641c74bbc9 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -65,6 +65,14 @@ async function deleteBranchSilently(branchName: string): Promise<void> { } } +function userChangedTargetBranch(pr: Pr): boolean { + const oldTargetBranch = pr.bodyStruct?.debugData?.targetBranch; + if (oldTargetBranch && pr.targetBranch) { + return pr.targetBranch !== oldTargetBranch; + } + return false; +} + export interface ProcessBranchResult { branchExists: boolean; updatesVerified?: boolean; @@ -199,11 +207,7 @@ export async function processBranch( ); throw new Error(REPOSITORY_CHANGED); } - if ( - branchIsModified || - (branchPr.targetBranch && - branchPr.targetBranch !== branchConfig.baseBranch) - ) { + if (branchIsModified || userChangedTargetBranch(branchPr)) { logger.debug(`PR has been edited, PrNo:${branchPr.number}`); await handleModifiedPr(config, branchPr); if (!(dependencyDashboardCheck || config.rebaseRequested)) { @@ -382,6 +386,17 @@ export async function processBranch( prNo: branchPr?.number, result: 'no-work', }; + } + // if the base branch has been changed by user in renovate config, rebase onto the new baseBranch + // we have already confirmed earlier that branch isn't modified, so its safe to use targetBranch here + else if ( + branchPr?.targetBranch && + branchPr.targetBranch !== config.baseBranch + ) { + logger.debug( + 'Base branch changed by user, rebasing the branch onto new base' + ); + config.reuseExistingBranch = false; } else { config = { ...config, ...(await shouldReuseExistingBranch(config)) }; } diff --git a/lib/workers/repository/update/pr/body/index.spec.ts b/lib/workers/repository/update/pr/body/index.spec.ts index 46004205fe594e7dbd272ae9b8ecdd9264a85272..207fab4f0735f801f51cc302ffa52e7b11050eea 100644 --- a/lib/workers/repository/update/pr/body/index.spec.ts +++ b/lib/workers/repository/update/pr/body/index.spec.ts @@ -62,6 +62,7 @@ describe('workers/repository/update/pr/body/index', () => { debugData: { updatedInVer: '1.2.3', createdInVer: '1.2.3', + targetBranch: 'base', }, } ); @@ -91,6 +92,7 @@ describe('workers/repository/update/pr/body/index', () => { debugData: { updatedInVer: '1.2.3', createdInVer: '1.2.3', + targetBranch: 'base', }, } ); @@ -128,6 +130,7 @@ describe('workers/repository/update/pr/body/index', () => { debugData: { updatedInVer: '1.2.3', createdInVer: '1.2.3', + targetBranch: 'base', }, } ); @@ -155,6 +158,7 @@ describe('workers/repository/update/pr/body/index', () => { debugData: { updatedInVer: '1.2.3', createdInVer: '1.2.3', + targetBranch: 'base', }, } ); @@ -178,6 +182,7 @@ describe('workers/repository/update/pr/body/index', () => { debugData: { updatedInVer: '1.2.3', createdInVer: '1.2.3', + targetBranch: 'base', }, } ); @@ -199,6 +204,7 @@ describe('workers/repository/update/pr/body/index', () => { debugData: { updatedInVer: '1.2.3', createdInVer: '1.2.3', + targetBranch: 'base', }, } ); @@ -247,6 +253,7 @@ describe('workers/repository/update/pr/body/index', () => { debugData: { updatedInVer: '1.2.3', createdInVer: '1.2.3', + targetBranch: 'base', }, } ); diff --git a/lib/workers/repository/update/pr/index.ts b/lib/workers/repository/update/pr/index.ts index bc3c9b2adebccbdd05bc7c8fedb55ee1a864fa77..99cd73a5d02f5a1b49106dc372419926158b80f7 100644 --- a/lib/workers/repository/update/pr/index.ts +++ b/lib/workers/repository/update/pr/index.ts @@ -72,6 +72,7 @@ export interface ResultWithoutPr { export type EnsurePrResult = ResultWithPr | ResultWithoutPr; export function updatePrDebugData( + targetBranch: string, debugData: PrDebugData | undefined ): PrDebugData { const createdByRenovateVersion = debugData?.createdInVer ?? pkg.version; @@ -79,6 +80,7 @@ export function updatePrDebugData( return { createdInVer: createdByRenovateVersion, updatedInVer: updatedByRenovateVersion, + targetBranch, }; } @@ -306,7 +308,10 @@ export async function ensurePr( } const prBody = getPrBody(config, { - debugData: updatePrDebugData(existingPr?.bodyStruct?.debugData), + debugData: updatePrDebugData( + config.baseBranch, + existingPr?.bodyStruct?.debugData + ), }); try {