From c3fd5c2edf0e8bedfe3c38c2684866741b93b642 Mon Sep 17 00:00:00 2001
From: Jonathan Narwold <jonnarwold+github@gmail.com>
Date: Fri, 1 Jul 2022 04:49:23 -0400
Subject: [PATCH] feat(bundler): support conservative mode (#16129)

---
 docs/usage/configuration-options.md           |  1 +
 lib/config/options/index.ts                   |  1 +
 lib/modules/manager/bundler/artifacts.spec.ts | 30 +++++++++++++++++++
 lib/modules/manager/bundler/artifacts.ts      |  9 +++++-
 4 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md
index cec8095deb..94c6e74e0c 100644
--- a/docs/usage/configuration-options.md
+++ b/docs/usage/configuration-options.md
@@ -1924,6 +1924,7 @@ This way Renovate can use GitHub's [Commit signing support for bots and other Gi
 
 ## postUpdateOptions
 
+- `bundlerConservative`: Enable conservative mode for `bundler` (Ruby dependencies). This will only update the immediate dependency in the lockfile instead of all subdependencies
 - `gomodMassage`: Enable massaging `replace` directives before calling `go` commands
 - `gomodTidy`: Run `go mod tidy` after Go module updates. This is implicitly enabled for major module updates when `gomodUpdateImportPaths` is enabled
 - `gomodTidy1.17`: Run `go mod tidy -compat=1.17` after Go module updates.
diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts
index 4f4920eded..37bb30e434 100644
--- a/lib/config/options/index.ts
+++ b/lib/config/options/index.ts
@@ -1853,6 +1853,7 @@ const options: RenovateOptions[] = [
     type: 'array',
     default: [],
     allowedValues: [
+      'bundlerConservative',
       'gomodMassage',
       'gomodUpdateImportPaths',
       'gomodTidy',
diff --git a/lib/modules/manager/bundler/artifacts.spec.ts b/lib/modules/manager/bundler/artifacts.spec.ts
index 47807c32bb..46fe6e91a7 100644
--- a/lib/modules/manager/bundler/artifacts.spec.ts
+++ b/lib/modules/manager/bundler/artifacts.spec.ts
@@ -128,6 +128,36 @@ describe('modules/manager/bundler/artifacts', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
 
+  it('supports conservative mode', async () => {
+    fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock');
+    fs.writeLocalFile.mockResolvedValueOnce();
+    fs.readLocalFile.mockResolvedValueOnce(null);
+    const execSnapshots = mockExecAll(exec);
+    git.getRepoStatus.mockResolvedValueOnce({
+      modified: ['Gemfile.lock'],
+    } as StatusResult);
+    fs.readLocalFile.mockResolvedValueOnce('Updated Gemfile.lock');
+    expect(
+      await updateArtifacts({
+        packageFileName: 'Gemfile',
+        updatedDeps: [{ depName: 'foo' }, { depName: 'bar' }],
+        newPackageFileContent: 'Updated Gemfile content',
+        config: {
+          ...config,
+          postUpdateOptions: [
+            ...(config.postUpdateOptions ?? []),
+            'bundlerConservative',
+          ],
+        },
+      })
+    ).toEqual([updatedGemfileLock]);
+    expect(execSnapshots).toMatchObject([
+      expect.objectContaining({
+        cmd: 'bundler lock --conservative --update foo bar',
+      }),
+    ]);
+  });
+
   describe('Docker', () => {
     beforeEach(() => {
       GlobalConfig.set({
diff --git a/lib/modules/manager/bundler/artifacts.ts b/lib/modules/manager/bundler/artifacts.ts
index f628afb66a..495260a1c5 100644
--- a/lib/modules/manager/bundler/artifacts.ts
+++ b/lib/modules/manager/bundler/artifacts.ts
@@ -1,4 +1,5 @@
 import { lt } from '@renovatebot/ruby-semver';
+import is from '@sindresorhus/is';
 import { quote } from 'shlex';
 import {
   BUNDLER_INVALID_CREDENTIALS,
@@ -61,6 +62,12 @@ export async function updateArtifacts(
     return null;
   }
 
+  const args = [
+    config.postUpdateOptions?.includes('bundlerConservative') &&
+      '--conservative',
+    '--update',
+  ].filter(is.nonEmptyString);
+
   try {
     await writeLocalFile(packageFileName, newPackageFileContent);
 
@@ -69,7 +76,7 @@ export async function updateArtifacts(
     if (config.isLockFileMaintenance) {
       cmd = 'bundler lock --update';
     } else {
-      cmd = `bundler lock --update ${updatedDeps
+      cmd = `bundler lock ${args.join(' ')} ${updatedDeps
         .map((dep) => `${dep.depName}`)
         .filter((dep) => dep !== 'ruby')
         .map(quote)
-- 
GitLab