Skip to content

Commit

Permalink
feat: expose merge strategy for configuration when automerging
Browse files Browse the repository at this point in the history
Expose the underlying merge strategy used by our platforms as a
configurable option for each repository to configure with the option
`automergeStrategy`. Additionally implement `automergeStrategy` for the
Bitbucket Cloud platform, as the only platform that doesn't currently
respect the remote repository's configured merge strategy.
  • Loading branch information
jbirch-atlassian committed Jun 27, 2021
1 parent 3be4446 commit a01c60e
Show file tree
Hide file tree
Showing 15 changed files with 281 additions and 16 deletions.
20 changes: 20 additions & 0 deletions docs/usage/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,26 @@ Example use:
}
```

## automergeStrategy

This setting is only applicable if you opt in to configure `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 pull requests. This
generally results in Renovate respecting a strategy configured in the platform itself for the repository where 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 new commit.

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

The only supported platform for this option at the moment 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
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,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 or automergeType=pr-comment',
type: 'string',
allowedValues: ['auto', 'merge-commit', 'rebase', 'squash'],
default: 'auto',
},
{
name: 'automergeComment',
description:
Expand Down
8 changes: 8 additions & 0 deletions lib/config/types.ts
Original file line number Diff line number Diff line change
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 @@ -228,6 +229,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
7 changes: 7 additions & 0 deletions lib/platform/azure/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# 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.
3 changes: 3 additions & 0 deletions lib/platform/bitbucket-server/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
## 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
Original file line number Diff line number Diff line change
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
3 changes: 3 additions & 0 deletions lib/platform/bitbucket/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
## Unsupported platform features/concepts

- Adding assignees to PRs not supported (does not seem to be a Bitbucket concept)
- `automergeStrategy=rebase` [is unsupported][bcloud-16610] by Bitbucket Cloud.

## Features requiring implementation

- Creating issues not implemented yet, e.g. when there is a config error
- Adding comments to PRs not implemented yet, e.g. when a PR has been edited or has a lockfile error

[bcloud-16610]: https://jira.atlassian.com/browse/BCLOUD-16610
35 changes: 34 additions & 1 deletion lib/platform/bitbucket/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -819,12 +819,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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import URL from 'url';
import is from '@sindresorhus/is';
import parseDiff from 'parse-diff';
import { 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 @@ -395,7 +396,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 @@ -752,19 +753,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
31 changes: 31 additions & 0 deletions lib/platform/bitbucket/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import url from 'url';
import { MergeStrategy } from '../../config/types';
import { BranchStatus, PrState } from '../../types';
import { HttpOptions, HttpPostOptions, HttpResponse } from '../../util/http';
import { BitbucketHttp } from '../../util/http/bitbucket';
Expand Down Expand Up @@ -56,6 +57,36 @@ export function repoInfoTransformer(repoInfoBody: RepoInfoBody): RepoInfo {
};
}

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

export interface MergeRequestBody {
close_source_branch?: boolean;
message: string;
merge_strategy?: BitbucketMergeStrategy;
}

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
6 changes: 6 additions & 0 deletions lib/platform/gitea/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@
## 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.

0 comments on commit a01c60e

Please sign in to comment.