From 5511f33d06a39902702a2dbf1de01c6837f2763c Mon Sep 17 00:00:00 2001
From: Philip <42116482+PhilipAbed@users.noreply.github.com>
Date: Tue, 2 May 2023 11:54:49 +0300
Subject: [PATCH] feat: checkedBranches (#21845)

Co-authored-by: Rhys Arkins <rhys@arkins.net>
Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>
---
 docs/usage/self-hosted-configuration.md       |  9 +++++++
 lib/config/options/index.ts                   | 10 ++++++++
 lib/config/types.ts                           |  2 ++
 .../repository/dependency-dashboard.spec.ts   | 25 +++++++++++++++++++
 .../repository/dependency-dashboard.ts        | 17 ++++++++++++-
 .../repository/update/branch/index.spec.ts    | 21 ++++++++++++++++
 lib/workers/repository/update/branch/index.ts |  3 +++
 7 files changed, 86 insertions(+), 1 deletion(-)

diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md
index e0fb0cc5ed..fcf72c943a 100644
--- a/docs/usage/self-hosted-configuration.md
+++ b/docs/usage/self-hosted-configuration.md
@@ -219,6 +219,15 @@ Results which are soft expired are reused in the following manner:
 - The `etag` from the cached results will be reused, and may result in a 304 response, meaning cached results are revalidated
 - If an error occurs when querying the `npmjs` registry, then soft expired results will be reused if they are present
 
+## checkedBranches
+
+This array will allow you to set the names of the branches you want to rebase/create, as if you selected their checkboxes in the Dependency Dashboard issue.
+
+It has been designed with the intention of being run on one repository, in a one-off manner, e.g. to "force" the rebase of a known existing branch.
+It is highly unlikely that you should ever need to add this to your permanent global config.
+
+Example: `renovate --checked-branches=renovate/chalk-4.x renovate-reproductions/checked` will rebase the `renovate/chalk-4.x` branch in the `renovate-reproductions/checked` repository.`
+
 ## containerbaseDir
 
 This directory is used to cache downloads when `binarySource=docker` or `binarySource=install`.
diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts
index 60153f1ef7..a020d9dae7 100644
--- a/lib/config/options/index.ts
+++ b/lib/config/options/index.ts
@@ -2624,6 +2624,16 @@ const options: RenovateOptions[] = [
     type: 'boolean',
     default: false,
   },
+  {
+    name: 'checkedBranches',
+    description:
+      'A list of branch names to mark for creation or rebasing as if it was selected in the Dependency Dashboard issue.',
+    type: 'array',
+    subType: 'string',
+    experimental: true,
+    globalOnly: true,
+    default: [],
+  },
 ];
 
 export function getOptions(): RenovateOptions[] {
diff --git a/lib/config/types.ts b/lib/config/types.ts
index aedf88d623..0df00b27a9 100644
--- a/lib/config/types.ts
+++ b/lib/config/types.ts
@@ -260,6 +260,8 @@ export interface RenovateConfig
   skipInstalls?: boolean | null;
 
   constraintsFiltering?: ConstraintsFilter;
+
+  checkedBranches?: string[];
 }
 
 export interface AllConfig
diff --git a/lib/workers/repository/dependency-dashboard.spec.ts b/lib/workers/repository/dependency-dashboard.spec.ts
index 6170c3dcdc..a2d9b97782 100644
--- a/lib/workers/repository/dependency-dashboard.spec.ts
+++ b/lib/workers/repository/dependency-dashboard.spec.ts
@@ -104,6 +104,31 @@ describe('workers/repository/dependency-dashboard', () => {
       });
     });
 
+    it('reads dashboard body and apply checkedBranches', async () => {
+      const conf: RenovateConfig = {};
+      conf.prCreation = 'approval';
+      conf.checkedBranches = ['branch1', 'branch2'];
+      platform.findIssue.mockResolvedValueOnce({
+        title: '',
+        number: 1,
+        body: Fixtures.get('dependency-dashboard-with-8-PR.txt'),
+      });
+      await dependencyDashboard.readDashboardBody(conf);
+      expect(conf).toEqual({
+        checkedBranches: ['branch1', 'branch2'],
+        dependencyDashboardAllPending: false,
+        dependencyDashboardAllRateLimited: false,
+        dependencyDashboardChecks: {
+          branch1: 'global-config',
+          branch2: 'global-config',
+        },
+        dependencyDashboardIssue: 1,
+        dependencyDashboardRebaseAllOpen: false,
+        dependencyDashboardTitle: 'Dependency Dashboard',
+        prCreation: 'approval',
+      });
+    });
+
     it('reads dashboard body all pending approval', async () => {
       const conf: RenovateConfig = {};
       conf.prCreation = 'approval';
diff --git a/lib/workers/repository/dependency-dashboard.ts b/lib/workers/repository/dependency-dashboard.ts
index ac00cdd0fe..6054f0e58e 100644
--- a/lib/workers/repository/dependency-dashboard.ts
+++ b/lib/workers/repository/dependency-dashboard.ts
@@ -113,7 +113,22 @@ export async function readDashboardBody(
     const issue = await platform.findIssue(config.dependencyDashboardTitle);
     if (issue) {
       config.dependencyDashboardIssue = issue.number;
-      Object.assign(config, parseDashboardIssue(issue.body!));
+      const dashboardChecks = parseDashboardIssue(issue.body!);
+
+      if (config.checkedBranches) {
+        const checkedBranchesRec: Record<string, string> = Object.fromEntries(
+          config.checkedBranches.map((branchName) => [
+            branchName,
+            'global-config',
+          ])
+        );
+        dashboardChecks.dependencyDashboardChecks = {
+          ...dashboardChecks.dependencyDashboardChecks,
+          ...checkedBranchesRec,
+        };
+      }
+
+      Object.assign(config, dashboardChecks);
     }
   }
 }
diff --git a/lib/workers/repository/update/branch/index.spec.ts b/lib/workers/repository/update/branch/index.spec.ts
index 7609eab2d2..636803ec39 100644
--- a/lib/workers/repository/update/branch/index.spec.ts
+++ b/lib/workers/repository/update/branch/index.spec.ts
@@ -2015,6 +2015,27 @@ describe('workers/repository/update/branch/index', () => {
       expect(commit.commitFilesToBranch).toHaveBeenCalled();
     });
 
+    it('continues when checked by checkedBranches', async () => {
+      getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce(
+        updatedPackageFiles
+      );
+      npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({
+        artifactErrors: [],
+        updatedArtifacts: [],
+      });
+      scm.branchExists.mockResolvedValue(true);
+      commit.commitFilesToBranch.mockResolvedValueOnce(null);
+      expect(
+        await branchWorker.processBranch({
+          ...config,
+          dependencyDashboardChecks: {
+            'renovate/some-branch': 'global-config',
+          },
+        })
+      ).toMatchObject({ result: 'done' });
+      expect(commit.commitFilesToBranch).toHaveBeenCalled();
+    });
+
     it('does nothing when branchPrefixOld/branch and its pr exists', async () => {
       getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({
         ...updatedPackageFiles,
diff --git a/lib/workers/repository/update/branch/index.ts b/lib/workers/repository/update/branch/index.ts
index 81c8c7b758..ba93eee78e 100644
--- a/lib/workers/repository/update/branch/index.ts
+++ b/lib/workers/repository/update/branch/index.ts
@@ -367,6 +367,9 @@ export async function processBranch(
     if (userRebaseRequested) {
       logger.debug('Manual rebase requested via Dependency Dashboard');
       config.reuseExistingBranch = false;
+    } else if (dependencyDashboardCheck === 'global-config') {
+      logger.debug(`Manual create/rebase requested via checkedBranches`);
+      config.reuseExistingBranch = false;
     } else if (userApproveAllPendingPR) {
       logger.debug(
         'A user manually approved all pending PRs via the Dependency Dashboard.'
-- 
GitLab