diff --git a/lib/config/definitions.js b/lib/config/definitions.js index a8537639d4b2a4257266a5b6191f8490a1b3b83f..408f15647574633066d0c8e629fb9e8c187c6dc5 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -453,6 +453,13 @@ const options = [ cli: false, env: false, }, + { + name: 'azureAutoComplete', + description: + 'If set to true, Azure DevOps PRs will be set to auto-complete after all (if any) branch policies have been met', + type: 'boolean', + default: false, + }, { name: 'azureWorkItemId', description: diff --git a/lib/platform/azure/index.js b/lib/platform/azure/index.js index b53c9f5cfd4abab6badd9fcec71837d979879c61..faf580239ec1ce764da3178c00fb5f0f1294fb4f 100644 --- a/lib/platform/azure/index.js +++ b/lib/platform/azure/index.js @@ -348,7 +348,14 @@ async function getPr(pullRequestId) { return pr; } -async function createPr(branchName, title, body, labels, useDefaultBranch) { +async function createPr( + branchName, + title, + body, + labels, + useDefaultBranch, + platformOptions = {} +) { const sourceRefName = azureHelper.getNewBranchName(branchName); const targetRefName = azureHelper.getNewBranchName( useDefaultBranch ? config.defaultBranch : config.baseBranch @@ -360,7 +367,7 @@ async function createPr(branchName, title, body, labels, useDefaultBranch) { id: config.azureWorkItemId, }, ]; - const pr = await azureApiGit.createPullRequest( + let pr = await azureApiGit.createPullRequest( { sourceRefName, targetRefName, @@ -370,6 +377,21 @@ async function createPr(branchName, title, body, labels, useDefaultBranch) { }, config.repoId ); + if (platformOptions.azureAutoComplete) { + pr = await azureApiGit.updatePullRequest( + { + autoCompleteSetBy: { + id: pr.createdBy.id, + }, + completionOptions: { + squashMerge: true, + deleteSourceBranch: true, + }, + }, + config.repoId, + pr.pullRequestId + ); + } await labels.forEach(async label => { await azureApiGit.createPullRequestLabel( { diff --git a/lib/platform/github/index.js b/lib/platform/github/index.js index e0372042005f69470b8b7bfbc609b7a849c11b22..dcf61511fd97a0de07fef92492cb982e4421fe65 100644 --- a/lib/platform/github/index.js +++ b/lib/platform/github/index.js @@ -945,7 +945,7 @@ async function createPr( body, labels, useDefaultBranch, - statusCheckVerify + platformOptions = {} ) { const base = useDefaultBranch ? config.defaultBranch : config.baseBranch; // Include the repository owner to handle forkMode and regular mode @@ -976,7 +976,7 @@ async function createPr( pr.displayNumber = `Pull Request #${pr.number}`; pr.branchName = branchName; await addLabels(pr.number, labels); - if (statusCheckVerify) { + if (platformOptions.statusCheckVerify) { logger.debug('Setting statusCheckVerify'); await setBranchStatus( branchName, diff --git a/lib/workers/pr/index.js b/lib/workers/pr/index.js index ed1043131749282282e178efda82404611dbe0da..060296b382a6321d71716ec92644beb3818f76d5 100644 --- a/lib/workers/pr/index.js +++ b/lib/workers/pr/index.js @@ -247,13 +247,17 @@ async function ensurePr(prConfig) { logger.info('DRY-RUN: Would create PR: ' + prTitle); pr = { number: 0, displayNumber: 'Dry run PR' }; } else { + const platformOptions = { + azureAutoComplete: config.azureAutoComplete, + statusCheckVerify: config.statusCheckVerify, + }; pr = await platform.createPr( branchName, prTitle, prBody, config.labels, false, - config.statusCheckVerify + platformOptions ); logger.info({ branch: branchName, pr: pr.number }, 'PR created'); } diff --git a/renovate-schema.json b/renovate-schema.json index d2a96a7b779e3d2b551248c62ed04d0241c9fdf2..eecf5bb8bcfc789ef0b255b93d9466985e780927 100644 --- a/renovate-schema.json +++ b/renovate-schema.json @@ -300,6 +300,11 @@ ], "default": "semver" }, + "azureAutoComplete": { + "description": "If set to true, Azure DevOps PRs will be set to auto-complete after all (if any) branch policies have been met", + "type": "boolean", + "default": false + }, "azureWorkItemId": { "description": "The id of an existing work item on Azure Boards to link to each PR", "type": "integer", diff --git a/test/platform/azure/__snapshots__/index.spec.js.snap b/test/platform/azure/__snapshots__/index.spec.js.snap index 862671e745f8cea0c4209e29ec48a4ee74c90dc9..a900a51278ff2b6f1abdd23ca99d79b3eb66119a 100644 --- a/test/platform/azure/__snapshots__/index.spec.js.snap +++ b/test/platform/azure/__snapshots__/index.spec.js.snap @@ -16,6 +16,24 @@ Object { } `; +exports[`platform/azure createPr() should create and return a PR object with auto-complete set 1`] = ` +Object { + "autoCompleteSetBy": Object { + "id": 123, + }, + "branchName": "some-branch", + "completionOptions": Object { + "deleteSourceBranch": true, + "squashMerge": true, + }, + "createdBy": Object { + "id": 123, + }, + "displayNumber": "Pull Request #456", + "pullRequestId": 456, +} +`; + exports[`platform/azure deleteLabel() Should delete a label 1`] = ` Array [ Array [], diff --git a/test/platform/azure/index.spec.js b/test/platform/azure/index.spec.js index b45192ffa8c05e3689eda96954ee582fecca19a8..5c8b113976299769a8aa5478bfaec3e71a4d4158 100644 --- a/test/platform/azure/index.spec.js +++ b/test/platform/azure/index.spec.js @@ -394,6 +394,45 @@ describe('platform/azure', () => { ); expect(pr).toMatchSnapshot(); }); + it('should create and return a PR object with auto-complete set', async () => { + await initRepo({ repository: 'some/repo', token: 'token' }); + const prResult = { + pullRequestId: 456, + displayNumber: `Pull Request #456`, + createdBy: { + id: 123, + }, + }; + const prUpdateResult = { + ...prResult, + autoCompleteSetBy: { + id: prResult.createdBy.id, + }, + completionOptions: { + squashMerge: true, + deleteSourceBranch: true, + }, + }; + const updateFn = jest + .fn(() => prUpdateResult) + .mockName('updatePullRequest'); + azureApi.gitApi.mockImplementationOnce(() => ({ + createPullRequest: jest.fn(() => prResult), + createPullRequestLabel: jest.fn(() => ({})), + updatePullRequest: updateFn, + })); + azureHelper.getRenovatePRFormat.mockImplementation(x => x); + const pr = await azure.createPr( + 'some-branch', + 'The Title', + 'Hello world', + ['deps', 'renovate'], + false, + { azureAutoComplete: true } + ); + expect(updateFn).toHaveBeenCalled(); + expect(pr).toMatchSnapshot(); + }); }); describe('updatePr(prNo, title, body)', () => { diff --git a/test/platform/github/index.spec.js b/test/platform/github/index.spec.js index 0e6af396518cce34bb22e906c8ad25df612de62a..bf1f3d1635f0c0de50ce1412ab1814707583ae04 100644 --- a/test/platform/github/index.spec.js +++ b/test/platform/github/index.spec.js @@ -1342,7 +1342,7 @@ describe('platform/github', () => { 'Hello world', ['deps', 'renovate'], false, - true + { statusCheckVerify: true } ); expect(pr).toMatchSnapshot(); expect(get.post.mock.calls).toMatchSnapshot(); diff --git a/test/workers/pr/__snapshots__/index.spec.js.snap b/test/workers/pr/__snapshots__/index.spec.js.snap index f99be39f2ebfd4ca2957412001bf4ff0831845c6..f6d65d2689fa1288bff14c579c113670c1e92aab 100644 --- a/test/workers/pr/__snapshots__/index.spec.js.snap +++ b/test/workers/pr/__snapshots__/index.spec.js.snap @@ -66,7 +66,10 @@ Array [ - [ ] <!-- renovate-rebase -->If you want to rebase/retry this PR, check this box", Array [], false, - false, + Object { + "azureAutoComplete": false, + "statusCheckVerify": false, + }, ] `; @@ -110,7 +113,10 @@ Array [ - [ ] <!-- renovate-rebase -->If you want to rebase/retry this PR, check this box", Array [], false, - false, + Object { + "azureAutoComplete": false, + "statusCheckVerify": false, + }, ] `; @@ -171,7 +177,10 @@ note 2 - [ ] <!-- renovate-rebase -->If you want to rebase/retry this PR, check this box", Array [], false, - false, + Object { + "azureAutoComplete": false, + "statusCheckVerify": false, + }, ] `; @@ -215,7 +224,10 @@ Array [ - [ ] <!-- renovate-rebase -->If you want to rebase/retry this PR, check this box", Array [], false, - false, + Object { + "azureAutoComplete": false, + "statusCheckVerify": false, + }, ] `; diff --git a/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap b/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap index fbbd2eef18f80ecc64d9ca12385d39b5ee95d1ee..5b865cbab7607a378722c79f55f0494a6f80cb10 100644 --- a/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap +++ b/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap @@ -7,6 +7,7 @@ Array [ "automerge": false, "automergeComment": "automergeComment", "automergeType": "pr", + "azureAutoComplete": false, "azureWorkItemId": 0, "baseDir": null, "bbUseDefaultReviewers": true, @@ -116,6 +117,7 @@ Array [ "automerge": false, "automergeComment": "automergeComment", "automergeType": "pr", + "azureAutoComplete": false, "azureWorkItemId": 0, "baseDir": null, "bbUseDefaultReviewers": true, @@ -225,6 +227,7 @@ Array [ "automerge": false, "automergeComment": "automergeComment", "automergeType": "pr", + "azureAutoComplete": false, "azureWorkItemId": 0, "baseDir": null, "bbUseDefaultReviewers": true, @@ -334,6 +337,7 @@ Array [ "automerge": false, "automergeComment": "automergeComment", "automergeType": "pr", + "azureAutoComplete": false, "azureWorkItemId": 0, "baseDir": null, "bbUseDefaultReviewers": true, @@ -443,6 +447,7 @@ Array [ "automerge": false, "automergeComment": "automergeComment", "automergeType": "pr", + "azureAutoComplete": false, "azureWorkItemId": 0, "baseDir": null, "bbUseDefaultReviewers": true, @@ -552,6 +557,7 @@ Array [ "automerge": false, "automergeComment": "automergeComment", "automergeType": "pr", + "azureAutoComplete": false, "azureWorkItemId": 0, "baseDir": null, "bbUseDefaultReviewers": true, @@ -661,6 +667,7 @@ Array [ "automerge": false, "automergeComment": "automergeComment", "automergeType": "pr", + "azureAutoComplete": false, "azureWorkItemId": 0, "baseDir": null, "bbUseDefaultReviewers": true, @@ -770,6 +777,7 @@ Array [ "automerge": false, "automergeComment": "automergeComment", "automergeType": "pr", + "azureAutoComplete": false, "azureWorkItemId": 0, "baseDir": null, "bbUseDefaultReviewers": true, diff --git a/website/docs/configuration-options.md b/website/docs/configuration-options.md index eb609c1ce3baa0b811821f1e219d6829ecaea353..f3e52b0c07a69c41190e3570e8603bd98ae276d2 100644 --- a/website/docs/configuration-options.md +++ b/website/docs/configuration-options.md @@ -75,6 +75,12 @@ Merge commits will employ the standard GitHub "merge commit" API, just like when Branch push employs GitHub's low-level `git` API to push the Renovate upgrade directly to the head of the base branch (e.g. `master`) to maintain a "clean" history. The downside of this approach is that it implicitly enables the `rebaseStalePrs` setting because otherwise we would risk pushing a bad commit to master. i.e. Renovate won't push the commit to base branch unless the branch is completely up-to-date with `master` and has passed tests, which means that if the default branch is getting updated regularly then it might take several rebases from Renovate until it has a branch commit that is safe to push to `master`. +## azureAutoComplete + +Setting this to true will set PRs in Azure DevOps to auto-complete after all (if any) branch policies have been met. + +You could also configure this using `packageRules`. + ## azureWorkItemId When creating a PR in Azure DevOps, some branches can be protected with branch policies to [check for linked work items](https://docs.microsoft.com/en-us/azure/devops/repos/git/branch-policies?view=azure-devops#check-for-linked-work-items). Creating a work item in Azure DevOps is beyond the scope of Renovate, but Renovate can link an already existing work item when creating PRs.