diff --git a/docs/usage/self-hosted-experimental.md b/docs/usage/self-hosted-experimental.md index 01a2993e414873..7ee6a8ee1f39df 100644 --- a/docs/usage/self-hosted-experimental.md +++ b/docs/usage/self-hosted-experimental.md @@ -96,7 +96,7 @@ If set, Renovate will terminate the whole process group of a terminated child pr ## `RENOVATE_X_GITLAB_AUTO_MERGEABLE_CHECK_ATTEMPS` If set to an positive integer, Renovate will use this as the number of attempts to check if a merge request on GitLab is mergable before trying to automerge. -The formula for the delay between attempts is `250 * attempt * attempt` milliseconds. +The formula for the delay between attempts is `RENOVATE_X_GITLAB_MERGE_REQUEST_DELAY * attempt * attempt` milliseconds. Default value: `5` (attempts results in max. 13.75 seconds timeout). @@ -108,6 +108,12 @@ Can be useful for slow-running, self-hosted GitLab instances that don't react fa Default value: `1000` (milliseconds). +## `RENOVATE_X_GITLAB_MERGE_REQUEST_DELAY` + +If set, Renovate will use this as a delay to proceed with an automerge. + +Default value: `250` (milliseconds). + ## `RENOVATE_X_HARD_EXIT` If set to any value, Renovate will use a "hard" `process.exit()` once all work is done, even if a sub-process is otherwise delaying Node.js from exiting. diff --git a/lib/modules/platform/gitlab/index.spec.ts b/lib/modules/platform/gitlab/index.spec.ts index d10b776c062508..57970274be0945 100644 --- a/lib/modules/platform/gitlab/index.spec.ts +++ b/lib/modules/platform/gitlab/index.spec.ts @@ -52,6 +52,7 @@ describe('modules/platform/gitlab/index', () => { delete process.env.GITLAB_IGNORE_REPO_URL; delete process.env.RENOVATE_X_GITLAB_BRANCH_STATUS_DELAY; delete process.env.RENOVATE_X_GITLAB_AUTO_MERGEABLE_CHECK_ATTEMPS; + delete process.env.RENOVATE_X_GITLAB_MERGE_REQUEST_DELAY; }); async function initFakePlatform(version: string) { @@ -1867,6 +1868,45 @@ describe('modules/platform/gitlab/index', () => { ]); }); + it('should parse detailed_merge_status attribute on >= 15.6', async () => { + await initPlatform('15.6.0-ee'); + httpMock + .scope(gitlabApiHost) + .post('/api/v4/projects/undefined/merge_requests') + .reply(200, { + id: 1, + iid: 12345, + title: 'some title', + }) + .get('/api/v4/projects/undefined/merge_requests/12345') + .reply(200) + .get('/api/v4/projects/undefined/merge_requests/12345') + .reply(200) + .get('/api/v4/projects/undefined/merge_requests/12345') + .reply(200) + .put('/api/v4/projects/undefined/merge_requests/12345/merge') + .reply(200); + process.env.RENOVATE_X_GITLAB_AUTO_MERGEABLE_CHECK_ATTEMPS = '3'; + process.env.RENOVATE_X_GITLAB_MERGE_REQUEST_DELAY = '100'; + const pr = await gitlab.createPr({ + sourceBranch: 'some-branch', + targetBranch: 'master', + prTitle: 'some-title', + prBody: 'the-body', + platformOptions: { + usePlatformAutomerge: true, + }, + }); + expect(pr).toEqual({ + id: 1, + iid: 12345, + number: 12345, + sourceBranch: 'some-branch', + title: 'some title', + }); + expect(timers.setTimeout.mock.calls).toMatchObject([[100], [400], [900]]); + }); + it('raises with squash enabled when repository squash option is default_on', async () => { await initPlatform('14.0.0'); diff --git a/lib/modules/platform/gitlab/index.ts b/lib/modules/platform/gitlab/index.ts index de5137f28e7441..44d47ffce053ba 100644 --- a/lib/modules/platform/gitlab/index.ts +++ b/lib/modules/platform/gitlab/index.ts @@ -644,26 +644,46 @@ async function tryPrAutomerge( await ignoreApprovals(pr); } - const desiredStatus = 'can_be_merged'; + let desiredStatus = 'can_be_merged'; // The default value of 5 attempts results in max. 13.75 seconds timeout if no pipeline created. const retryTimes = parseInteger( process.env.RENOVATE_X_GITLAB_AUTO_MERGEABLE_CHECK_ATTEMPS, 5, ); + if (semver.gte(defaults.version, '15.6.0')) { + logger.trace( + { version: defaults.version }, + 'In GitLab 15.6 merge_status, using detailed_merge_status to check the merge request status', + ); + desiredStatus = 'mergeable'; + } + + const mergeDelay = parseInteger( + process.env.RENOVATE_X_GITLAB_MERGE_REQUEST_DELAY, + 250, + ); + // Check for correct merge request status before setting `merge_when_pipeline_succeeds` to `true`. for (let attempt = 1; attempt <= retryTimes; attempt += 1) { const { body } = await gitlabApi.getJson<{ merge_status: string; + detailed_merge_status?: string; pipeline: string; }>(`projects/${config.repository}/merge_requests/${pr}`, { memCache: false, }); // Only continue if the merge request can be merged and has a pipeline. - if (body.merge_status === desiredStatus && body.pipeline !== null) { + if ( + ((desiredStatus === 'mergeable' && + body.detailed_merge_status === desiredStatus) || + (desiredStatus === 'can_be_merged' && + body.merge_status === desiredStatus)) && + body.pipeline !== null + ) { break; } - await setTimeout(250 * attempt ** 2); // exponential backoff + await setTimeout(mergeDelay * attempt ** 2); // exponential backoff } await gitlabApi.putJson(