diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 1df68058b92ab2a0cbd9f0ba5192602847f4230b..d21d69bcf3a7967a294f5edcfbf628a2ec994796 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -313,6 +313,12 @@ e.g. instead of `renovate/{{parentDir}}-`, configure the template part in `addit This setting does not change the default _onboarding_ branch name, i.e. `renovate/configure`. If you wish to change that too, you need to also configure the field `onboardingBranch` in your global bot config. +## branchPrefixOld + +Renovate uses branch names as part of its checks to see if an update PR was created previously, and already merged or ignored. +If you change `branchPrefix`, then no previously closed PRs will match, which could lead to Renovate recreating PRs in such cases. +Instead, set the old `branchPrefix` value as `branchPrefixOld` to allow Renovate to look for those branches too, and avoid this happening. + ## branchTopic This field is combined with `branchPrefix` and `additionalBranchPrefix` to form the full `branchName`. `branchName` uniqueness is important for dependency update grouping or non-grouping so be cautious about ever editing this field manually. diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index d5d3d5b6e5abe1bba790121c1476bd1d0174ea8d..20851f1168259f66679e254922dbb8da720cda0e 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -1215,6 +1215,13 @@ const options: RenovateOptions[] = [ type: 'string', default: `renovate/`, }, + { + name: 'branchPrefixOld', + description: 'Old Prefix to check for existing PRs.', + stage: 'branch', + type: 'string', + default: `renovate/`, + }, { name: 'bumpVersion', description: 'Bump the version in the package file being updated.', diff --git a/lib/config/types.ts b/lib/config/types.ts index 2c58466bd75976ba71a274e42412963e3b3966a3..de7c403a40b38b0078f3842fcfa8122d1d93bf9c 100644 --- a/lib/config/types.ts +++ b/lib/config/types.ts @@ -27,6 +27,7 @@ export interface RenovateSharedConfig { automergeStrategy?: MergeStrategy; pruneBranchAfterAutomerge?: boolean; branchPrefix?: string; + branchPrefixOld?: string; branchName?: string; manager?: string | null; commitMessage?: string; diff --git a/lib/workers/repository/update/branch/check-existing.spec.ts b/lib/workers/repository/update/branch/check-existing.spec.ts index 09d3506dedb3581d5577e4316e10d0d82da30bdd..213fe459fd67f9cdc11c3e0f2ceeed84366349ce 100644 --- a/lib/workers/repository/update/branch/check-existing.spec.ts +++ b/lib/workers/repository/update/branch/check-existing.spec.ts @@ -1,4 +1,6 @@ import { defaultConfig, partial, platform } from '../../../../../test/util'; +import { logger } from '../../../../logger'; +import type { Pr } from '../../../../modules/platform'; import { PrState } from '../../../../types'; import type { BranchConfig } from '../../../types'; import { prAlreadyExisted } from './check-existing'; @@ -37,5 +39,22 @@ describe('workers/repository/update/branch/check-existing', () => { expect(await prAlreadyExisted(config)).toEqual({ number: 12 }); expect(platform.findPr).toHaveBeenCalledTimes(1); }); + + it('returns true if second check hits', async () => { + config.branchPrefixOld = 'deps/'; + platform.findPr.mockResolvedValueOnce(null); + platform.findPr.mockResolvedValueOnce(partial<Pr>({ number: 12 })); + platform.getPr.mockResolvedValueOnce( + partial<Pr>({ + number: 12, + state: PrState.Closed, + }) + ); + expect(await prAlreadyExisted(config)).toEqual({ number: 12 }); + expect(platform.findPr).toHaveBeenCalledTimes(2); + expect(logger.debug).toHaveBeenCalledWith( + `Found closed PR with current title` + ); + }); }); }); diff --git a/lib/workers/repository/update/branch/check-existing.ts b/lib/workers/repository/update/branch/check-existing.ts index 81fb78b90472bd527ed9e5140cf17ebae7747c2c..a16a56a778c4e4d037bce1c775a37243f83ce786 100644 --- a/lib/workers/repository/update/branch/check-existing.ts +++ b/lib/workers/repository/update/branch/check-existing.ts @@ -14,11 +14,26 @@ export async function prAlreadyExisted( } logger.debug('recreateClosed is false'); // Return if same PR already existed - const pr = await platform.findPr({ + let pr = await platform.findPr({ branchName: config.branchName, prTitle: config.prTitle, state: PrState.NotOpen, }); + + if (!pr && config.branchPrefix !== config.branchPrefixOld) { + pr = await platform.findPr({ + branchName: config.branchName.replace( + config.branchPrefix, + config.branchPrefixOld + ), + prTitle: config.prTitle, + state: PrState.NotOpen, + }); + if (pr) { + logger.debug('Found closed PR with branchPrefixOld'); + } + } + if (pr) { logger.debug('Found closed PR with current title'); const prDetails = await platform.getPr(pr.number); diff --git a/lib/workers/repository/update/branch/index.spec.ts b/lib/workers/repository/update/branch/index.spec.ts index 1be57ad581c5e42da906448811259c6e6e6c7bbb..a65f9951939a694059030f67a0a934ea950517b2 100644 --- a/lib/workers/repository/update/branch/index.spec.ts +++ b/lib/workers/repository/update/branch/index.spec.ts @@ -12,6 +12,7 @@ import { MANAGER_LOCKFILE_ERROR, REPOSITORY_CHANGED, } from '../../../../constants/error-messages'; +import { logger } from '../../../../logger'; import * as _npmPostExtract from '../../../../modules/manager/npm/post-update'; import type { WriteExistingFilesResult } from '../../../../modules/manager/npm/post-update/types'; import { hashBody } from '../../../../modules/platform/pr-body'; @@ -1607,5 +1608,39 @@ describe('workers/repository/update/branch/index', () => { ).toMatchObject({ result: BranchResult.NoWork }); expect(commit.commitFilesToBranch).not.toHaveBeenCalled(); }); + + it('does nothing when branchPrefixOld/branch and its pr exists', async () => { + getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ + ...updatedPackageFiles, + }); + npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({ + artifactErrors: [], + updatedArtifacts: [], + }); + git.branchExists.mockReturnValueOnce(false); + git.branchExists.mockReturnValueOnce(true); + platform.getBranchPr.mockResolvedValueOnce( + partial<Pr>({ + sourceBranch: 'old/some-branch', + state: PrState.Open, + }) + ); + expect( + await branchWorker.processBranch({ + ...config, + branchName: 'new/some-branch', + branchPrefix: 'new/', + branchPrefixOld: 'old/', + }) + ).toEqual({ + branchExists: true, + prNo: undefined, + result: 'done', + }); + expect(logger.debug).toHaveBeenCalledWith('Found existing branch PR'); + expect(logger.debug).toHaveBeenCalledWith( + 'No package files need updating' + ); + }); }); }); diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index a7fed9f6bbe79be6f002b0c91dd8a664526ef16e..0249ea701ab9fdcb580042032931c11bea0bc845 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -83,7 +83,20 @@ export async function processBranch( let config: BranchConfig = { ...branchConfig }; logger.trace({ config }, 'processBranch()'); await checkoutBranch(config.baseBranch); - const branchExists = gitBranchExists(config.branchName); + let branchExists = gitBranchExists(config.branchName); + + if (!branchExists && config.branchPrefix !== config.branchPrefixOld) { + const branchName = config.branchName.replace( + config.branchPrefix, + config.branchPrefixOld + ); + branchExists = gitBranchExists(branchName); + if (branchExists) { + config.branchName = branchName; + logger.debug('Found existing branch with branchPrefixOld'); + } + } + let branchPr = await platform.getBranchPr(config.branchName); logger.debug(`branchExists=${branchExists}`); const dependencyDashboardCheck =