Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(platform/gitlab): handle detailed_merge_status to proceed with automerge #26438

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/usage/self-hosted-experimental.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand All @@ -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.
Expand Down
43 changes: 43 additions & 0 deletions lib/modules/platform/gitlab/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1824,6 +1824,49 @@ 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 = '250';
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([
[250],
[1000],
[2250],
]);
});

it('raises with squash enabled when repository squash option is default_on', async () => {
await initPlatform('14.0.0');

Expand Down
26 changes: 23 additions & 3 deletions lib/modules/platform/gitlab/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down