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/azure): implement automergeStrategy for Azure DevOps platform #26429

Merged
merged 10 commits into from
Dec 27, 2023
2 changes: 1 addition & 1 deletion lib/config/options/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1828,7 +1828,7 @@ const options: RenovateOptions[] = [
type: 'string',
allowedValues: ['auto', 'fast-forward', 'merge-commit', 'rebase', 'squash'],
default: 'auto',
supportedPlatforms: ['bitbucket', 'gitea'],
supportedPlatforms: ['azure', 'bitbucket', 'gitea'],
},
{
name: 'automergeComment',
Expand Down
112 changes: 112 additions & 0 deletions lib/modules/platform/azure/__snapshots__/index.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,118 @@ exports[`modules/platform/azure/index createPr() should create and return an app
}
`;

exports[`modules/platform/azure/index createPr() when usePlatformAutomerge is set should create PR with automergeStrategy fast-forward 1`] = `
{
"autoCompleteSetBy": {
"id": "123",
},
"bodyStruct": {
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
"completionOptions": {
"deleteSourceBranch": true,
"mergeCommitMessage": "The Title",
"mergeStrategy": 3,
"squashMerge": false,
},
"createdAt": undefined,
"createdBy": {
"id": "123",
},
"number": 456,
"pullRequestId": 456,
"sourceBranch": undefined,
"sourceRefName": undefined,
"state": "open",
"targetBranch": undefined,
"title": "The Title",
}
`;

exports[`modules/platform/azure/index createPr() when usePlatformAutomerge is set should create PR with automergeStrategy merge-commit 1`] = `
{
"autoCompleteSetBy": {
"id": "123",
},
"bodyStruct": {
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
"completionOptions": {
"deleteSourceBranch": true,
"mergeCommitMessage": "The Title",
"mergeStrategy": 1,
"squashMerge": false,
},
"createdAt": undefined,
"createdBy": {
"id": "123",
},
"number": 456,
"pullRequestId": 456,
"sourceBranch": undefined,
"sourceRefName": undefined,
"state": "open",
"targetBranch": undefined,
"title": "The Title",
}
`;

exports[`modules/platform/azure/index createPr() when usePlatformAutomerge is set should create PR with automergeStrategy rebase 1`] = `
{
"autoCompleteSetBy": {
"id": "123",
},
"bodyStruct": {
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
"completionOptions": {
"deleteSourceBranch": true,
"mergeCommitMessage": "The Title",
"mergeStrategy": 3,
"squashMerge": false,
},
"createdAt": undefined,
"createdBy": {
"id": "123",
},
"number": 456,
"pullRequestId": 456,
"sourceBranch": undefined,
"sourceRefName": undefined,
"state": "open",
"targetBranch": undefined,
"title": "The Title",
}
`;

exports[`modules/platform/azure/index createPr() when usePlatformAutomerge is set should create PR with automergeStrategy squash 1`] = `
{
"autoCompleteSetBy": {
"id": "123",
},
"bodyStruct": {
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
"completionOptions": {
"deleteSourceBranch": true,
"mergeCommitMessage": "The Title",
"mergeStrategy": 2,
"squashMerge": false,
},
"createdAt": undefined,
"createdBy": {
"id": "123",
},
"number": 456,
"pullRequestId": 456,
"sourceBranch": undefined,
"sourceRefName": undefined,
"state": "open",
"targetBranch": undefined,
"title": "The Title",
}
`;

exports[`modules/platform/azure/index createPr() when usePlatformAutomerge is set should create and return a PR object with auto-complete set 1`] = `
{
"autoCompleteSetBy": {
Expand Down
122 changes: 119 additions & 3 deletions lib/modules/platform/azure/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -936,7 +936,7 @@ describe('modules/platform/azure/index', () => {
expect(pr).toMatchSnapshot();
});

it('should only call getMergeMethod once per run', async () => {
it('should only call getMergeMethod once per run when automergeStrategy is auto', async () => {
await initRepo({ repository: 'some/repo' });
const prResult = [
{
Expand Down Expand Up @@ -1001,7 +1001,10 @@ describe('modules/platform/azure/index', () => {
prTitle: 'The Title',
prBody: 'Hello world',
labels: ['deps', 'renovate'],
platformOptions: { usePlatformAutomerge: true },
platformOptions: {
automergeStrategy: 'auto',
usePlatformAutomerge: true,
},
});

await azure.createPr({
Expand All @@ -1010,12 +1013,125 @@ describe('modules/platform/azure/index', () => {
prTitle: 'The Second Title',
prBody: 'Hello world',
labels: ['deps', 'renovate'],
platformOptions: { usePlatformAutomerge: true },
platformOptions: {
automergeStrategy: 'auto',
usePlatformAutomerge: true,
},
});

expect(updateFn).toHaveBeenCalledTimes(2);
expect(azureHelper.getMergeMethod).toHaveBeenCalledTimes(1);
});

it.each`
automergeStrategy
${'fast-forward'}
${'merge-commit'}
${'rebase'}
${'squash'}
`(
'should not call getMergeMethod when automergeStrategy is $automergeStrategy',
async (automergeStrategy) => {
await initRepo({ repository: 'some/repo' });
const prResult = {
pullRequestId: 123,
title: 'The Title',
createdBy: {
id: '123',
},
};
const prUpdateResults = {
...prResult,
autoCompleteSetBy: {
id: prResult.createdBy.id,
},
completionOptions: {
squashMerge: true,
deleteSourceBranch: true,
mergeCommitMessage: 'The Title',
},
};
const updateFn = jest.fn(() => Promise.resolve(prUpdateResults));

azureApi.gitApi.mockResolvedValue(
partial<IGitApi>({
createPullRequest: jest.fn(() => Promise.resolve(prResult)),
createPullRequestLabel: jest.fn().mockResolvedValue({}),
updatePullRequest: updateFn,
}),
);
await azure.createPr({
sourceBranch: 'some-branch',
targetBranch: 'dev',
prTitle: 'The Title',
prBody: 'Hello world',
labels: ['deps', 'renovate'],
platformOptions: {
automergeStrategy,
usePlatformAutomerge: true,
},
});

expect(azureHelper.getMergeMethod).toHaveBeenCalledTimes(0);
},
);

it.each`
automergeStrategy | prMergeStrategy
${'fast-forward'} | ${GitPullRequestMergeStrategy.Rebase}
${'merge-commit'} | ${GitPullRequestMergeStrategy.NoFastForward}
${'rebase'} | ${GitPullRequestMergeStrategy.Rebase}
${'squash'} | ${GitPullRequestMergeStrategy.Squash}
`(
'should create PR with automergeStrategy $automergeStrategy',
async ({ automergeStrategy, prMergeStrategy }) => {
await initRepo({ repository: 'some/repo' });
const prResult = {
pullRequestId: 456,
title: 'The Title',
createdBy: {
id: '123',
},
};
const prUpdateResult = {
...prResult,
autoCompleteSetBy: {
id: prResult.createdBy.id,
},
completionOptions: {
mergeStrategy: prMergeStrategy,
squashMerge: false,
deleteSourceBranch: true,
mergeCommitMessage: 'The Title',
},
};
const updateFn = jest.fn().mockResolvedValue(prUpdateResult);
azureApi.gitApi.mockResolvedValueOnce(
partial<IGitApi>({
createPullRequest: jest.fn().mockResolvedValue(prResult),
createPullRequestLabel: jest.fn().mockResolvedValue({}),
updatePullRequest: updateFn,
}),
);
const pr = await azure.createPr({
sourceBranch: 'some-branch',
targetBranch: 'dev',
prTitle: 'The Title',
prBody: 'Hello world',
labels: ['deps', 'renovate'],
platformOptions: {
automergeStrategy,
usePlatformAutomerge: true,
},
});
expect(pr).toMatchSnapshot();
joegoldman2 marked this conversation as resolved.
Show resolved Hide resolved
expect(updateFn).toHaveBeenCalled();
expect(
updateFn.mock.calls[0][0].completionOptions.mergeStrategy,
).toBe(prMergeStrategy);
expect(azureHelper.getMergeMethod).toHaveBeenCalledTimes(0);
},
);
});

it('should create and return an approved PR object', async () => {
Expand Down
22 changes: 21 additions & 1 deletion lib/modules/platform/azure/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
GitVersionDescriptor,
PullRequestStatus,
} from 'azure-devops-node-api/interfaces/GitInterfaces.js';
import type { MergeStrategy } from '../../../config/types';
import {
REPOSITORY_ARCHIVED,
REPOSITORY_EMPTY,
Expand Down Expand Up @@ -461,6 +462,22 @@ async function getMergeStrategy(
);
}

function mapMergeStrategy(
joegoldman2 marked this conversation as resolved.
Show resolved Hide resolved
mergeStrategy?: MergeStrategy,
): GitPullRequestMergeStrategy {
switch (mergeStrategy) {
case 'rebase':
case 'fast-forward':
viceice marked this conversation as resolved.
Show resolved Hide resolved
return GitPullRequestMergeStrategy.Rebase;
case 'merge-commit':
return GitPullRequestMergeStrategy.NoFastForward;
case 'squash':
return GitPullRequestMergeStrategy.Squash;
default:
return GitPullRequestMergeStrategy.NoFastForward;
viceice marked this conversation as resolved.
Show resolved Hide resolved
}
}

export async function createPr({
sourceBranch,
targetBranch,
Expand Down Expand Up @@ -491,7 +508,10 @@ export async function createPr({
config.repoId,
);
if (platformOptions?.usePlatformAutomerge) {
const mergeStrategy = await getMergeStrategy(pr.targetRefName!);
const mergeStrategy =
platformOptions.automergeStrategy === 'auto'
? await getMergeStrategy(pr.targetRefName!)
: mapMergeStrategy(platformOptions.automergeStrategy);
pr = await azureApiGit.updatePullRequest(
{
autoCompleteSetBy: {
Expand Down
4 changes: 0 additions & 4 deletions lib/modules/platform/azure/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ Permissions for your PAT should be at minimum:

Remember to set `platform=azure` somewhere in your Renovate config file.

## Features awaiting implementation

- The `automergeStrategy` configuration option has not been implemented for this platform, and all values behave as if the value `auto` was used. Renovate will use the merge strategy configured in the Azure Repos repository itself, and this cannot be overridden yet

## Running Renovate in Azure Pipelines

### Setting up a new pipeline
Expand Down
1 change: 1 addition & 0 deletions lib/modules/platform/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export interface Issue {
}
export type PlatformPrOptions = {
autoApprove?: boolean;
automergeStrategy?: MergeStrategy;
azureWorkItemId?: number;
bbUseDefaultReviewers?: boolean;
gitLabIgnoreApprovals?: boolean;
Expand Down
1 change: 1 addition & 0 deletions lib/workers/repository/update/pr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export function getPlatformPrOptions(

return {
autoApprove: !!config.autoApprove,
automergeStrategy: config.automergeStrategy,
azureWorkItemId: config.azureWorkItemId ?? 0,
bbUseDefaultReviewers: !!config.bbUseDefaultReviewers,
gitLabIgnoreApprovals: !!config.gitLabIgnoreApprovals,
Expand Down