From 01ceaeace4fcc2685dabf6347e87d0a28c3c4c03 Mon Sep 17 00:00:00 2001
From: Gabriel-Ladzaretti
 <97394622+Gabriel-Ladzaretti@users.noreply.github.com>
Date: Sun, 21 Aug 2022 08:33:50 +0300
Subject: [PATCH] feat(core/config): allow close to ignore for migration PRs
 (#16773)

---
 docs/usage/configuration-options.md           |  4 ++
 .../config-migration/branch/index.spec.ts     | 50 +++++++++++++
 .../config-migration/branch/index.ts          | 71 +++++++++++++++++--
 .../pr/__snapshots__/index.spec.ts.snap       |  8 +++
 .../repository/config-migration/pr/index.ts   |  2 +
 5 files changed, 131 insertions(+), 4 deletions(-)

diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md
index 4b904edbef..4c5bf2cb7c 100644
--- a/docs/usage/configuration-options.md
+++ b/docs/usage/configuration-options.md
@@ -454,6 +454,10 @@ After we changed the [`baseBranches`](https://docs.renovatebot.com/configuration
     This feature writes plain JSON for `.json` files, and JSON5 for `.json5` files.
     JSON5 content can potentially be down leveled (`.json` files) and all comments will be removed.
 
+<!-- prettier-ignore -->
+!!! note
+    Closing the config migration PR will cause it to be ignored and not being reopend/recreated in the future.',
+
 ## configWarningReuseIssue
 
 Renovate's default behavior is to reuse/reopen a single Config Warning issue in each repository so as to keep the "noise" down.
diff --git a/lib/workers/repository/config-migration/branch/index.spec.ts b/lib/workers/repository/config-migration/branch/index.spec.ts
index 9c35ae38d6..dc07e94174 100644
--- a/lib/workers/repository/config-migration/branch/index.spec.ts
+++ b/lib/workers/repository/config-migration/branch/index.spec.ts
@@ -5,11 +5,13 @@ import {
   getConfig,
   git,
   mockedFunction,
+  partial,
   platform,
 } from '../../../../../test/util';
 import { GlobalConfig } from '../../../../config/global';
 import { logger } from '../../../../logger';
 import type { Pr } from '../../../../modules/platform';
+import { PrState } from '../../../../types';
 import { createConfigMigrationBranch } from './create';
 import type { MigratedData } from './migrated-data';
 import { rebaseMigrationBranch } from './rebase';
@@ -19,6 +21,7 @@ jest.mock('./migrated-data');
 jest.mock('./rebase');
 jest.mock('./create');
 jest.mock('../../../../util/git');
+jest.mock('../../update/branch/handle-existing');
 
 const migratedData = Fixtures.getJson<MigratedData>('./migrated-data.json');
 
@@ -98,5 +101,52 @@ describe('workers/repository/config-migration/branch/index', () => {
       expect(git.checkoutBranch).toHaveBeenCalledTimes(0);
       expect(git.commitFiles).toHaveBeenCalledTimes(0);
     });
+
+    describe('handle closed PR', () => {
+      const title = 'PR title';
+      const pr = partial<Pr>({ title, state: PrState.Closed, number: 1 });
+
+      it('skips branch when there is a closed one delete it and add an ignore PR message', async () => {
+        platform.findPr.mockResolvedValueOnce(pr);
+        platform.getBranchPr.mockResolvedValue(null);
+        git.branchExists.mockReturnValueOnce(true);
+        const res = await checkConfigMigrationBranch(config, migratedData);
+        expect(res).toBeNull();
+        expect(git.checkoutBranch).toHaveBeenCalledTimes(0);
+        expect(git.commitFiles).toHaveBeenCalledTimes(0);
+        expect(git.deleteBranch).toHaveBeenCalledTimes(1);
+        expect(logger.debug).toHaveBeenCalledWith(
+          { prTitle: title },
+          'Closed PR already exists. Skipping branch.'
+        );
+        expect(platform.ensureComment).toHaveBeenCalledTimes(1);
+        expect(platform.ensureComment).toHaveBeenCalledWith({
+          content:
+            '\n\nIf this PR was closed by mistake or you changed your mind, you can simply rename this PR and you will soon get a fresh replacement PR opened.',
+          topic: 'Renovate Ignore Notification',
+          number: 1,
+        });
+      });
+
+      it('dryrun: skips branch when there is a closed one and add an ignore PR message', async () => {
+        GlobalConfig.set({ dryRun: 'full' });
+        platform.findPr.mockResolvedValueOnce(pr);
+        platform.getBranchPr.mockResolvedValue(null);
+        git.branchExists.mockReturnValueOnce(true);
+        const res = await checkConfigMigrationBranch(config, migratedData);
+        expect(res).toBeNull();
+        expect(logger.info).toHaveBeenCalledWith(
+          `DRY-RUN: Would ensure closed PR comment in PR #${pr.number}`
+        );
+        expect(logger.info).toHaveBeenCalledWith(
+          'DRY-RUN: Would delete branch ' + pr.sourceBranch
+        );
+        expect(logger.debug).toHaveBeenCalledWith(
+          { prTitle: title },
+          'Closed PR already exists. Skipping branch.'
+        );
+        expect(platform.ensureComment).toHaveBeenCalledTimes(0);
+      });
+    });
   });
 });
diff --git a/lib/workers/repository/config-migration/branch/index.ts b/lib/workers/repository/config-migration/branch/index.ts
index 1149201eaa..2f0e14a3b4 100644
--- a/lib/workers/repository/config-migration/branch/index.ts
+++ b/lib/workers/repository/config-migration/branch/index.ts
@@ -1,9 +1,16 @@
 import { GlobalConfig } from '../../../../config/global';
 import type { RenovateConfig } from '../../../../config/types';
 import { logger } from '../../../../logger';
-import { platform } from '../../../../modules/platform';
-import { checkoutBranch } from '../../../../util/git';
+import { FindPRConfig, Pr, platform } from '../../../../modules/platform';
+import { ensureComment } from '../../../../modules/platform/comment';
+import { PrState } from '../../../../types';
+import {
+  branchExists,
+  checkoutBranch,
+  deleteBranch,
+} from '../../../../util/git';
 import { getMigrationBranchName } from '../common';
+import { ConfigMigrationCommitMessageFactory } from './commit-message';
 import { createConfigMigrationBranch } from './create';
 import type { MigratedData } from './migrated-data';
 import { rebaseMigrationBranch } from './rebase';
@@ -18,10 +25,38 @@ export async function checkConfigMigrationBranch(
     return null;
   }
   const configMigrationBranch = getMigrationBranchName(config);
-  if (await migrationPrExists(configMigrationBranch)) {
+
+  const branchPr = await migrationPrExists(configMigrationBranch); // handles open/autoClosed PRs
+
+  if (!branchPr) {
+    const commitMessageFactory = new ConfigMigrationCommitMessageFactory(
+      config,
+      migratedConfigData.filename
+    );
+    const prTitle = commitMessageFactory.getPrTitle();
+    const closedPrConfig: FindPRConfig = {
+      branchName: configMigrationBranch,
+      prTitle,
+      state: PrState.Closed,
+    };
+
+    // handles closed PR
+    const closedPr = await platform.findPr(closedPrConfig);
+
+    // found closed migration PR
+    if (closedPr) {
+      logger.debug(
+        { prTitle: closedPr.title },
+        'Closed PR already exists. Skipping branch.'
+      );
+      await handlepr(config, closedPr);
+      return null;
+    }
+  }
+
+  if (branchPr) {
     logger.debug('Config Migration PR already exists');
     await rebaseMigrationBranch(config, migratedConfigData);
-
     if (platform.refreshPr) {
       const configMigrationPr = await platform.getBranchPr(
         configMigrationBranch
@@ -44,3 +79,31 @@ export async function checkConfigMigrationBranch(
 export async function migrationPrExists(branchName: string): Promise<boolean> {
   return !!(await platform.getBranchPr(branchName));
 }
+
+async function handlepr(config: RenovateConfig, pr: Pr): Promise<void> {
+  if (
+    pr.state === PrState.Closed &&
+    !config.suppressNotifications!.includes('prIgnoreNotification')
+  ) {
+    if (GlobalConfig.get('dryRun')) {
+      logger.info(
+        `DRY-RUN: Would ensure closed PR comment in PR #${pr.number}`
+      );
+    } else {
+      const content =
+        '\n\nIf this PR was closed by mistake or you changed your mind, you can simply rename this PR and you will soon get a fresh replacement PR opened.';
+      await ensureComment({
+        number: pr.number,
+        topic: 'Renovate Ignore Notification',
+        content,
+      });
+    }
+    if (branchExists(pr.sourceBranch)) {
+      if (GlobalConfig.get('dryRun')) {
+        logger.info('DRY-RUN: Would delete branch ' + pr.sourceBranch);
+      } else {
+        await deleteBranch(pr.sourceBranch);
+      }
+    }
+  }
+}
diff --git a/lib/workers/repository/config-migration/pr/__snapshots__/index.spec.ts.snap b/lib/workers/repository/config-migration/pr/__snapshots__/index.spec.ts.snap
index aba3532b87..1d88bc780b 100644
--- a/lib/workers/repository/config-migration/pr/__snapshots__/index.spec.ts.snap
+++ b/lib/workers/repository/config-migration/pr/__snapshots__/index.spec.ts.snap
@@ -9,6 +9,8 @@ exports[`workers/repository/config-migration/pr/index ensureConfigMigrationPr()
 
 #### [PLEASE NOTE](https://docs.renovatebot.com/configuration-options#configmigration): JSON5 config file migrated! All comments & trailing commas were removed.
 
+🔕 **Ignore**: Close this PR and you won't be reminded about config migration again, but one day your current config may no longer be valid.
+
 ❓ Got questions? Does something look wrong to you? Please don't hesitate to [request help here](https://github.com/renovatebot/renovate/discussions).
 
 
@@ -29,6 +31,8 @@ The Renovate config in this repository needs migrating. Typically this is becaus
 
 
 
+🔕 **Ignore**: Close this PR and you won't be reminded about config migration again, but one day your current config may no longer be valid.
+
 ❓ Got questions? Does something look wrong to you? Please don't hesitate to [request help here](https://github.com/renovatebot/renovate/discussions).
 
 
@@ -49,6 +53,8 @@ The Renovate config in this repository needs migrating. Typically this is becaus
 
 
 
+🔕 **Ignore**: Close this PR and you won't be reminded about config migration again, but one day your current config may no longer be valid.
+
 ❓ Got questions? Does something look wrong to you? Please don't hesitate to [request help here](https://github.com/renovatebot/renovate/discussions).
 
 
@@ -71,6 +77,8 @@ The Renovate config in this repository needs migrating. Typically this is becaus
 
 
 
+🔕 **Ignore**: Close this PR and you won't be reminded about config migration again, but one day your current config may no longer be valid.
+
 ❓ Got questions? Does something look wrong to you? Please don't hesitate to [request help here](https://github.com/renovatebot/renovate/discussions).
 
 
diff --git a/lib/workers/repository/config-migration/pr/index.ts b/lib/workers/repository/config-migration/pr/index.ts
index ac85b8de89..d66287fdd6 100644
--- a/lib/workers/repository/config-migration/pr/index.ts
+++ b/lib/workers/repository/config-migration/pr/index.ts
@@ -47,6 +47,8 @@ ${
     : ''
 }
 
+:no_bell: **Ignore**: Close this PR and you won't be reminded about config migration again, but one day your current config may no longer be valid.
+
 :question: Got questions? Does something look wrong to you? Please don't hesitate to [request help here](${
       // TODO: types (#7154)
       // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-- 
GitLab