Skip to content

Commit

Permalink
feat: expose merge strategy for configuration when automerging (#10627)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbirch-atlassian committed Jul 29, 2021
1 parent e631cae commit 3096f34
Show file tree
Hide file tree
Showing 16 changed files with 269 additions and 16 deletions.
19 changes: 19 additions & 0 deletions docs/usage/configuration-options.md
Expand Up @@ -173,6 +173,25 @@ Example use:
}
```

## automergeStrategy

This setting is only applicable if you opt-in by configuring `automerge` to `true` and `automergeType` to `pr` for any of your dependencies.

The automerge strategy defaults to `auto`, in which Renovate will make its best guess as to how to merge pull requests.
This generally results in Renovate respecting the strategy configured in the platform itself for the repository if possible.
Acceptable values are:

- `auto`, in which the choice is left to Renovate
- `fast-forward`, which generally involves no new commits in the resultant tree, but "fast-forwarding" the main branch reference
- `merge-commit`, which generally involves synthesizing a new merge commit
- `rebase`, which generally involves rewriting history as part of the merge — but usually retaining the individual commits
- `squash`, which generally involves flattening the commits that are being merged into a single new commit

Not all platforms support all pull request merge strategies.
In cases where a merge strategy is not supported by the platform, Renovate will hold off on merging instead of silently merging in a way you didn't wish for.

The only platform that supports `automergeStrategy` is Bitbucket Cloud.

## automergeType

This setting is only applicable if you opt in to configure `automerge` to `true` for any of your dependencies.
Expand Down
8 changes: 8 additions & 0 deletions lib/config/definitions.ts
Expand Up @@ -1285,6 +1285,14 @@ const options: RenovateOptions[] = [
allowedValues: ['branch', 'pr', 'pr-comment'],
default: 'pr',
},
{
name: 'automergeStrategy',
description:
'The merge strategy to use when automerging PRs. Used only if `automergeType=pr`.',
type: 'string',
allowedValues: ['auto', 'fast-forward', 'merge-commit', 'rebase', 'squash'],
default: 'auto',
},
{
name: 'automergeComment',
description:
Expand Down
8 changes: 8 additions & 0 deletions lib/config/types.ts
Expand Up @@ -21,6 +21,7 @@ export interface GroupConfig extends Record<string, unknown> {
export interface RenovateSharedConfig {
$schema?: string;
automerge?: boolean;
automergeStrategy?: MergeStrategy;
branchPrefix?: string;
branchName?: string;
manager?: string;
Expand Down Expand Up @@ -229,6 +230,13 @@ export type UpdateType =

export type MatchStringsStrategy = 'any' | 'recursive' | 'combination';

export type MergeStrategy =
| 'auto'
| 'fast-forward'
| 'merge-commit'
| 'rebase'
| 'squash';

// TODO: Proper typings
export interface PackageRule
extends RenovateSharedConfig,
Expand Down
5 changes: 5 additions & 0 deletions lib/platform/azure/index.md
@@ -0,0 +1,5 @@
# Azure DevOps and Azure DevOps Server

## 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
1 change: 1 addition & 0 deletions lib/platform/bitbucket-server/index.md
Expand Up @@ -8,6 +8,7 @@
## Features awaiting implementation

- Creating issues not implemented yet, used to alert users when there is a config error
- The `automergeStrategy` configuration option has not been implemented for this platform, and all values behave as if the value `auto` was used. Renovate will implicitly use the merge strategy configured as 'default' in the Bitbucket Server repository itself, and this cannot be overridden yet

## Testing

Expand Down
124 changes: 122 additions & 2 deletions lib/platform/bitbucket/__snapshots__/index.spec.ts.snap
Expand Up @@ -1254,7 +1254,7 @@ Array [

exports[`platform/bitbucket/index massageMarkdown() returns diff files 1`] = `"**foo**bartext"`;

exports[`platform/bitbucket/index mergePr() posts Merge 1`] = `
exports[`platform/bitbucket/index mergePr() posts Merge with auto 1`] = `
Array [
Object {
"headers": Object {
Expand All @@ -1268,7 +1268,67 @@ Array [
"url": "https://api.bitbucket.org/2.0/repositories/some/repo",
},
Object {
"body": "{\\"close_source_branch\\":true,\\"merge_strategy\\":\\"merge_commit\\",\\"message\\":\\"auto merged\\"}",
"body": "{\\"close_source_branch\\":true,\\"message\\":\\"auto merged\\"}",
"headers": Object {
"accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"authorization": "Basic YWJjOjEyMw==",
"content-length": "52",
"content-type": "application/json",
"host": "api.bitbucket.org",
"user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)",
},
"method": "POST",
"url": "https://api.bitbucket.org/2.0/repositories/some/repo/pullrequests/5/merge",
},
]
`;

exports[`platform/bitbucket/index mergePr() posts Merge with fast-forward 1`] = `
Array [
Object {
"headers": Object {
"accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"authorization": "Basic YWJjOjEyMw==",
"host": "api.bitbucket.org",
"user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)",
},
"method": "GET",
"url": "https://api.bitbucket.org/2.0/repositories/some/repo",
},
Object {
"body": "{\\"close_source_branch\\":true,\\"message\\":\\"auto merged\\",\\"merge_strategy\\":\\"fast_forward\\"}",
"headers": Object {
"accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"authorization": "Basic YWJjOjEyMw==",
"content-length": "84",
"content-type": "application/json",
"host": "api.bitbucket.org",
"user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)",
},
"method": "POST",
"url": "https://api.bitbucket.org/2.0/repositories/some/repo/pullrequests/5/merge",
},
]
`;

exports[`platform/bitbucket/index mergePr() posts Merge with merge-commit 1`] = `
Array [
Object {
"headers": Object {
"accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"authorization": "Basic YWJjOjEyMw==",
"host": "api.bitbucket.org",
"user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)",
},
"method": "GET",
"url": "https://api.bitbucket.org/2.0/repositories/some/repo",
},
Object {
"body": "{\\"close_source_branch\\":true,\\"message\\":\\"auto merged\\",\\"merge_strategy\\":\\"merge_commit\\"}",
"headers": Object {
"accept": "application/json",
"accept-encoding": "gzip, deflate, br",
Expand All @@ -1284,6 +1344,66 @@ Array [
]
`;

exports[`platform/bitbucket/index mergePr() posts Merge with optional merge strategy 1`] = `
Array [
Object {
"headers": Object {
"accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"authorization": "Basic YWJjOjEyMw==",
"host": "api.bitbucket.org",
"user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)",
},
"method": "GET",
"url": "https://api.bitbucket.org/2.0/repositories/some/repo",
},
Object {
"body": "{\\"close_source_branch\\":true,\\"message\\":\\"auto merged\\"}",
"headers": Object {
"accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"authorization": "Basic YWJjOjEyMw==",
"content-length": "52",
"content-type": "application/json",
"host": "api.bitbucket.org",
"user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)",
},
"method": "POST",
"url": "https://api.bitbucket.org/2.0/repositories/some/repo/pullrequests/5/merge",
},
]
`;

exports[`platform/bitbucket/index mergePr() posts Merge with squash 1`] = `
Array [
Object {
"headers": Object {
"accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"authorization": "Basic YWJjOjEyMw==",
"host": "api.bitbucket.org",
"user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)",
},
"method": "GET",
"url": "https://api.bitbucket.org/2.0/repositories/some/repo",
},
Object {
"body": "{\\"close_source_branch\\":true,\\"message\\":\\"auto merged\\",\\"merge_strategy\\":\\"squash\\"}",
"headers": Object {
"accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"authorization": "Basic YWJjOjEyMw==",
"content-length": "78",
"content-type": "application/json",
"host": "api.bitbucket.org",
"user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)",
},
"method": "POST",
"url": "https://api.bitbucket.org/2.0/repositories/some/repo/pullrequests/5/merge",
},
]
`;

exports[`platform/bitbucket/index setBranchStatus() posts status 1`] = `
Array [
Object {
Expand Down
1 change: 1 addition & 0 deletions lib/platform/bitbucket/index.md
Expand Up @@ -3,6 +3,7 @@
## Unsupported platform features/concepts

- Adding assignees to PRs not supported (does not seem to be a Bitbucket concept)
- `automergeStrategy=rebase` not supported by BitBucket Cloud, see [Jira issue BCLOUD-16610](https://jira.atlassian.com/browse/BCLOUD-16610)

## Features requiring implementation

Expand Down
35 changes: 34 additions & 1 deletion lib/platform/bitbucket/index.spec.ts
Expand Up @@ -812,12 +812,45 @@ describe(getName(), () => {
});

describe('mergePr()', () => {
it('posts Merge', async () => {
it('posts Merge with optional merge strategy', async () => {
const scope = await initRepoMock();
scope.post('/2.0/repositories/some/repo/pullrequests/5/merge').reply(200);
await bitbucket.mergePr(5, 'branch');
expect(httpMock.getTrace()).toMatchSnapshot();
});

it('posts Merge with auto', async () => {
const scope = await initRepoMock();
scope.post('/2.0/repositories/some/repo/pullrequests/5/merge').reply(200);
await bitbucket.mergePr(5, 'branch', 'auto');
expect(httpMock.getTrace()).toMatchSnapshot();
});

it('posts Merge with merge-commit', async () => {
const scope = await initRepoMock();
scope.post('/2.0/repositories/some/repo/pullrequests/5/merge').reply(200);
await bitbucket.mergePr(5, 'branch', 'merge-commit');
expect(httpMock.getTrace()).toMatchSnapshot();
});

it('posts Merge with squash', async () => {
const scope = await initRepoMock();
scope.post('/2.0/repositories/some/repo/pullrequests/5/merge').reply(200);
await bitbucket.mergePr(5, 'branch', 'squash');
expect(httpMock.getTrace()).toMatchSnapshot();
});

it('does not post Merge with rebase', async () => {
await bitbucket.mergePr(5, 'branch', 'rebase');
expect(httpMock.getTrace()).toEqual([]);
});

it('posts Merge with fast-forward', async () => {
const scope = await initRepoMock();
scope.post('/2.0/repositories/some/repo/pullrequests/5/merge').reply(200);
await bitbucket.mergePr(5, 'branch', 'fast-forward');
expect(httpMock.getTrace()).toMatchSnapshot();
});
});

describe('getVulnerabilityAlerts()', () => {
Expand Down
22 changes: 13 additions & 9 deletions lib/platform/bitbucket/index.ts
@@ -1,6 +1,7 @@
import URL from 'url';
import is from '@sindresorhus/is';
import parseDiff from 'parse-diff';
import type { MergeStrategy } from '../../config/types';
import { REPOSITORY_NOT_FOUND } from '../../constants/error-messages';
import { PLATFORM_TYPE_BITBUCKET } from '../../constants/platforms';
import { logger } from '../../logger';
Expand Down Expand Up @@ -29,7 +30,7 @@ import { smartTruncate } from '../utils/pr-body';
import { readOnlyIssueBody } from '../utils/read-only-issue-body';
import * as comments from './comments';
import * as utils from './utils';
import { PrResponse, RepoInfoBody } from './utils';
import { PrResponse, RepoInfoBody, mergeBodyTransformer } from './utils';

const bitbucketHttp = new BitbucketHttp();

Expand Down Expand Up @@ -391,7 +392,7 @@ export async function setBranchStatus({
const sha = await getBranchCommit(branchName);

// TargetUrl can not be empty so default to bitbucket
const url = targetUrl || /* istanbul ignore next */ 'http://bitbucket.org';
const url = targetUrl || /* istanbul ignore next */ 'https://bitbucket.org';

const body = {
name: context,
Expand Down Expand Up @@ -748,19 +749,22 @@ export async function updatePr({

export async function mergePr(
prNo: number,
branchName: string
branchName: string,
mergeStrategy: MergeStrategy
): Promise<boolean> {
logger.debug(`mergePr(${prNo}, ${branchName})`);
logger.debug(`mergePr(${prNo}, ${branchName}, ${mergeStrategy})`);

// Bitbucket Cloud does not support a rebase-alike; https://jira.atlassian.com/browse/BCLOUD-16610
if (mergeStrategy === 'rebase') {
logger.warn('Bitbucket Cloud does not support a "rebase" strategy.');
return false;
}

try {
await bitbucketHttp.postJson(
`/2.0/repositories/${config.repository}/pullrequests/${prNo}/merge`,
{
body: {
close_source_branch: true,
merge_strategy: 'merge_commit',
message: 'auto merged',
},
body: mergeBodyTransformer(mergeStrategy),
}
);
logger.debug('Automerging succeeded');
Expand Down
7 changes: 7 additions & 0 deletions lib/platform/bitbucket/types.ts
@@ -0,0 +1,7 @@
export type BitbucketMergeStrategy = 'fast_forward' | 'merge_commit' | 'squash';

export interface MergeRequestBody {
close_source_branch?: boolean;
message: string;
merge_strategy?: BitbucketMergeStrategy;
}
25 changes: 25 additions & 0 deletions lib/platform/bitbucket/utils.ts
@@ -1,8 +1,10 @@
import url from 'url';
import type { MergeStrategy } from '../../config/types';
import { BranchStatus, PrState } from '../../types';
import { HttpOptions, HttpPostOptions, HttpResponse } from '../../util/http';
import { BitbucketHttp } from '../../util/http/bitbucket';
import type { Pr } from '../types';
import type { BitbucketMergeStrategy, MergeRequestBody } from './types';

const bitbucketHttp = new BitbucketHttp();

Expand Down Expand Up @@ -56,6 +58,29 @@ export function repoInfoTransformer(repoInfoBody: RepoInfoBody): RepoInfo {
};
}

const bitbucketMergeStrategies: Map<MergeStrategy, BitbucketMergeStrategy> =
new Map([
['squash', 'squash'],
['merge-commit', 'merge_commit'],
['fast-forward', 'fast_forward'],
]);

export function mergeBodyTransformer(
mergeStrategy: MergeStrategy
): MergeRequestBody {
const body: MergeRequestBody = {
close_source_branch: true,
message: 'auto merged',
};

// The `auto` strategy will use the strategy configured inside Bitbucket.
if (mergeStrategy !== 'auto') {
body.merge_strategy = bitbucketMergeStrategies.get(mergeStrategy);
}

return body;
}

export const prStates = {
open: ['OPEN'],
notOpen: ['MERGED', 'DECLINED', 'SUPERSEDED'],
Expand Down
4 changes: 4 additions & 0 deletions lib/platform/gitea/index.md
Expand Up @@ -3,3 +3,7 @@
## Unsupported platform features/concepts

- **Adding reviewers to PRs not supported**: Gitea versions older than v1.14.0 do not have the required API.

## 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 Gitea repository itself, and this cannot be overridden yet
5 changes: 5 additions & 0 deletions lib/platform/github/index.md
@@ -0,0 +1,5 @@
# GitHub and GitHub Enterprise

## 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 GitHub repository itself, and this cannot be overridden yet

0 comments on commit 3096f34

Please sign in to comment.