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): re-attempt platform automerge on github and gitlab #26567

Merged
merged 26 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
77dfd20
feat(platform/github): re-attempt platform automerge
straub Jan 9, 2024
46f0aaa
Merge branch 'main' into feat/github-refreshPr-tryPrAutomerge
straub Jan 9, 2024
5667cf4
refactor(platform/github): use a new function, reattemptPlatformAutom…
straub Jan 10, 2024
206bdf9
refactor(platform/github): less renaming
straub Jan 10, 2024
967e9bb
docs(platformAutomerge): address new GitHub Merge Queue behavior
straub Jan 10, 2024
c69d58e
docs(platformAutomerge): rm extra blank line
straub Jan 10, 2024
d5591ca
Merge branch 'main' into feat/github-refreshPr-tryPrAutomerge
straub Jan 10, 2024
f3417d0
docs(platformAutomerge): fix typo
straub Jan 10, 2024
aa59178
fix(platform/github): correct inconsistent log message
straub Jan 10, 2024
318b7c2
refactor(platform/github): number -> prNo
straub Jan 10, 2024
e2515dd
refactor(workers/repository/update/branch): add boolean checks from t…
straub Jan 11, 2024
fafbc3a
fix(workers/repository/update/branch): use optional chaining on platf…
straub Jan 11, 2024
3023529
fix(workers/repository/update/branch): rm config.autoMergeAllowed che…
straub Jan 11, 2024
89227b7
Merge branch 'main' into feat/github-refreshPr-tryPrAutomerge
straub Jan 11, 2024
5de6b57
perf(platform/github): fetch PR from cache in reattemptPlatformAutomerge
straub Jan 16, 2024
e62dac6
Update lib/modules/platform/github/index.ts
viceice Jan 17, 2024
89b9f94
Update lib/modules/platform/github/index.ts
viceice Jan 17, 2024
1f6fb0a
Merge branch 'main' into feat/github-refreshPr-tryPrAutomerge
viceice Jan 17, 2024
3eb4a39
Merge branch 'main' into feat/github-refreshPr-tryPrAutomerge
straub Feb 29, 2024
5da6078
test(platform/github): add restGetPrList to mocks
straub Feb 29, 2024
2d9d267
Merge branch 'main' into feat/github-refreshPr-tryPrAutomerge
straub Mar 7, 2024
1d97360
Merge branch 'main' into feat/github-refreshPr-tryPrAutomerge
straub Mar 16, 2024
71550f3
refactor(platform/gitlab): move automerge re-attempt from updatePr to…
straub Mar 16, 2024
d556665
Merge branch 'main' into feat/github-refreshPr-tryPrAutomerge
straub Mar 16, 2024
b4d3419
test(platform/gitlab): resolve coverage issue
straub Mar 16, 2024
aa67c3e
Merge branch 'main' into feat/github-refreshPr-tryPrAutomerge
straub Mar 17, 2024
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
3 changes: 1 addition & 2 deletions docs/usage/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -3150,8 +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.

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 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.
Expand Down
129 changes: 128 additions & 1 deletion lib/modules/platform/github/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,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,
ReattemptPlatformAutomergeConfig,
RepoParams,
UpdatePrConfig,
} from '../types';
import * as branch from './branch';
import type { ApiPageCache, GhRestPr } from './types';
import * as github from '.';
Expand Down Expand Up @@ -3330,6 +3335,128 @@ describe('modules/platform/github/index', () => {
});
});

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',
head: { repo: { full_name: 'some/repo' } },
};

const graphqlAutomergeResp = {
data: {
enablePullRequestAutoMerge: {
pullRequest: {
number: 123,
},
},
},
};

const pr: ReattemptPlatformAutomergeConfig = {
number: 123,
platformOptions: { usePlatformAutomerge: true },
};

const mockScope = async (repoOpts: any = {}): Promise<httpMock.Scope> => {
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, getPrListResp);
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 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',
};

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.reattemptPlatformAutomerge(pr)).toResolve();

expect(logger.logger.debug).toHaveBeenLastCalledWith(
'PR platform automerge re-attempted...prNo: 123',
);

expect(httpMock.getTrace()).toMatchObject([
graphqlGetRepo,
restGetPrList,
restGetPr,
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)', () => {
it('should merge the PR', async () => {
const scope = httpMock.scope(githubApiHost);
Expand Down
17 changes: 17 additions & 0 deletions lib/modules/platform/github/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import type {
PlatformParams,
PlatformPrOptions,
PlatformResult,
ReattemptPlatformAutomergeConfig,
RepoParams,
RepoResult,
UpdatePrConfig,
Expand Down Expand Up @@ -1794,6 +1795,22 @@ export async function updatePr({
}
}

export async function reattemptPlatformAutomerge({
number,
platformOptions,
}: ReattemptPlatformAutomergeConfig): Promise<void> {
try {
const result = (await getPr(number))!;
const { node_id } = result;

await tryPrAutomerge(number, node_id, platformOptions);

logger.debug(`PR platform automerge re-attempted...prNo: ${number}`);
} catch (err) {
logger.warn({ err }, 'Error re-attempting PR platform automerge');
}
}

export async function mergePr({
branchName,
id: prNo,
Expand Down
32 changes: 32 additions & 0 deletions lib/modules/platform/gitlab/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2876,6 +2876,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
Expand Down
8 changes: 8 additions & 0 deletions lib/modules/platform/gitlab/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import type {
PlatformPrOptions,
PlatformResult,
Pr,
ReattemptPlatformAutomergeConfig,
RepoParams,
RepoResult,
UpdatePrConfig,
Expand Down Expand Up @@ -848,8 +849,15 @@ export async function updatePr({
if (platformOptions?.autoApprove) {
await approvePr(iid);
}
}

export async function reattemptPlatformAutomerge({
number: iid,
platformOptions,
}: ReattemptPlatformAutomergeConfig): Promise<void> {
await tryPrAutomerge(iid, platformOptions);

logger.debug(`PR platform automerge re-attempted...prNo: ${iid}`);
}

export async function mergePr({ id }: MergePRConfig): Promise<boolean> {
Expand Down
7 changes: 7 additions & 0 deletions lib/modules/platform/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ export interface UpdatePrConfig {
state?: 'open' | 'closed';
targetBranch?: string;
}
export interface ReattemptPlatformAutomergeConfig {
number: number;
platformOptions?: PlatformPrOptions;
}
export interface EnsureIssueConfig {
title: string;
reuseTitle?: string;
Expand Down Expand Up @@ -226,6 +230,9 @@ export interface Platform {
getPr(number: number): Promise<Pr | null>;
findPr(findPRConfig: FindPRConfig): Promise<Pr | null>;
refreshPr?(number: number): Promise<void>;
reattemptPlatformAutomerge?(
prConfig: ReattemptPlatformAutomergeConfig,
): Promise<void>;
getBranchStatus(
branchName: string,
internalChecksAsSuccess: boolean,
Expand Down
4 changes: 4 additions & 0 deletions lib/workers/repository/update/branch/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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!);
Expand Down
21 changes: 17 additions & 4 deletions lib/workers/repository/update/branch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -572,9 +572,22 @@ export async function processBranch(
await scm.checkoutBranch(config.baseBranch);
updatesVerified = true;
}
// istanbul ignore if
if (branchPr && platform.refreshPr) {
await platform.refreshPr(branchPr.number);

if (branchPr) {
const platformOptions = getPlatformPrOptions(config);
if (
platformOptions.usePlatformAutomerge &&
platform.reattemptPlatformAutomerge
) {
await platform.reattemptPlatformAutomerge({
number: branchPr.number,
platformOptions,
});
}
// istanbul ignore if
if (platform.refreshPr) {
await platform.refreshPr(branchPr.number);
}
}
if (!commitSha && !branchExists) {
return {
Expand Down