From 2784016166bfa48630bba47f36bebb921eb98202 Mon Sep 17 00:00:00 2001
From: Jamie Magee <JamieMagee@users.noreply.github.com>
Date: Fri, 18 Oct 2019 13:25:47 +0200
Subject: [PATCH] feat(azure): support different merge strategies for
 autocomplete (#4584)

---
 lib/platform/azure/azure-got-wrapper.ts       |  6 +-
 lib/platform/azure/azure-helper.ts            | 63 ++++++++-----------
 lib/platform/azure/index.ts                   | 10 +--
 test/platform/azure/azure-got-wrapper.spec.ts |  3 +-
 test/platform/azure/azure-helper.spec.ts      | 53 ++++++++++++++++
 test/platform/azure/index.spec.ts             |  2 +-
 6 files changed, 93 insertions(+), 44 deletions(-)

diff --git a/lib/platform/azure/azure-got-wrapper.ts b/lib/platform/azure/azure-got-wrapper.ts
index f0d71ba340..3558cc1225 100644
--- a/lib/platform/azure/azure-got-wrapper.ts
+++ b/lib/platform/azure/azure-got-wrapper.ts
@@ -17,10 +17,14 @@ export function gitApi() {
   return azureObj().getGitApi();
 }
 
-export function getCoreApi() {
+export function coreApi() {
   return azureObj().getCoreApi();
 }
 
+export function policyApi() {
+  return azureObj().getPolicyApi();
+}
+
 export function setEndpoint(e: string) {
   endpoint = e;
 }
diff --git a/lib/platform/azure/azure-helper.ts b/lib/platform/azure/azure-helper.ts
index 6c16e4e2a7..b14d6b8d0c 100644
--- a/lib/platform/azure/azure-helper.ts
+++ b/lib/platform/azure/azure-helper.ts
@@ -1,10 +1,10 @@
+import { GitPullRequestMergeStrategy } from 'azure-devops-node-api/interfaces/GitInterfaces';
+
 import * as azureApi from './azure-got-wrapper';
 import { logger } from '../../logger';
 
-/**
- *
- * @param {string} branchName
- */
+const mergePolicyGuid = 'fa4e907d-c16b-4a4c-9dfa-4916e5d171ab'; // Magic GUID for merge strategy policy configurations
+
 export function getNewBranchName(branchName?: string) {
   if (branchName && !branchName.startsWith('refs/heads/')) {
     return `refs/heads/${branchName}`;
@@ -12,10 +12,6 @@ export function getNewBranchName(branchName?: string) {
   return branchName;
 }
 
-/**
- *
- * @param {string} branchPath
- */
 export function getBranchNameWithoutRefsheadsPrefix(branchPath: string) {
   if (!branchPath) {
     logger.error(`getBranchNameWithoutRefsheadsPrefix(${branchPath})`);
@@ -30,10 +26,6 @@ export function getBranchNameWithoutRefsheadsPrefix(branchPath: string) {
   return branchPath.substring(11, branchPath.length);
 }
 
-/**
- *
- * @param {string} branchPath
- */
 function getBranchNameWithoutRefsPrefix(branchPath?: string) {
   if (!branchPath) {
     logger.error(`getBranchNameWithoutRefsPrefix(${branchPath})`);
@@ -48,11 +40,6 @@ function getBranchNameWithoutRefsPrefix(branchPath?: string) {
   return branchPath.substring(5, branchPath.length);
 }
 
-/**
- *
- * @param {string} repoId
- * @param {string} branchName
- */
 export async function getRefs(repoId: string, branchName?: string) {
   logger.debug(`getRefs(${repoId}, ${branchName})`);
   const azureApiGit = await azureApi.gitApi();
@@ -64,12 +51,6 @@ export async function getRefs(repoId: string, branchName?: string) {
   return refs;
 }
 
-/**
- *
- * @param repoId
- * @param branchName
- * @param from
- */
 export async function getAzureBranchObj(
   repoId: string,
   branchName: string,
@@ -119,12 +100,7 @@ export async function getChanges(
   return changes;
 }
 
-/**
- * if no branchName, look globaly
- * @param {string} repoId
- * @param {string} filePath
- * @param {string} branchName
- */
+// if no branchName, look globaly
 export async function getFile(
   repoId: string,
   filePath: string,
@@ -182,10 +158,6 @@ async function streamToString(stream: NodeJS.ReadableStream) {
   return p;
 }
 
-/**
- *
- * @param {string} str
- */
 export function max4000Chars(str: string) {
   if (str && str.length >= 4000) {
     return str.substring(0, 3999);
@@ -248,10 +220,6 @@ export async function getCommitDetails(commit: string, repoId: string) {
   return results;
 }
 
-/**
- *
- * @param {string} str
- */
 export function getProjectAndRepo(str: string) {
   logger.trace(`getProjectAndRepo(${str})`);
   const strSplited = str.split(`/`);
@@ -271,3 +239,24 @@ export function getProjectAndRepo(str: string) {
   logger.error(msg);
   throw new Error(msg);
 }
+
+export async function getMergeMethod(
+  repoId: string,
+  project: string
+): Promise<GitPullRequestMergeStrategy> {
+  const policyConfigurations = (await (await azureApi.policyApi()).getPolicyConfigurations(
+    project
+  ))
+    .filter(
+      p =>
+        p.settings.scope.some(s => s.repositoryId === repoId) &&
+        p.type.id === mergePolicyGuid
+    )
+    .map(p => p.settings)[0];
+
+  return (
+    Object.keys(policyConfigurations)
+      .map(p => GitPullRequestMergeStrategy[p.slice(5)])
+      .find(p => p) || GitPullRequestMergeStrategy.NoFastForward
+  );
+}
diff --git a/lib/platform/azure/index.ts b/lib/platform/azure/index.ts
index 677bb7f05e..367cb03d2b 100644
--- a/lib/platform/azure/index.ts
+++ b/lib/platform/azure/index.ts
@@ -1,3 +1,5 @@
+import { GitPullRequestMergeStrategy } from 'azure-devops-node-api/interfaces/GitInterfaces';
+
 import * as azureHelper from './azure-helper';
 import * as azureApi from './azure-got-wrapper';
 import * as hostRules from '../../util/host-rules';
@@ -11,7 +13,7 @@ import { smartTruncate } from '../utils/pr-body';
 interface Config {
   storage: GitStorage;
   repoForceRebase: boolean;
-  mergeMethod: string;
+  mergeMethod: GitPullRequestMergeStrategy;
   baseCommitSHA: string | undefined;
   baseBranch: string;
   defaultBranch: string;
@@ -86,7 +88,7 @@ export async function initRepo({
   config.baseBranch = config.defaultBranch;
   logger.debug(`${repository} default branch = ${config.defaultBranch}`);
   config.baseCommitSHA = await getBranchCommit(config.baseBranch);
-  config.mergeMethod = 'merge';
+  config.mergeMethod = await azureHelper.getMergeMethod(repo.id, names.project);
   config.repoForceRebase = false;
 
   if (optimizeForDisabled) {
@@ -379,7 +381,7 @@ export async function createPr(
           id: pr.createdBy!.id,
         },
         completionOptions: {
-          squashMerge: true,
+          mergeStrategy: config.mergeMethod,
           deleteSourceBranch: true,
         },
       },
@@ -535,7 +537,7 @@ export async function addAssignees(issueNo: number, assignees: string[]) {
 export async function addReviewers(prNo: number, reviewers: string[]) {
   logger.trace(`addReviewers(${prNo}, ${reviewers})`);
   const azureApiGit = await azureApi.gitApi();
-  const azureApiCore = await azureApi.getCoreApi();
+  const azureApiCore = await azureApi.coreApi();
   const repos = await azureApiGit.getRepositories();
   const repo = repos.filter(c => c.id === config.repoId)[0];
   const teams = await azureApiCore.getTeams(repo!.project!.id!);
diff --git a/test/platform/azure/azure-got-wrapper.spec.ts b/test/platform/azure/azure-got-wrapper.spec.ts
index 1350735b6e..85a2c4b1f0 100644
--- a/test/platform/azure/azure-got-wrapper.spec.ts
+++ b/test/platform/azure/azure-got-wrapper.spec.ts
@@ -13,7 +13,8 @@ describe('platform/azure/azure-got-wrapper', () => {
   describe('gitApi', () => {
     it('should throw an error if no token is provided', () => {
       expect(azure.gitApi).toThrow('No token found for azure');
-      expect(azure.getCoreApi).toThrow('No token found for azure');
+      expect(azure.coreApi).toThrow('No token found for azure');
+      expect(azure.policyApi).toThrow('No token found for azure');
     });
     it('should set token and endpoint', async () => {
       hostRules.add({
diff --git a/test/platform/azure/azure-helper.spec.ts b/test/platform/azure/azure-helper.spec.ts
index e3bb4ea18b..2fe6cd9503 100644
--- a/test/platform/azure/azure-helper.spec.ts
+++ b/test/platform/azure/azure-helper.spec.ts
@@ -1,4 +1,5 @@
 import { Readable } from 'stream';
+import { GitPullRequestMergeStrategy } from 'azure-devops-node-api/interfaces/GitInterfaces';
 
 describe('platform/azure/helpers', () => {
   let azureHelper: typeof import('../../../lib/platform/azure/azure-helper');
@@ -342,4 +343,56 @@ describe('platform/azure/helpers', () => {
       );
     });
   });
+
+  describe('getMergeMethod', () => {
+    it('should default to NoFastForward', async () => {
+      azureApi.policyApi.mockImplementationOnce(
+        () =>
+          ({
+            getPolicyConfigurations: jest.fn(() => [
+              {
+                settings: {
+                  scope: [
+                    {
+                      repositoryId: '',
+                    },
+                  ],
+                },
+                type: {
+                  id: 'fa4e907d-c16b-4a4c-9dfa-4916e5d171ab',
+                },
+              },
+            ]),
+          } as any)
+      );
+      expect(await azureHelper.getMergeMethod('', '')).toEqual(
+        GitPullRequestMergeStrategy.NoFastForward
+      );
+    });
+    it('should return Squash', async () => {
+      azureApi.policyApi.mockImplementationOnce(
+        () =>
+          ({
+            getPolicyConfigurations: jest.fn(() => [
+              {
+                settings: {
+                  allowSquash: true,
+                  scope: [
+                    {
+                      repositoryId: '',
+                    },
+                  ],
+                },
+                type: {
+                  id: 'fa4e907d-c16b-4a4c-9dfa-4916e5d171ab',
+                },
+              },
+            ]),
+          } as any)
+      );
+      expect(await azureHelper.getMergeMethod('', '')).toEqual(
+        GitPullRequestMergeStrategy.Squash
+      );
+    });
+  });
 });
diff --git a/test/platform/azure/index.spec.ts b/test/platform/azure/index.spec.ts
index e93d28124d..9215c91b3c 100644
--- a/test/platform/azure/index.spec.ts
+++ b/test/platform/azure/index.spec.ts
@@ -622,7 +622,7 @@ describe('platform/azure', () => {
             createPullRequestReviewer: jest.fn(),
           } as any)
       );
-      azureApi.getCoreApi.mockImplementation(
+      azureApi.coreApi.mockImplementation(
         () =>
           ({
             getTeams: jest.fn(() => [
-- 
GitLab