From 77dfd208f78dc3aabfd0223d1d5dee1dec7b5d88 Mon Sep 17 00:00:00 2001 From: David Straub Date: Tue, 9 Jan 2024 16:01:53 -0500 Subject: [PATCH 01/17] feat(platform/github): re-attempt platform automerge --- .../platform/bitbucket-server/index.ts | 7 +- lib/modules/platform/github/index.spec.ts | 87 ++++++++++++++++++- lib/modules/platform/github/index.ts | 23 +++++ lib/modules/platform/types.ts | 6 +- .../config-migration/branch/index.ts | 2 +- .../repository/onboarding/branch/index.ts | 2 +- lib/workers/repository/update/branch/index.ts | 7 +- 7 files changed, 126 insertions(+), 8 deletions(-) diff --git a/lib/modules/platform/bitbucket-server/index.ts b/lib/modules/platform/bitbucket-server/index.ts index aa6bcc2e7a4828..acd63da39904c8 100644 --- a/lib/modules/platform/bitbucket-server/index.ts +++ b/lib/modules/platform/bitbucket-server/index.ts @@ -33,6 +33,7 @@ import type { PlatformParams, PlatformResult, Pr, + RefreshPrConfig, RepoParams, RepoResult, UpdatePrConfig, @@ -373,11 +374,13 @@ export async function getBranchPr(branchName: string): Promise { } // istanbul ignore next -export async function refreshPr(number: number): Promise { +export async function refreshPr({ + number: prNo, +}: RefreshPrConfig): Promise { // wait for pr change propagation await setTimeout(1000); // refresh cache - await getPr(number, true); + await getPr(prNo, true); } async function getStatus( diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index 653907f8cc4be5..4c6407316a59c4 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -17,7 +17,12 @@ import * as _hostRules from '../../../util/host-rules'; import { setBaseUrl } from '../../../util/http/github'; import { toBase64 } from '../../../util/string'; import { hashBody } from '../pr-body'; -import type { CreatePRConfig, RepoParams, UpdatePrConfig } from '../types'; +import type { + CreatePRConfig, + RefreshPrConfig, + RepoParams, + UpdatePrConfig, +} from '../types'; import * as branch from './branch'; import type { ApiPageCache, GhRestPr } from './types'; import * as github from '.'; @@ -3111,6 +3116,86 @@ describe('modules/platform/github/index', () => { }); }); + describe('refreshPr(prNo, platformOptions)', () => { + const getPrResp = { + number: 123, + node_id: 'abcd', + head: { repo: { full_name: 'some/repo' } }, + }; + + const graphqlAutomergeResp = { + data: { + enablePullRequestAutoMerge: { + pullRequest: { + number: 123, + }, + }, + }, + }; + + const pr: RefreshPrConfig = { + number: 123, + platformOptions: { usePlatformAutomerge: true }, + }; + + const mockScope = async (repoOpts: any = {}): Promise => { + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo', repoOpts); + scope.get('/repos/some/repo/pulls/123').reply(200, getPrResp); + await github.initRepo({ repository: 'some/repo' }); + return scope; + }; + + const graphqlGetRepo = { + method: 'POST', + url: 'https://api.github.com/graphql', + graphql: { query: { repository: {} } }, + }; + + const restGetPr = { + method: 'GET', + url: 'https://api.github.com/repos/some/repo/pulls/123', + }; + + const graphqlAutomerge = { + method: 'POST', + url: 'https://api.github.com/graphql', + graphql: { + mutation: { + __vars: { + $pullRequestId: 'ID!', + $mergeMethod: 'PullRequestMergeMethod!', + }, + enablePullRequestAutoMerge: { + __args: { + input: { + pullRequestId: '$pullRequestId', + mergeMethod: '$mergeMethod', + }, + }, + }, + }, + variables: { + pullRequestId: 'abcd', + mergeMethod: 'REBASE', + }, + }, + }; + + it('should set automatic merge', async () => { + const scope = await mockScope(); + scope.post('/graphql').reply(200, graphqlAutomergeResp); + + await expect(github.refreshPr(pr)).toResolve(); + + expect(httpMock.getTrace()).toMatchObject([ + graphqlGetRepo, + restGetPr, + graphqlAutomerge, + ]); + }); + }); + describe('mergePr(prNo)', () => { it('should merge the PR', async () => { const scope = httpMock.scope(githubApiHost); diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index 4117a7be1033ce..1d6c94c5f0d452 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -56,6 +56,7 @@ import type { PlatformParams, PlatformPrOptions, PlatformResult, + RefreshPrConfig, RepoParams, RepoResult, UpdatePrConfig, @@ -1731,6 +1732,28 @@ export async function updatePr({ } } +export async function refreshPr({ + number: prNo, + platformOptions, +}: RefreshPrConfig): Promise { + try { + const { body: ghPr } = await githubApi.getJson( + `repos/${config.parentRepo ?? config.repository}/pulls/${prNo}`, + ); + const result = coerceRestPr(ghPr); + const { number, node_id } = result; + + await tryPrAutomerge(number, node_id, platformOptions); + + logger.debug(`PR refreshed...prNo: ${prNo}`); + } catch (err) /* istanbul ignore next */ { + if (err instanceof ExternalHostError) { + throw err; + } + logger.warn({ err }, 'Error refreshing PR'); + } +} + export async function mergePr({ branchName, id: prNo, diff --git a/lib/modules/platform/types.ts b/lib/modules/platform/types.ts index f0209f6d23334d..fdf09fb4dc94c4 100644 --- a/lib/modules/platform/types.ts +++ b/lib/modules/platform/types.ts @@ -121,6 +121,10 @@ export interface UpdatePrConfig { state?: 'open' | 'closed'; targetBranch?: string; } +export interface RefreshPrConfig { + number: number; + platformOptions?: PlatformPrOptions; +} export interface EnsureIssueConfig { title: string; reuseTitle?: string; @@ -222,7 +226,7 @@ export interface Platform { ensureComment(ensureComment: EnsureCommentConfig): Promise; getPr(number: number): Promise; findPr(findPRConfig: FindPRConfig): Promise; - refreshPr?(number: number): Promise; + refreshPr?(prConfig: RefreshPrConfig): Promise; getBranchStatus( branchName: string, internalChecksAsSuccess: boolean, diff --git a/lib/workers/repository/config-migration/branch/index.ts b/lib/workers/repository/config-migration/branch/index.ts index 2862c8ef507a07..385c5fc66c73f9 100644 --- a/lib/workers/repository/config-migration/branch/index.ts +++ b/lib/workers/repository/config-migration/branch/index.ts @@ -62,7 +62,7 @@ export async function checkConfigMigrationBranch( config.baseBranch, ); if (configMigrationPr) { - await platform.refreshPr(configMigrationPr.number); + await platform.refreshPr({ number: configMigrationPr.number }); } } } else { diff --git a/lib/workers/repository/onboarding/branch/index.ts b/lib/workers/repository/onboarding/branch/index.ts index 488d75a0c6ea77..f2ef43834f3c05 100644 --- a/lib/workers/repository/onboarding/branch/index.ts +++ b/lib/workers/repository/onboarding/branch/index.ts @@ -69,7 +69,7 @@ export async function checkOnboardingBranch( } // istanbul ignore if if (platform.refreshPr) { - await platform.refreshPr(onboardingPr.number); + await platform.refreshPr({ number: onboardingPr.number }); } } if (config.onboardingRebaseCheckbox) { diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index 050ce7403f71e6..f5800695bb9e3b 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -36,7 +36,7 @@ import * as template from '../../../../util/template'; import { isLimitReached } from '../../../global/limits'; import type { BranchConfig, BranchResult, PrBlockedBy } from '../../../types'; import { embedChangelogs } from '../../changelog'; -import { ensurePr } from '../pr'; +import { ensurePr, getPlatformPrOptions } from '../pr'; import { checkAutoMerge } from '../pr/automerge'; import { setArtifactErrorStatus } from './artifacts'; import { tryBranchAutomerge } from './automerge'; @@ -572,7 +572,10 @@ export async function processBranch( } // istanbul ignore if if (branchPr && platform.refreshPr) { - await platform.refreshPr(branchPr.number); + await platform.refreshPr({ + number: branchPr.number, + platformOptions: getPlatformPrOptions(config), + }); } if (!commitSha && !branchExists) { return { From 5667cf473032fc6f6d1c4051f644054a1e060349 Mon Sep 17 00:00:00 2001 From: David Straub Date: Wed, 10 Jan 2024 09:51:02 -0500 Subject: [PATCH 02/17] refactor(platform/github): use a new function, reattemptPlatformAutomerge, instead --- lib/modules/platform/bitbucket-server/index.ts | 7 ++----- lib/modules/platform/github/index.spec.ts | 8 ++++---- lib/modules/platform/github/index.ts | 6 +++--- lib/modules/platform/types.ts | 7 +++++-- .../config-migration/branch/index.ts | 2 +- .../repository/onboarding/branch/index.ts | 2 +- lib/workers/repository/update/branch/index.ts | 18 ++++++++++++------ 7 files changed, 28 insertions(+), 22 deletions(-) diff --git a/lib/modules/platform/bitbucket-server/index.ts b/lib/modules/platform/bitbucket-server/index.ts index acd63da39904c8..aa6bcc2e7a4828 100644 --- a/lib/modules/platform/bitbucket-server/index.ts +++ b/lib/modules/platform/bitbucket-server/index.ts @@ -33,7 +33,6 @@ import type { PlatformParams, PlatformResult, Pr, - RefreshPrConfig, RepoParams, RepoResult, UpdatePrConfig, @@ -374,13 +373,11 @@ export async function getBranchPr(branchName: string): Promise { } // istanbul ignore next -export async function refreshPr({ - number: prNo, -}: RefreshPrConfig): Promise { +export async function refreshPr(number: number): Promise { // wait for pr change propagation await setTimeout(1000); // refresh cache - await getPr(prNo, true); + await getPr(number, true); } async function getStatus( diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index 4c6407316a59c4..13b8ab2c111dda 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -19,7 +19,7 @@ import { toBase64 } from '../../../util/string'; import { hashBody } from '../pr-body'; import type { CreatePRConfig, - RefreshPrConfig, + ReattemptPlatformAutomergeConfig, RepoParams, UpdatePrConfig, } from '../types'; @@ -3116,7 +3116,7 @@ describe('modules/platform/github/index', () => { }); }); - describe('refreshPr(prNo, platformOptions)', () => { + describe('reattemptPlatformAutomerge(prNo, platformOptions)', () => { const getPrResp = { number: 123, node_id: 'abcd', @@ -3133,7 +3133,7 @@ describe('modules/platform/github/index', () => { }, }; - const pr: RefreshPrConfig = { + const pr: ReattemptPlatformAutomergeConfig = { number: 123, platformOptions: { usePlatformAutomerge: true }, }; @@ -3186,7 +3186,7 @@ describe('modules/platform/github/index', () => { const scope = await mockScope(); scope.post('/graphql').reply(200, graphqlAutomergeResp); - await expect(github.refreshPr(pr)).toResolve(); + await expect(github.reattemptPlatformAutomerge(pr)).toResolve(); expect(httpMock.getTrace()).toMatchObject([ graphqlGetRepo, diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index 1d6c94c5f0d452..f1cd719fbd2773 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -56,7 +56,7 @@ import type { PlatformParams, PlatformPrOptions, PlatformResult, - RefreshPrConfig, + ReattemptPlatformAutomergeConfig, RepoParams, RepoResult, UpdatePrConfig, @@ -1732,10 +1732,10 @@ export async function updatePr({ } } -export async function refreshPr({ +export async function reattemptPlatformAutomerge({ number: prNo, platformOptions, -}: RefreshPrConfig): Promise { +}: ReattemptPlatformAutomergeConfig): Promise { try { const { body: ghPr } = await githubApi.getJson( `repos/${config.parentRepo ?? config.repository}/pulls/${prNo}`, diff --git a/lib/modules/platform/types.ts b/lib/modules/platform/types.ts index fdf09fb4dc94c4..0b80069d899962 100644 --- a/lib/modules/platform/types.ts +++ b/lib/modules/platform/types.ts @@ -121,7 +121,7 @@ export interface UpdatePrConfig { state?: 'open' | 'closed'; targetBranch?: string; } -export interface RefreshPrConfig { +export interface ReattemptPlatformAutomergeConfig { number: number; platformOptions?: PlatformPrOptions; } @@ -226,7 +226,10 @@ export interface Platform { ensureComment(ensureComment: EnsureCommentConfig): Promise; getPr(number: number): Promise; findPr(findPRConfig: FindPRConfig): Promise; - refreshPr?(prConfig: RefreshPrConfig): Promise; + refreshPr?(number: number): Promise; + reattemptPlatformAutomerge?( + prConfig: ReattemptPlatformAutomergeConfig, + ): Promise; getBranchStatus( branchName: string, internalChecksAsSuccess: boolean, diff --git a/lib/workers/repository/config-migration/branch/index.ts b/lib/workers/repository/config-migration/branch/index.ts index 385c5fc66c73f9..2862c8ef507a07 100644 --- a/lib/workers/repository/config-migration/branch/index.ts +++ b/lib/workers/repository/config-migration/branch/index.ts @@ -62,7 +62,7 @@ export async function checkConfigMigrationBranch( config.baseBranch, ); if (configMigrationPr) { - await platform.refreshPr({ number: configMigrationPr.number }); + await platform.refreshPr(configMigrationPr.number); } } } else { diff --git a/lib/workers/repository/onboarding/branch/index.ts b/lib/workers/repository/onboarding/branch/index.ts index f2ef43834f3c05..488d75a0c6ea77 100644 --- a/lib/workers/repository/onboarding/branch/index.ts +++ b/lib/workers/repository/onboarding/branch/index.ts @@ -69,7 +69,7 @@ export async function checkOnboardingBranch( } // istanbul ignore if if (platform.refreshPr) { - await platform.refreshPr({ number: onboardingPr.number }); + await platform.refreshPr(onboardingPr.number); } } if (config.onboardingRebaseCheckbox) { diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index f5800695bb9e3b..b5bb751c65dacc 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -570,12 +570,18 @@ export async function processBranch( await scm.checkoutBranch(config.baseBranch); updatesVerified = true; } - // istanbul ignore if - if (branchPr && platform.refreshPr) { - await platform.refreshPr({ - number: branchPr.number, - platformOptions: getPlatformPrOptions(config), - }); + + if (branchPr) { + if (platform.reattemptPlatformAutomerge) { + await platform.reattemptPlatformAutomerge({ + number: branchPr.number, + platformOptions: getPlatformPrOptions(config), + }); + } + // istanbul ignore if + if (platform.refreshPr) { + await platform.refreshPr(branchPr.number); + } } if (!commitSha && !branchExists) { return { From 206bdf9ca444605e24a1d3a9b38aec510bd3b144 Mon Sep 17 00:00:00 2001 From: David Straub Date: Wed, 10 Jan 2024 10:12:49 -0500 Subject: [PATCH 03/17] refactor(platform/github): less renaming --- lib/modules/platform/github/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index f1cd719fbd2773..38802f6f808cef 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -1733,19 +1733,19 @@ export async function updatePr({ } export async function reattemptPlatformAutomerge({ - number: prNo, + number, platformOptions, }: ReattemptPlatformAutomergeConfig): Promise { try { const { body: ghPr } = await githubApi.getJson( - `repos/${config.parentRepo ?? config.repository}/pulls/${prNo}`, + `repos/${config.parentRepo ?? config.repository}/pulls/${number}`, ); const result = coerceRestPr(ghPr); - const { number, node_id } = result; + const { node_id } = result; await tryPrAutomerge(number, node_id, platformOptions); - logger.debug(`PR refreshed...prNo: ${prNo}`); + logger.debug(`PR refreshed...prNo: ${number}`); } catch (err) /* istanbul ignore next */ { if (err instanceof ExternalHostError) { throw err; From 967e9bbbe7922cd3365f013201a8ea52fc76aed2 Mon Sep 17 00:00:00 2001 From: David Straub Date: Wed, 10 Jan 2024 10:17:51 -0500 Subject: [PATCH 04/17] docs(platformAutomerge): address new GitHub Merge Queue behavior --- docs/usage/configuration-options.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index bb4a74e298a9ad..1701b24e7b9d51 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -2978,8 +2978,10 @@ If enabled Renovate will pin Docker images or GitHub Actions by means of their S If you have enabled `automerge` and set `automergeType=pr` in the Renovate config, then leaving `platformAutomerge` as `true` speeds up merging via the platform's native automerge functionality. -Renovate tries platform-native automerge only when it initially creates the PR. +On most platforms, Renovate tries platform-native automerge only when it initially creates the PR. Any PR that is being updated will be automerged with the Renovate-based automerge. +On GitHub, Renovate re-enables the PR for automerge whenever it's rebased, so that merge conflicts enocuntered in the Merge Queue can be resolved. + `platformAutomerge` will configure PRs to be merged after all (if any) branch policies have been met. This option is available for Azure, Gitea, GitHub and GitLab. From c69d58ea4fee2b7333a2a434e7fe2adcf55b03aa Mon Sep 17 00:00:00 2001 From: David Straub Date: Wed, 10 Jan 2024 10:19:55 -0500 Subject: [PATCH 05/17] docs(platformAutomerge): rm extra blank line --- docs/usage/configuration-options.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 1701b24e7b9d51..ba422f7cb8a342 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -2982,7 +2982,6 @@ On most platforms, Renovate tries platform-native automerge only when it initial Any PR that is being updated will be automerged with the Renovate-based automerge. On GitHub, Renovate re-enables the PR for automerge whenever it's rebased, so that merge conflicts enocuntered in the Merge Queue can be resolved. - `platformAutomerge` will configure PRs to be merged after all (if any) branch policies have been met. This option is available for Azure, Gitea, GitHub and GitLab. It falls back to Renovate-based automerge if the platform-native automerge is not available. From f3417d0068ee254a5edeb94709251e25323587b6 Mon Sep 17 00:00:00 2001 From: David Straub Date: Wed, 10 Jan 2024 10:52:31 -0500 Subject: [PATCH 06/17] docs(platformAutomerge): fix typo --- docs/usage/configuration-options.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index ba422f7cb8a342..cbbe64a3c64f3f 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -2980,7 +2980,7 @@ If you have enabled `automerge` and set `automergeType=pr` in the Renovate confi On most platforms, Renovate tries platform-native automerge only when it initially creates the PR. Any PR that is being updated will be automerged with the Renovate-based automerge. -On GitHub, Renovate re-enables the PR for automerge whenever it's rebased, so that merge conflicts enocuntered in the Merge Queue can be resolved. +On GitHub, Renovate re-enables the PR for automerge whenever it's rebased, so that merge conflicts encountered in the Merge Queue can be resolved. `platformAutomerge` will configure PRs to be merged after all (if any) branch policies have been met. This option is available for Azure, Gitea, GitHub and GitLab. From aa5917847bb4232c1f41e6d2ef2e27be7a6f8f6d Mon Sep 17 00:00:00 2001 From: David Straub Date: Wed, 10 Jan 2024 13:27:42 -0500 Subject: [PATCH 07/17] fix(platform/github): correct inconsistent log message --- lib/modules/platform/github/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index 38802f6f808cef..a1879248fdea4b 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -1745,12 +1745,12 @@ export async function reattemptPlatformAutomerge({ await tryPrAutomerge(number, node_id, platformOptions); - logger.debug(`PR refreshed...prNo: ${number}`); + logger.debug(`PR platform automerge re-attempted...prNo: ${number}`); } catch (err) /* istanbul ignore next */ { if (err instanceof ExternalHostError) { throw err; } - logger.warn({ err }, 'Error refreshing PR'); + logger.warn({ err }, 'Error re-attempting PR platform automerge'); } } From 318b7c27bda969afb0a5895e932457c21671a531 Mon Sep 17 00:00:00 2001 From: David Straub Date: Wed, 10 Jan 2024 13:48:07 -0500 Subject: [PATCH 08/17] refactor(platform/github): number -> prNo --- lib/modules/platform/github/index.spec.ts | 2 +- lib/modules/platform/github/index.ts | 8 ++++---- lib/modules/platform/types.ts | 2 +- lib/workers/repository/update/branch/index.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index 13b8ab2c111dda..5464c1cd604972 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -3134,7 +3134,7 @@ describe('modules/platform/github/index', () => { }; const pr: ReattemptPlatformAutomergeConfig = { - number: 123, + prNo: 123, platformOptions: { usePlatformAutomerge: true }, }; diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index a1879248fdea4b..97008652d12043 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -1733,19 +1733,19 @@ export async function updatePr({ } export async function reattemptPlatformAutomerge({ - number, + prNo, platformOptions, }: ReattemptPlatformAutomergeConfig): Promise { try { const { body: ghPr } = await githubApi.getJson( - `repos/${config.parentRepo ?? config.repository}/pulls/${number}`, + `repos/${config.parentRepo ?? config.repository}/pulls/${prNo}`, ); const result = coerceRestPr(ghPr); - const { node_id } = result; + const { number, node_id } = result; await tryPrAutomerge(number, node_id, platformOptions); - logger.debug(`PR platform automerge re-attempted...prNo: ${number}`); + logger.debug(`PR platform automerge re-attempted...prNo: ${prNo}`); } catch (err) /* istanbul ignore next */ { if (err instanceof ExternalHostError) { throw err; diff --git a/lib/modules/platform/types.ts b/lib/modules/platform/types.ts index 0b80069d899962..11d596e8be48f6 100644 --- a/lib/modules/platform/types.ts +++ b/lib/modules/platform/types.ts @@ -122,7 +122,7 @@ export interface UpdatePrConfig { targetBranch?: string; } export interface ReattemptPlatformAutomergeConfig { - number: number; + prNo: number; platformOptions?: PlatformPrOptions; } export interface EnsureIssueConfig { diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index b5bb751c65dacc..2c5a264d17e1bb 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -574,7 +574,7 @@ export async function processBranch( if (branchPr) { if (platform.reattemptPlatformAutomerge) { await platform.reattemptPlatformAutomerge({ - number: branchPr.number, + prNo: branchPr.number, platformOptions: getPlatformPrOptions(config), }); } From e2515dd17b81510b8adf6768e284f468684bf816 Mon Sep 17 00:00:00 2001 From: David Straub Date: Thu, 11 Jan 2024 10:12:13 -0500 Subject: [PATCH 09/17] refactor(workers/repository/update/branch): add boolean checks from tryPrAutomerge --- lib/workers/repository/update/branch/index.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index 2c5a264d17e1bb..7231ab310e6163 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -572,10 +572,15 @@ export async function processBranch( } if (branchPr) { - if (platform.reattemptPlatformAutomerge) { + const platformOptions = getPlatformPrOptions(config); + if ( + platformOptions.usePlatformAutomerge && + config.autoMergeAllowed && + platform.reattemptPlatformAutomerge + ) { await platform.reattemptPlatformAutomerge({ prNo: branchPr.number, - platformOptions: getPlatformPrOptions(config), + platformOptions, }); } // istanbul ignore if From fafbc3ac02cfd1584198c49d8e465bcd4a8b787a Mon Sep 17 00:00:00 2001 From: David Straub Date: Thu, 11 Jan 2024 10:55:48 -0500 Subject: [PATCH 10/17] fix(workers/repository/update/branch): use optional chaining on platformOptions --- lib/workers/repository/update/branch/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index 7231ab310e6163..a190cda8fb45b7 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -574,7 +574,7 @@ export async function processBranch( if (branchPr) { const platformOptions = getPlatformPrOptions(config); if ( - platformOptions.usePlatformAutomerge && + platformOptions?.usePlatformAutomerge && config.autoMergeAllowed && platform.reattemptPlatformAutomerge ) { From 3023529a2e7be8c10a6c06dd563ecc136d1fe076 Mon Sep 17 00:00:00 2001 From: David Straub Date: Thu, 11 Jan 2024 12:18:21 -0500 Subject: [PATCH 11/17] fix(workers/repository/update/branch): rm config.autoMergeAllowed check, update mocks --- lib/workers/repository/update/branch/index.spec.ts | 4 ++++ lib/workers/repository/update/branch/index.ts | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/workers/repository/update/branch/index.spec.ts b/lib/workers/repository/update/branch/index.spec.ts index 511063135527a5..d3483f3407ab05 100644 --- a/lib/workers/repository/update/branch/index.spec.ts +++ b/lib/workers/repository/update/branch/index.spec.ts @@ -108,6 +108,7 @@ describe('workers/repository/update/branch/index', () => { beforeEach(() => { scm.branchExists.mockResolvedValue(false); prWorker.ensurePr = jest.fn(); + prWorker.getPlatformPrOptions = jest.fn(); prAutomerge.checkAutoMerge = jest.fn(); // TODO: incompatible types (#22198) config = { @@ -133,6 +134,9 @@ describe('workers/repository/update/branch/index', () => { state: '', }), }); + prWorker.getPlatformPrOptions.mockReturnValue({ + usePlatformAutomerge: true, + }); GlobalConfig.set(adminConfig); // TODO: fix types, jest is using wrong overload (#22198) sanitize.sanitize.mockImplementation((input) => input!); diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index a190cda8fb45b7..2ee92280e91ba4 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -574,8 +574,7 @@ export async function processBranch( if (branchPr) { const platformOptions = getPlatformPrOptions(config); if ( - platformOptions?.usePlatformAutomerge && - config.autoMergeAllowed && + platformOptions.usePlatformAutomerge && platform.reattemptPlatformAutomerge ) { await platform.reattemptPlatformAutomerge({ From 5de6b57689e24d938c3720a22f08675bfa3d5f45 Mon Sep 17 00:00:00 2001 From: David Straub Date: Tue, 16 Jan 2024 10:17:49 -0500 Subject: [PATCH 12/17] perf(platform/github): fetch PR from cache in reattemptPlatformAutomerge Co-authored-by: Michael Kriese --- lib/modules/platform/github/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index 97008652d12043..0b27b89dccaef7 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -1737,10 +1737,7 @@ export async function reattemptPlatformAutomerge({ platformOptions, }: ReattemptPlatformAutomergeConfig): Promise { try { - const { body: ghPr } = await githubApi.getJson( - `repos/${config.parentRepo ?? config.repository}/pulls/${prNo}`, - ); - const result = coerceRestPr(ghPr); + const result = getPr(prNo); const { number, node_id } = result; await tryPrAutomerge(number, node_id, platformOptions); From e62dac6c32cf2d4fe959c938d55375137d5b5056 Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Wed, 17 Jan 2024 10:30:47 +0100 Subject: [PATCH 13/17] Update lib/modules/platform/github/index.ts --- lib/modules/platform/github/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index 0b27b89dccaef7..898f719b7d85ac 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -1737,7 +1737,7 @@ export async function reattemptPlatformAutomerge({ platformOptions, }: ReattemptPlatformAutomergeConfig): Promise { try { - const result = getPr(prNo); + const result = await getPr(prNo); const { number, node_id } = result; await tryPrAutomerge(number, node_id, platformOptions); From 89b9f94693a0c3fba2213c3dbfe49b85316d2b06 Mon Sep 17 00:00:00 2001 From: Michael Kriese Date: Wed, 17 Jan 2024 10:35:03 +0100 Subject: [PATCH 14/17] Update lib/modules/platform/github/index.ts --- lib/modules/platform/github/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index 898f719b7d85ac..a55dabc3d6e364 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -1737,7 +1737,7 @@ export async function reattemptPlatformAutomerge({ platformOptions, }: ReattemptPlatformAutomergeConfig): Promise { try { - const result = await getPr(prNo); + const result = (await getPr(prNo))!; const { number, node_id } = result; await tryPrAutomerge(number, node_id, platformOptions); From 5da60787bd7182f859a53ffae7ac08986c416a3d Mon Sep 17 00:00:00 2001 From: David Straub Date: Thu, 29 Feb 2024 11:52:14 -0500 Subject: [PATCH 15/17] test(platform/github): add restGetPrList to mocks --- lib/modules/platform/github/index.spec.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index b3b10fc7d3123f..e5bbe76597d20f 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -3227,6 +3227,19 @@ describe('modules/platform/github/index', () => { const mockScope = async (repoOpts: any = {}): Promise => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo', repoOpts); + scope + .get( + '/repos/some/repo/pulls?per_page=100&state=all&sort=updated&direction=desc&page=1', + ) + .reply(200, [ + { + number: 1234, + base: { sha: '1234' }, + head: { ref: 'somebranch', repo: { full_name: 'some/repo' } }, + state: 'open', + title: 'Some PR', + }, + ]); scope.get('/repos/some/repo/pulls/123').reply(200, getPrResp); await github.initRepo({ repository: 'some/repo' }); return scope; @@ -3238,6 +3251,11 @@ describe('modules/platform/github/index', () => { graphql: { query: { repository: {} } }, }; + const restGetPrList = { + method: 'GET', + url: 'https://api.github.com/repos/some/repo/pulls?per_page=100&state=all&sort=updated&direction=desc&page=1', + }; + const restGetPr = { method: 'GET', url: 'https://api.github.com/repos/some/repo/pulls/123', @@ -3276,6 +3294,7 @@ describe('modules/platform/github/index', () => { expect(httpMock.getTrace()).toMatchObject([ graphqlGetRepo, + restGetPrList, restGetPr, graphqlAutomerge, ]); From 71550f3a36bf72f6a9a9d33bedcf78577a364b41 Mon Sep 17 00:00:00 2001 From: David Straub Date: Sat, 16 Mar 2024 10:22:47 -0400 Subject: [PATCH 16/17] refactor(platform/gitlab): move automerge re-attempt from updatePr to reattemptPlatformAutomerge --- docs/usage/configuration-options.md | 4 +- lib/modules/platform/github/index.spec.ts | 45 ++++++++++++++----- lib/modules/platform/github/index.ts | 13 +++--- lib/modules/platform/gitlab/index.spec.ts | 32 +++++++++++++ lib/modules/platform/gitlab/index.ts | 14 +++++- lib/modules/platform/types.ts | 2 +- lib/workers/repository/update/branch/index.ts | 2 +- 7 files changed, 87 insertions(+), 25 deletions(-) diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 91a4a92f39587c..d289ca77023e59 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -3150,9 +3150,7 @@ If enabled Renovate will pin Docker images or GitHub Actions by means of their S If you have enabled `automerge` and set `automergeType=pr` in the Renovate config, then leaving `platformAutomerge` as `true` speeds up merging via the platform's native automerge functionality. -On most platforms, Renovate tries platform-native automerge only when it initially creates the PR. -Any PR that is being updated will be automerged with the Renovate-based automerge. -On GitHub, Renovate re-enables the PR for automerge whenever it's rebased, so that merge conflicts encountered in the Merge Queue can be resolved. +On GitHub and GitLab, Renovate re-enables the PR for platform-native automerge whenever it's rebased. `platformAutomerge` will configure PRs to be merged after all (if any) branch policies have been met. This option is available for Azure, Gitea, GitHub and GitLab. diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index 568205d2190e5e..995b2091f1de7a 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -3335,7 +3335,16 @@ describe('modules/platform/github/index', () => { }); }); - describe('reattemptPlatformAutomerge(prNo, platformOptions)', () => { + describe('reattemptPlatformAutomerge(number, platformOptions)', () => { + const getPrListResp = [ + { + number: 1234, + base: { sha: '1234' }, + head: { ref: 'somebranch', repo: { full_name: 'some/repo' } }, + state: 'open', + title: 'Some PR', + }, + ]; const getPrResp = { number: 123, node_id: 'abcd', @@ -3353,7 +3362,7 @@ describe('modules/platform/github/index', () => { }; const pr: ReattemptPlatformAutomergeConfig = { - prNo: 123, + number: 123, platformOptions: { usePlatformAutomerge: true }, }; @@ -3364,15 +3373,7 @@ describe('modules/platform/github/index', () => { .get( '/repos/some/repo/pulls?per_page=100&state=all&sort=updated&direction=desc&page=1', ) - .reply(200, [ - { - number: 1234, - base: { sha: '1234' }, - head: { ref: 'somebranch', repo: { full_name: 'some/repo' } }, - state: 'open', - title: 'Some PR', - }, - ]); + .reply(200, getPrListResp); scope.get('/repos/some/repo/pulls/123').reply(200, getPrResp); await github.initRepo({ repository: 'some/repo' }); return scope; @@ -3425,6 +3426,10 @@ describe('modules/platform/github/index', () => { await expect(github.reattemptPlatformAutomerge(pr)).toResolve(); + expect(logger.logger.debug).toHaveBeenLastCalledWith( + 'PR platform automerge re-attempted...prNo: 123', + ); + expect(httpMock.getTrace()).toMatchObject([ graphqlGetRepo, restGetPrList, @@ -3432,6 +3437,24 @@ describe('modules/platform/github/index', () => { graphqlAutomerge, ]); }); + + it('handles unknown error', async () => { + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + await github.initRepo({ repository: 'some/repo' }); + scope + .get( + '/repos/some/repo/pulls?per_page=100&state=all&sort=updated&direction=desc&page=1', + ) + .replyWithError('unknown error'); + + await expect(github.reattemptPlatformAutomerge(pr)).toResolve(); + + expect(logger.logger.warn).toHaveBeenCalledWith( + { err: new Error('external-host-error') }, + 'Error re-attempting PR platform automerge', + ); + }); }); describe('mergePr(prNo)', () => { diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts index bf58e3bbb60955..def3a90e63b194 100644 --- a/lib/modules/platform/github/index.ts +++ b/lib/modules/platform/github/index.ts @@ -1796,20 +1796,17 @@ export async function updatePr({ } export async function reattemptPlatformAutomerge({ - prNo, + number, platformOptions, }: ReattemptPlatformAutomergeConfig): Promise { try { - const result = (await getPr(prNo))!; - const { number, node_id } = result; + const result = (await getPr(number))!; + const { node_id } = result; await tryPrAutomerge(number, node_id, platformOptions); - logger.debug(`PR platform automerge re-attempted...prNo: ${prNo}`); - } catch (err) /* istanbul ignore next */ { - if (err instanceof ExternalHostError) { - throw err; - } + logger.debug(`PR platform automerge re-attempted...prNo: ${number}`); + } catch (err) { logger.warn({ err }, 'Error re-attempting PR platform automerge'); } } diff --git a/lib/modules/platform/gitlab/index.spec.ts b/lib/modules/platform/gitlab/index.spec.ts index ee59b73ca19363..317f07df0dcc56 100644 --- a/lib/modules/platform/gitlab/index.spec.ts +++ b/lib/modules/platform/gitlab/index.spec.ts @@ -2798,6 +2798,38 @@ describe('modules/platform/gitlab/index', () => { }); }); + describe('reattemptPlatformAutomerge(number, platformOptions)', () => { + const pr = { + number: 12345, + platformOptions: { + usePlatformAutomerge: true, + }, + }; + + it('should set automatic merge', async () => { + await initPlatform('13.3.6-ee'); + httpMock + .scope(gitlabApiHost) + .get('/api/v4/projects/undefined/merge_requests/12345') + .reply(200) + .get('/api/v4/projects/undefined/merge_requests/12345') + .reply(200, { + merge_status: 'can_be_merged', + pipeline: { + status: 'running', + }, + }) + .put('/api/v4/projects/undefined/merge_requests/12345/merge') + .reply(200); + + await expect(gitlab.reattemptPlatformAutomerge?.(pr)).toResolve(); + + expect(logger.debug).toHaveBeenLastCalledWith( + 'PR platform automerge re-attempted...prNo: 12345', + ); + }); + }); + describe('mergePr(pr)', () => { it('merges the PR', async () => { httpMock diff --git a/lib/modules/platform/gitlab/index.ts b/lib/modules/platform/gitlab/index.ts index 50d61078ee5b5e..843948806d22c9 100644 --- a/lib/modules/platform/gitlab/index.ts +++ b/lib/modules/platform/gitlab/index.ts @@ -48,6 +48,7 @@ import type { PlatformPrOptions, PlatformResult, Pr, + ReattemptPlatformAutomergeConfig, RepoParams, RepoResult, UpdatePrConfig, @@ -845,8 +846,19 @@ export async function updatePr({ if (platformOptions?.autoApprove) { await approvePr(iid); } +} - await tryPrAutomerge(iid, platformOptions); +export async function reattemptPlatformAutomerge({ + number: iid, + platformOptions, +}: ReattemptPlatformAutomergeConfig): Promise { + try { + await tryPrAutomerge(iid, platformOptions); + + logger.debug(`PR platform automerge re-attempted...prNo: ${iid}`); + } catch (err) { + logger.warn({ err }, 'Error re-attempting PR platform automerge'); + } } export async function mergePr({ id }: MergePRConfig): Promise { diff --git a/lib/modules/platform/types.ts b/lib/modules/platform/types.ts index cb6111dc733973..156dc63015d0b5 100644 --- a/lib/modules/platform/types.ts +++ b/lib/modules/platform/types.ts @@ -124,7 +124,7 @@ export interface UpdatePrConfig { targetBranch?: string; } export interface ReattemptPlatformAutomergeConfig { - prNo: number; + number: number; platformOptions?: PlatformPrOptions; } export interface EnsureIssueConfig { diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts index 54a6bcbaffc7d8..762d55622c86d4 100644 --- a/lib/workers/repository/update/branch/index.ts +++ b/lib/workers/repository/update/branch/index.ts @@ -580,7 +580,7 @@ export async function processBranch( platform.reattemptPlatformAutomerge ) { await platform.reattemptPlatformAutomerge({ - prNo: branchPr.number, + number: branchPr.number, platformOptions, }); } From b4d3419c2ac413fc062842d46f28c5ea55710947 Mon Sep 17 00:00:00 2001 From: David Straub Date: Sat, 16 Mar 2024 10:51:21 -0400 Subject: [PATCH 17/17] test(platform/gitlab): resolve coverage issue --- lib/modules/platform/gitlab/index.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/modules/platform/gitlab/index.ts b/lib/modules/platform/gitlab/index.ts index 843948806d22c9..9bbe45c58c8749 100644 --- a/lib/modules/platform/gitlab/index.ts +++ b/lib/modules/platform/gitlab/index.ts @@ -852,13 +852,9 @@ export async function reattemptPlatformAutomerge({ number: iid, platformOptions, }: ReattemptPlatformAutomergeConfig): Promise { - try { - await tryPrAutomerge(iid, platformOptions); + await tryPrAutomerge(iid, platformOptions); - logger.debug(`PR platform automerge re-attempted...prNo: ${iid}`); - } catch (err) { - logger.warn({ err }, 'Error re-attempting PR platform automerge'); - } + logger.debug(`PR platform automerge re-attempted...prNo: ${iid}`); } export async function mergePr({ id }: MergePRConfig): Promise {