From 4180370e01837aeb21d8ce144f88aab610570d8b Mon Sep 17 00:00:00 2001
From: RahulGautamSingh <rahultesnik@gmail.com>
Date: Sat, 1 Jul 2023 20:43:34 +0545
Subject: [PATCH] feat(platform): update PR target branch if baseBranch changed
 (#23010)

---
 .../azure/__snapshots__/index.spec.ts.snap    | 32 +++++++++++--------
 lib/modules/platform/azure/index.spec.ts      |  1 +
 lib/modules/platform/azure/index.ts           |  6 +++-
 .../platform/bitbucket-server/index.spec.ts   |  1 +
 .../platform/bitbucket-server/index.ts        |  6 +++-
 lib/modules/platform/bitbucket/index.spec.ts  |  7 +++-
 lib/modules/platform/bitbucket/index.ts       |  6 ++++
 lib/modules/platform/gitea/index.spec.ts      | 16 ++++++++++
 lib/modules/platform/gitea/index.ts           |  2 ++
 lib/modules/platform/gitea/types.ts           |  2 +-
 lib/modules/platform/gitlab/index.spec.ts     | 29 +++++++++++++++++
 lib/modules/platform/gitlab/index.ts          |  2 ++
 12 files changed, 92 insertions(+), 18 deletions(-)

diff --git a/lib/modules/platform/azure/__snapshots__/index.spec.ts.snap b/lib/modules/platform/azure/__snapshots__/index.spec.ts.snap
index 2ca73528d8..dabb70a924 100644
--- a/lib/modules/platform/azure/__snapshots__/index.spec.ts.snap
+++ b/lib/modules/platform/azure/__snapshots__/index.spec.ts.snap
@@ -30,22 +30,15 @@ exports[`modules/platform/azure/index createPr() should create and return a PR o
 }
 `;
 
-exports[`modules/platform/azure/index createPr() when usePlatformAutomerge is set should create and return a PR object with auto-complete set 1`] = `
+exports[`modules/platform/azure/index createPr() should create and return an approved PR object 1`] = `
 {
-  "autoCompleteSetBy": {
-    "id": "123",
-  },
   "bodyStruct": {
     "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
   },
-  "completionOptions": {
-    "deleteSourceBranch": true,
-    "mergeCommitMessage": "The Title",
-    "squashMerge": true,
-  },
   "createdAt": undefined,
   "createdBy": {
-    "id": "123",
+    "id": 123,
+    "url": "user-url",
   },
   "number": 456,
   "pullRequestId": 456,
@@ -53,19 +46,25 @@ exports[`modules/platform/azure/index createPr() when usePlatformAutomerge is se
   "sourceRefName": undefined,
   "state": "open",
   "targetBranch": undefined,
-  "title": "The Title",
 }
 `;
 
-exports[`modules/platform/azure/index createPr() should create and return an approved PR object 1`] = `
+exports[`modules/platform/azure/index createPr() when usePlatformAutomerge is set should create and return a PR object with auto-complete set 1`] = `
 {
+  "autoCompleteSetBy": {
+    "id": "123",
+  },
   "bodyStruct": {
     "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
   },
+  "completionOptions": {
+    "deleteSourceBranch": true,
+    "mergeCommitMessage": "The Title",
+    "squashMerge": true,
+  },
   "createdAt": undefined,
   "createdBy": {
-    "id": 123,
-    "url": "user-url",
+    "id": "123",
   },
   "number": 456,
   "pullRequestId": 456,
@@ -73,6 +72,7 @@ exports[`modules/platform/azure/index createPr() should create and return an app
   "sourceRefName": undefined,
   "state": "open",
   "targetBranch": undefined,
+  "title": "The Title",
 }
 `;
 
@@ -280,6 +280,7 @@ exports[`modules/platform/azure/index updatePr(prNo, title, body, platformOption
     {
       "description": "Hello world again",
       "status": 2,
+      "targetRefName": undefined,
       "title": "The New Title",
     },
     "1",
@@ -302,6 +303,7 @@ exports[`modules/platform/azure/index updatePr(prNo, title, body, platformOption
   [
     {
       "description": "Hello world again",
+      "targetRefName": undefined,
       "title": "The New Title",
     },
     "1",
@@ -315,6 +317,7 @@ exports[`modules/platform/azure/index updatePr(prNo, title, body, platformOption
   [
     {
       "description": "Hello world again",
+      "targetRefName": "refs/heads/new_base",
       "title": "The New Title",
     },
     "1",
@@ -327,6 +330,7 @@ exports[`modules/platform/azure/index updatePr(prNo, title, body, platformOption
 [
   [
     {
+      "targetRefName": undefined,
       "title": "The New Title - autoclose",
     },
     "1",
diff --git a/lib/modules/platform/azure/index.spec.ts b/lib/modules/platform/azure/index.spec.ts
index 440bc9d1df..8ea5ac8f83 100644
--- a/lib/modules/platform/azure/index.spec.ts
+++ b/lib/modules/platform/azure/index.spec.ts
@@ -906,6 +906,7 @@ describe('modules/platform/azure/index', () => {
         number: 1234,
         prTitle: 'The New Title',
         prBody: 'Hello world again',
+        targetBranch: 'new_base',
       });
       expect(updatePullRequest.mock.calls).toMatchSnapshot();
     });
diff --git a/lib/modules/platform/azure/index.ts b/lib/modules/platform/azure/index.ts
index 37091f45a6..256097cebf 100644
--- a/lib/modules/platform/azure/index.ts
+++ b/lib/modules/platform/azure/index.ts
@@ -514,12 +514,14 @@ export async function updatePr({
   prBody: body,
   state,
   platformOptions,
+  targetBranch,
 }: UpdatePrConfig): Promise<void> {
   logger.debug(`updatePr(${prNo}, ${title}, body)`);
 
   const azureApiGit = await azureApi.gitApi();
   const objToUpdate: GitPullRequest = {
     title,
+    targetRefName: getNewBranchName(targetBranch),
   };
 
   if (body) {
@@ -528,7 +530,9 @@ export async function updatePr({
 
   if (state === 'open') {
     await azureApiGit.updatePullRequest(
-      { status: PullRequestStatus.Active },
+      {
+        status: PullRequestStatus.Active,
+      },
       config.repoId,
       prNo
     );
diff --git a/lib/modules/platform/bitbucket-server/index.spec.ts b/lib/modules/platform/bitbucket-server/index.spec.ts
index 763d837ecf..5ab4eb98b5 100644
--- a/lib/modules/platform/bitbucket-server/index.spec.ts
+++ b/lib/modules/platform/bitbucket-server/index.spec.ts
@@ -1417,6 +1417,7 @@ describe('modules/platform/bitbucket-server/index', () => {
               number: 5,
               prTitle: 'title',
               prBody: 'body',
+              targetBranch: 'new_base',
             })
           ).toResolve();
         });
diff --git a/lib/modules/platform/bitbucket-server/index.ts b/lib/modules/platform/bitbucket-server/index.ts
index 43c44bf84c..ae3436d707 100644
--- a/lib/modules/platform/bitbucket-server/index.ts
+++ b/lib/modules/platform/bitbucket-server/index.ts
@@ -37,7 +37,7 @@ import type {
   RepoResult,
   UpdatePrConfig,
 } from '../types';
-import { repoFingerprint } from '../util';
+import { getNewBranchName, repoFingerprint } from '../util';
 import { smartTruncate } from '../utils/pr-body';
 import type {
   BbsConfig,
@@ -851,6 +851,7 @@ export async function updatePr({
   prBody: rawDescription,
   state,
   bitbucketInvalidReviewers,
+  targetBranch,
 }: UpdatePrConfig & {
   bitbucketInvalidReviewers: string[] | undefined;
 }): Promise<void> {
@@ -878,6 +879,9 @@ export async function updatePr({
               (name: string) => !bitbucketInvalidReviewers?.includes(name)
             )
             .map((name: string) => ({ user: { name } })),
+          toRef: {
+            id: getNewBranchName(targetBranch),
+          },
         },
       }
     );
diff --git a/lib/modules/platform/bitbucket/index.spec.ts b/lib/modules/platform/bitbucket/index.spec.ts
index 40a37e4bf1..44d4cc8afc 100644
--- a/lib/modules/platform/bitbucket/index.spec.ts
+++ b/lib/modules/platform/bitbucket/index.spec.ts
@@ -1225,7 +1225,12 @@ describe('modules/platform/bitbucket/index', () => {
         .put('/2.0/repositories/some/repo/pullrequests/5')
         .reply(200);
       await expect(
-        bitbucket.updatePr({ number: 5, prTitle: 'title', prBody: 'body' })
+        bitbucket.updatePr({
+          number: 5,
+          prTitle: 'title',
+          prBody: 'body',
+          targetBranch: 'new_base',
+        })
       ).toResolve();
     });
 
diff --git a/lib/modules/platform/bitbucket/index.ts b/lib/modules/platform/bitbucket/index.ts
index 3bfee11463..81bd8c2341 100644
--- a/lib/modules/platform/bitbucket/index.ts
+++ b/lib/modules/platform/bitbucket/index.ts
@@ -905,6 +905,7 @@ export async function updatePr({
   prTitle: title,
   prBody: description,
   state,
+  targetBranch,
 }: UpdatePrConfig): Promise<void> {
   logger.debug(`updatePr(${prNo}, ${title}, body)`);
   // Updating a PR in Bitbucket will clear the reviewers if reviewers is not present
@@ -922,6 +923,11 @@ export async function updatePr({
           title,
           description: sanitize(description),
           reviewers: pr.reviewers,
+          destination: {
+            branch: {
+              name: targetBranch,
+            },
+          },
         },
       }
     );
diff --git a/lib/modules/platform/gitea/index.spec.ts b/lib/modules/platform/gitea/index.spec.ts
index a306b76a7b..cb010cd765 100644
--- a/lib/modules/platform/gitea/index.spec.ts
+++ b/lib/modules/platform/gitea/index.spec.ts
@@ -1230,6 +1230,22 @@ describe('modules/platform/gitea/index', () => {
       });
     });
 
+    it('should update pull target branch', async () => {
+      helper.searchPRs.mockResolvedValueOnce(mockPRs);
+      await initFakeRepo();
+      await gitea.updatePr({
+        number: 1,
+        prTitle: 'New Title',
+        targetBranch: 'New Base',
+      });
+
+      expect(helper.updatePR).toHaveBeenCalledTimes(1);
+      expect(helper.updatePR).toHaveBeenCalledWith(mockRepo.full_name, 1, {
+        title: 'New Title',
+        base: 'New Base',
+      });
+    });
+
     it('should update pull request with title and body', async () => {
       helper.searchPRs.mockResolvedValueOnce(mockPRs);
       await initFakeRepo();
diff --git a/lib/modules/platform/gitea/index.ts b/lib/modules/platform/gitea/index.ts
index cc1fdfda04..32f23555b8 100644
--- a/lib/modules/platform/gitea/index.ts
+++ b/lib/modules/platform/gitea/index.ts
@@ -622,6 +622,7 @@ const platform: Platform = {
     prTitle,
     prBody: body,
     state,
+    targetBranch,
   }: UpdatePrConfig): Promise<void> {
     let title = prTitle;
     if ((await getPrList()).find((pr) => pr.number === number)?.isDraft) {
@@ -630,6 +631,7 @@ const platform: Platform = {
 
     await helper.updatePR(config.repository, number, {
       title,
+      base: targetBranch,
       ...(body && { body }),
       ...(state && { state }),
     });
diff --git a/lib/modules/platform/gitea/types.ts b/lib/modules/platform/gitea/types.ts
index de89f4f46a..bc6d78fc8c 100644
--- a/lib/modules/platform/gitea/types.ts
+++ b/lib/modules/platform/gitea/types.ts
@@ -169,7 +169,6 @@ export interface IssueSearchParams {
 }
 
 export interface PRCreateParams extends PRUpdateParams {
-  base?: string;
   head?: string;
 }
 
@@ -179,6 +178,7 @@ export interface PRUpdateParams {
   assignees?: string[];
   labels?: number[];
   state?: PRState;
+  base?: string;
 }
 
 export interface PRSearchParams {
diff --git a/lib/modules/platform/gitlab/index.spec.ts b/lib/modules/platform/gitlab/index.spec.ts
index 0530d65e25..bc2cf66c47 100644
--- a/lib/modules/platform/gitlab/index.spec.ts
+++ b/lib/modules/platform/gitlab/index.spec.ts
@@ -2190,6 +2190,35 @@ describe('modules/platform/gitlab/index', () => {
       ).toResolve();
     });
 
+    it('updates target branch of the PR', async () => {
+      await initPlatform('13.3.6-ee');
+      httpMock
+        .scope(gitlabApiHost)
+        .get(
+          '/api/v4/projects/undefined/merge_requests?per_page=100&scope=created_by_me'
+        )
+        .reply(200, [
+          {
+            iid: 1,
+            source_branch: 'branch-a',
+            title: 'branch a pr',
+            state: 'open',
+            target_branch: 'branch-b',
+          },
+        ])
+        .put('/api/v4/projects/undefined/merge_requests/1')
+        .reply(200);
+      await expect(
+        gitlab.updatePr({
+          number: 1,
+          prTitle: 'title',
+          prBody: 'body',
+          state: 'closed',
+          targetBranch: 'branch-b',
+        })
+      ).toResolve();
+    });
+
     it('closes the PR', async () => {
       await initPlatform('13.3.6-ee');
       httpMock
diff --git a/lib/modules/platform/gitlab/index.ts b/lib/modules/platform/gitlab/index.ts
index 88fbf9f1f0..0580175dea 100644
--- a/lib/modules/platform/gitlab/index.ts
+++ b/lib/modules/platform/gitlab/index.ts
@@ -701,6 +701,7 @@ export async function updatePr({
   prBody: description,
   state,
   platformOptions,
+  targetBranch,
 }: UpdatePrConfig): Promise<void> {
   let title = prTitle;
   if ((await getPrList()).find((pr) => pr.number === iid)?.isDraft) {
@@ -718,6 +719,7 @@ export async function updatePr({
         title,
         description: sanitize(description),
         ...(newState && { state_event: newState }),
+        target_branch: targetBranch,
       },
     }
   );
-- 
GitLab