From 72d50cc5d61db16f5f7c73cdc3d5b087e1e03cf1 Mon Sep 17 00:00:00 2001
From: Atsushi Watanabe <atsushi.w@ieee.org>
Date: Wed, 20 Oct 2021 20:28:09 +0900
Subject: [PATCH] fix(gomod): Do not tidy go.mod on major update without
 gomodUpdateImportPaths (#11976)

Co-authored-by: Rhys Arkins <rhys@arkins.net>
---
 docs/usage/configuration-options.md           |  2 +-
 docs/usage/golang.md                          |  2 +-
 .../__snapshots__/artifacts.spec.ts.snap      | 70 +++++++++++++++----
 lib/manager/gomod/artifacts.spec.ts           | 51 ++++++++++++++
 lib/manager/gomod/artifacts.ts                | 12 +++-
 lib/manager/gomod/readme.md                   |  2 +-
 6 files changed, 121 insertions(+), 18 deletions(-)

diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md
index 4956dbc50a..17ccab605d 100644
--- a/docs/usage/configuration-options.md
+++ b/docs/usage/configuration-options.md
@@ -1680,7 +1680,7 @@ Though this option is enabled by default, you can fine tune the behavior by sett
 
 ## postUpdateOptions
 
-- `gomodTidy`: Run `go mod tidy` after Go module updates. This is implicitly enabled for major module updates.
+- `gomodTidy`: Run `go mod tidy` after Go module updates. This is implicitly enabled for major module updates when `gomodUpdateImportPaths` is enabled
 - `gomodUpdateImportPaths`: Update source import paths on major module updates, using [mod](https://github.com/marwan-at-work/mod)
 - `npmDedupe`: Run `npm dedupe` after `package-lock.json` updates
 - `yarnDedupeFewer`: Run `yarn-deduplicate --strategy fewer` after `yarn.lock` updates
diff --git a/docs/usage/golang.md b/docs/usage/golang.md
index e724dbec3a..bd5122e108 100644
--- a/docs/usage/golang.md
+++ b/docs/usage/golang.md
@@ -16,7 +16,7 @@ Renovate supports upgrading dependencies in `go.mod` files and their accompanyin
 1. Renovate runs `go get` to update the `go.sum` files
 1. If the user has enabled the option `gomodUpdateImportPaths` in the [`postUpdateOptions`](https://docs.renovatebot.com/configuration-options/#postupdateoptions) array, then Renovate uses [mod](https://github.com/marwan-at-work/mod) to update import paths on major updates, which can update any Go source file
 1. If the user has enabled the option `gomodTidy` in the [`postUpdateOptions`](https://docs.renovatebot.com/configuration-options/#postupdateoptions) array, then Renovate runs `go mod tidy`, which itself can update `go.mod` and `go.sum`.
-   1. This is implicitly enabled for major updates
+   1. This is implicitly enabled for major updates if the user has enabled the option `gomodUpdateImportPaths` in the [`postUpdateOptions`](https://docs.renovatebot.com/configuration-options/#postupdateoptions) array
 1. `go mod vendor` is run if vendored modules are detected
 1. A PR will be created with `go.mod`,`go.sum`, and any updated vendored files updated in the one commit
 1. If the source repository has either a "changelog" file or uses GitHub releases, then Release Notes for each version will be embedded in the generated PR
diff --git a/lib/manager/gomod/__snapshots__/artifacts.spec.ts.snap b/lib/manager/gomod/__snapshots__/artifacts.spec.ts.snap
index a446d705f8..e55225009a 100644
--- a/lib/manager/gomod/__snapshots__/artifacts.spec.ts.snap
+++ b/lib/manager/gomod/__snapshots__/artifacts.spec.ts.snap
@@ -2,9 +2,24 @@
 
 exports[`manager/gomod/artifacts catches errors 1`] = `Array []`;
 
-exports[`manager/gomod/artifacts returns if no go.sum found 1`] = `Array []`;
+exports[`manager/gomod/artifacts does not execute go mod tidy when none of gomodTidy and gomodUpdateImportPaths are set 1`] = `
+Array [
+  Object {
+    "file": Object {
+      "contents": "New go.sum",
+      "name": "go.sum",
+    },
+  },
+  Object {
+    "file": Object {
+      "contents": "New main.go",
+      "name": "go.mod",
+    },
+  },
+]
+`;
 
-exports[`manager/gomod/artifacts returns null if unchanged 1`] = `
+exports[`manager/gomod/artifacts does not execute go mod tidy when none of gomodTidy and gomodUpdateImportPaths are set 2`] = `
 Array [
   Object {
     "cmd": "go get -d ./...",
@@ -34,7 +49,9 @@ Array [
 ]
 `;
 
-exports[`manager/gomod/artifacts returns updated go.sum 1`] = `
+exports[`manager/gomod/artifacts returns if no go.sum found 1`] = `Array []`;
+
+exports[`manager/gomod/artifacts returns null if unchanged 1`] = `
 Array [
   Object {
     "cmd": "go get -d ./...",
@@ -64,7 +81,7 @@ Array [
 ]
 `;
 
-exports[`manager/gomod/artifacts skips updating import paths for gopkg.in dependencies 1`] = `
+exports[`manager/gomod/artifacts returns updated go.sum 1`] = `
 Array [
   Object {
     "cmd": "go get -d ./...",
@@ -91,8 +108,30 @@ Array [
       "timeout": 900000,
     },
   },
+]
+`;
+
+exports[`manager/gomod/artifacts skips gomodTidy without gomodUpdateImportPaths on major update 1`] = `
+Array [
   Object {
-    "cmd": "go mod tidy",
+    "file": Object {
+      "contents": "New go.sum",
+      "name": "go.sum",
+    },
+  },
+  Object {
+    "file": Object {
+      "contents": "New main.go",
+      "name": "go.mod",
+    },
+  },
+]
+`;
+
+exports[`manager/gomod/artifacts skips gomodTidy without gomodUpdateImportPaths on major update 2`] = `
+Array [
+  Object {
+    "cmd": "go get -d ./...",
     "options": Object {
       "cwd": "/tmp/github/some/repo",
       "encoding": "utf-8",
@@ -116,8 +155,13 @@ Array [
       "timeout": 900000,
     },
   },
+]
+`;
+
+exports[`manager/gomod/artifacts skips updating import paths for gopkg.in dependencies 1`] = `
+Array [
   Object {
-    "cmd": "go mod tidy",
+    "cmd": "go get -d ./...",
     "options": Object {
       "cwd": "/tmp/github/some/repo",
       "encoding": "utf-8",
@@ -141,13 +185,8 @@ Array [
       "timeout": 900000,
     },
   },
-]
-`;
-
-exports[`manager/gomod/artifacts skips updating import paths with gomodUpdateImportPaths on v0 to v1 1`] = `
-Array [
   Object {
-    "cmd": "go get -d ./...",
+    "cmd": "go mod tidy",
     "options": Object {
       "cwd": "/tmp/github/some/repo",
       "encoding": "utf-8",
@@ -196,8 +235,13 @@ Array [
       "timeout": 900000,
     },
   },
+]
+`;
+
+exports[`manager/gomod/artifacts skips updating import paths with gomodUpdateImportPaths on v0 to v1 1`] = `
+Array [
   Object {
-    "cmd": "go mod tidy",
+    "cmd": "go get -d ./...",
     "options": Object {
       "cwd": "/tmp/github/some/repo",
       "encoding": "utf-8",
diff --git a/lib/manager/gomod/artifacts.spec.ts b/lib/manager/gomod/artifacts.spec.ts
index 78d487b864..2b3234323e 100644
--- a/lib/manager/gomod/artifacts.spec.ts
+++ b/lib/manager/gomod/artifacts.spec.ts
@@ -318,6 +318,57 @@ describe('manager/gomod/artifacts', () => {
     ]);
     expect(execSnapshots).toMatchSnapshot();
   });
+  it('skips gomodTidy without gomodUpdateImportPaths on major update', async () => {
+    fs.readFile.mockResolvedValueOnce('Current go.sum' as any);
+    fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename
+    const execSnapshots = mockExecAll(exec);
+    git.getRepoStatus.mockResolvedValueOnce({
+      modified: ['go.sum', 'main.go'],
+    } as StatusResult);
+    fs.readFile
+      .mockResolvedValueOnce('New go.sum' as any)
+      .mockResolvedValueOnce('New main.go' as any)
+      .mockResolvedValueOnce('New go.mod' as any);
+    expect(
+      await gomod.updateArtifacts({
+        packageFileName: 'go.mod',
+        updatedDeps: [{ depName: 'github.com/google/go-github/v24' }],
+        newPackageFileContent: gomod1,
+        config: {
+          ...config,
+          updateType: 'major',
+          newMajor: 28,
+          postUpdateOptions: ['gomodTidy'],
+        },
+      })
+    ).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
+  });
+  it('does not execute go mod tidy when none of gomodTidy and gomodUpdateImportPaths are set', async () => {
+    fs.readFile.mockResolvedValueOnce('Current go.sum' as any);
+    fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename
+    const execSnapshots = mockExecAll(exec);
+    git.getRepoStatus.mockResolvedValueOnce({
+      modified: ['go.sum', 'main.go'],
+    } as StatusResult);
+    fs.readFile
+      .mockResolvedValueOnce('New go.sum' as any)
+      .mockResolvedValueOnce('New main.go' as any)
+      .mockResolvedValueOnce('New go.mod' as any);
+    expect(
+      await gomod.updateArtifacts({
+        packageFileName: 'go.mod',
+        updatedDeps: [{ depName: 'github.com/google/go-github/v24' }],
+        newPackageFileContent: gomod1,
+        config: {
+          ...config,
+          updateType: 'major',
+          newMajor: 28,
+        },
+      })
+    ).toMatchSnapshot();
+    expect(execSnapshots).toMatchSnapshot();
+  });
   it('updates import paths with specific tool version from constraint', async () => {
     fs.readFile.mockResolvedValueOnce('Current go.sum' as any);
     fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename
diff --git a/lib/manager/gomod/artifacts.ts b/lib/manager/gomod/artifacts.ts
index d770c9edcc..2d35392c22 100644
--- a/lib/manager/gomod/artifacts.ts
+++ b/lib/manager/gomod/artifacts.ts
@@ -159,9 +159,17 @@ export async function updateArtifacts({
       }
     }
 
-    const isGoModTidyRequired =
-      config.postUpdateOptions?.includes('gomodTidy') ||
+    const mustSkipGoModTidy =
+      !config.postUpdateOptions?.includes('gomodUpdateImportPaths') &&
       config.updateType === 'major';
+    if (mustSkipGoModTidy) {
+      logger.debug({ cmd, args }, 'go mod tidy command skipped');
+    }
+
+    const isGoModTidyRequired =
+      !mustSkipGoModTidy &&
+      (config.postUpdateOptions?.includes('gomodTidy') ||
+        (config.updateType === 'major' && isImportPathUpdateRequired));
     if (isGoModTidyRequired) {
       args = 'mod tidy';
       logger.debug({ cmd, args }, 'go mod tidy command included');
diff --git a/lib/manager/gomod/readme.md b/lib/manager/gomod/readme.md
index ba2ae2ee41..4902635cb5 100644
--- a/lib/manager/gomod/readme.md
+++ b/lib/manager/gomod/readme.md
@@ -1,7 +1,7 @@
 You might be interested in the following `postUpdateOptions`:
 
 1. `gomodTidy` - if you'd like Renovate to run `go mod tidy` after every update before raising the PR.
-   1. This is implicitly enabled for major updates
+   1. This is implicitly enabled for major updates if the user has enabled the option `gomodUpdateImportPaths`
 1. `gomodUpdateImportPaths` - if you'd like Renovate to update your source import paths on major updates before raising the PR.
 
 When Renovate is running using `binarySource=docker` (such as in the hosted WhiteSource Renovate app) then it will pick the latest compatible version of Go to run, i.e. the latest `1.x` release.
-- 
GitLab