From a446c2bc6d67e6337fe56e556194252156afbdb0 Mon Sep 17 00:00:00 2001
From: Thomas <9749173+uhthomas@users.noreply.github.com>
Date: Fri, 21 Oct 2022 20:42:14 +0100
Subject: [PATCH] fix(go)!: Fallback to git-tags instead of github-tags
 (#18060)

Changes go datasource fallback from github tags to git tags.

Closes #17923

BREAKING CHANGE: git-tags will be used instead of github-tags if a go package's host type is unknown.

Co-authored-by: Rhys Arkins <rhys@arkins.net>
---
 .../go/__fixtures__/go-get-git-digest.html    |  8 +++++
 .../releases-direct.spec.ts.snap              | 16 ++++++++++
 lib/modules/datasource/go/base.spec.ts        | 26 ++++++++++++++--
 lib/modules/datasource/go/base.ts             | 31 +++++++++++++------
 lib/modules/datasource/go/index.spec.ts       | 15 +++++++++
 lib/modules/datasource/go/index.ts            |  4 +++
 .../datasource/go/releases-direct.spec.ts     | 21 +++++++++++++
 lib/modules/datasource/go/releases-direct.ts  |  7 +++++
 lib/modules/datasource/go/types.ts            |  2 +-
 9 files changed, 116 insertions(+), 14 deletions(-)
 create mode 100644 lib/modules/datasource/go/__fixtures__/go-get-git-digest.html

diff --git a/lib/modules/datasource/go/__fixtures__/go-get-git-digest.html b/lib/modules/datasource/go/__fixtures__/go-get-git-digest.html
new file mode 100644
index 0000000000..4b10d74eac
--- /dev/null
+++ b/lib/modules/datasource/go/__fixtures__/go-get-git-digest.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<meta name="go-import" content="renovatebot.com/abc/def git https://renovatebot.com/abc/def.git">
+</head>
+<body>
+go get renovatebot.com/abc/def
+</body>
+</html>
diff --git a/lib/modules/datasource/go/__snapshots__/releases-direct.spec.ts.snap b/lib/modules/datasource/go/__snapshots__/releases-direct.spec.ts.snap
index dbb33a89b2..40b52a688f 100644
--- a/lib/modules/datasource/go/__snapshots__/releases-direct.spec.ts.snap
+++ b/lib/modules/datasource/go/__snapshots__/releases-direct.spec.ts.snap
@@ -19,6 +19,22 @@ exports[`modules/datasource/go/releases-direct getReleases support bitbucket tag
 }
 `;
 
+exports[`modules/datasource/go/releases-direct getReleases support git 1`] = `
+{
+  "releases": [
+    {
+      "gitRef": "v1.0.0",
+      "version": "v1.0.0",
+    },
+    {
+      "gitRef": "v2.0.0",
+      "version": "v2.0.0",
+    },
+  ],
+  "sourceUrl": undefined,
+}
+`;
+
 exports[`modules/datasource/go/releases-direct getReleases support gitlab 1`] = `
 {
   "releases": [
diff --git a/lib/modules/datasource/go/base.spec.ts b/lib/modules/datasource/go/base.spec.ts
index 517cb5d811..66d771ed00 100644
--- a/lib/modules/datasource/go/base.spec.ts
+++ b/lib/modules/datasource/go/base.spec.ts
@@ -3,6 +3,7 @@ import * as httpMock from '../../../../test/http-mock';
 import { mocked } from '../../../../test/util';
 import { PlatformId } from '../../../constants';
 import * as _hostRules from '../../../util/host-rules';
+import { GitTagsDatasource } from '../git-tags';
 import { GithubTagsDatasource } from '../github-tags';
 import { GitlabTagsDatasource } from '../gitlab-tags';
 import { BaseGoDatasource } from './base';
@@ -99,6 +100,7 @@ describe('modules/datasource/go/base', () => {
       });
 
       it('supports GitHub EE deps', async () => {
+        hostRules.hostType.mockReturnValue('github');
         httpMock
           .scope('https://git.enterprise.com')
           .get('/example/module?go-get=1')
@@ -303,7 +305,7 @@ describe('modules/datasource/go/base', () => {
         const res = await BaseGoDatasource.getDatasource('fyne.io/fyne');
 
         expect(res).toEqual({
-          datasource: 'github-tags',
+          datasource: GithubTagsDatasource.id,
           registryUrl: 'https://github.com',
           packageName: 'fyne-io/fyne',
         });
@@ -320,7 +322,7 @@ describe('modules/datasource/go/base', () => {
         const res = await BaseGoDatasource.getDatasource('fyne.io/fyne');
 
         expect(res).toEqual({
-          datasource: 'github-tags',
+          datasource: GithubTagsDatasource.id,
           registryUrl: 'https://github.com',
           packageName: 'fyne-io/fyne',
         });
@@ -339,11 +341,29 @@ describe('modules/datasource/go/base', () => {
         );
 
         expect(res).toEqual({
-          datasource: 'gitlab-tags',
+          datasource: GitlabTagsDatasource.id,
           registryUrl: 'https://gitlab.com',
           packageName: 'golang/myrepo',
         });
       });
+
+      it('handles uncommon imports', async () => {
+        const meta =
+          '<meta name="go-import" content="example.com/uncommon git ssh://git.example.com/uncommon">';
+        httpMock
+          .scope('https://example.com')
+          .get('/uncommon?go-get=1')
+          .reply(200, meta);
+
+        const res = await BaseGoDatasource.getDatasource(
+          'example.com/uncommon'
+        );
+
+        expect(res).toEqual({
+          datasource: GitTagsDatasource.id,
+          packageName: 'ssh://git.example.com/uncommon',
+        });
+      });
     });
   });
 });
diff --git a/lib/modules/datasource/go/base.ts b/lib/modules/datasource/go/base.ts
index 4e8f00ff83..43e241e507 100644
--- a/lib/modules/datasource/go/base.ts
+++ b/lib/modules/datasource/go/base.ts
@@ -3,11 +3,13 @@
 import URL from 'url';
 import { PlatformId } from '../../../constants';
 import { logger } from '../../../logger';
+import { detectPlatform } from '../../../util/common';
 import * as hostRules from '../../../util/host-rules';
 import { Http } from '../../../util/http';
 import { regEx } from '../../../util/regex';
 import { trimLeadingSlash, trimTrailingSlash } from '../../../util/url';
 import { BitBucketTagsDatasource } from '../bitbucket-tags';
+import { GitTagsDatasource } from '../git-tags';
 import { GithubTagsDatasource } from '../github-tags';
 import { GitlabTagsDatasource } from '../gitlab-tags';
 import type { DataSource } from './types';
@@ -198,18 +200,27 @@ export class BaseGoDatasource {
     }
     // fall back to old behaviour if detection did not work
 
-    // split the go module from the URL: host/go/module -> go/module
-    // TODO: `parsedUrl.pathname` can be undefined
-    const packageName = trimTrailingSlash(`${parsedUrl.pathname}`)
-      .replace(regEx(/\.git$/), '')
-      .split('/')
-      .slice(-2)
-      .join('/');
+    if (detectPlatform(goImportURL) === 'github') {
+      // split the go module from the URL: host/go/module -> go/module
+      // TODO: `parsedUrl.pathname` can be undefined
+      const packageName = trimTrailingSlash(`${parsedUrl.pathname}`)
+        .replace(regEx(/\.git$/), '')
+        .split('/')
+        .slice(-2)
+        .join('/');
+
+      return {
+        datasource: GithubTagsDatasource.id,
+        registryUrl: `${parsedUrl.protocol}//${parsedUrl.host}`,
+        packageName,
+      };
+    }
+
+    // Fall back to git tags
 
     return {
-      datasource: GithubTagsDatasource.id,
-      registryUrl: `${parsedUrl.protocol}//${parsedUrl.host}`,
-      packageName,
+      datasource: GitTagsDatasource.id,
+      packageName: goImportURL,
     };
   }
 }
diff --git a/lib/modules/datasource/go/index.spec.ts b/lib/modules/datasource/go/index.spec.ts
index 229bbb32f9..6f5575968b 100644
--- a/lib/modules/datasource/go/index.spec.ts
+++ b/lib/modules/datasource/go/index.spec.ts
@@ -11,11 +11,13 @@ const getReleasesDirectMock = jest.fn();
 
 const getDigestGithubMock = jest.fn();
 const getDigestGitlabMock = jest.fn();
+const getDigestGitMock = jest.fn();
 const getDigestBitbucketMock = jest.fn();
 jest.mock('./releases-direct', () => {
   return {
     GoDirectDatasource: jest.fn().mockImplementation(() => {
       return {
+        git: { getDigest: () => getDigestGitMock() },
         github: { getDigest: () => getDigestGithubMock() },
         gitlab: { getDigest: () => getDigestGitlabMock() },
         bitbucket: { getDigest: () => getDigestBitbucketMock() },
@@ -129,6 +131,19 @@ describe('modules/datasource/go/index', () => {
       expect(res).toBe('abcdefabcdefabcdefabcdef');
     });
 
+    it('supports git digest', async () => {
+      httpMock
+        .scope('https://renovatebot.com/')
+        .get('/abc/def?go-get=1')
+        .reply(200, Fixtures.get('go-get-git-digest.html'));
+      getDigestGitMock.mockResolvedValue('abcdefabcdefabcdefabcdef');
+      const res = await datasource.getDigest(
+        { packageName: 'renovatebot.com/abc/def' },
+        null
+      );
+      expect(res).toBe('abcdefabcdefabcdefabcdef');
+    });
+
     it('supports gitlab digest with a specific branch', async () => {
       const branch = 'some-branch';
       httpMock
diff --git a/lib/modules/datasource/go/index.ts b/lib/modules/datasource/go/index.ts
index 8b58406455..b3673e2880 100644
--- a/lib/modules/datasource/go/index.ts
+++ b/lib/modules/datasource/go/index.ts
@@ -4,6 +4,7 @@ import { addSecretForSanitizing } from '../../../util/sanitize';
 import { parseUrl } from '../../../util/url';
 import { BitBucketTagsDatasource } from '../bitbucket-tags';
 import { Datasource } from '../datasource';
+import { GitTagsDatasource } from '../git-tags';
 import { GithubTagsDatasource } from '../github-tags';
 import { GitlabTagsDatasource } from '../gitlab-tags';
 import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types';
@@ -62,6 +63,9 @@ export class GoDatasource extends Datasource {
     const tag = value && !value.startsWith('v0.0.0-2') ? value : undefined;
 
     switch (source.datasource) {
+      case GitTagsDatasource.id: {
+        return this.direct.git.getDigest?.(source, tag) ?? null;
+      }
       case GithubTagsDatasource.id: {
         return this.direct.github.getDigest(source, tag);
       }
diff --git a/lib/modules/datasource/go/releases-direct.spec.ts b/lib/modules/datasource/go/releases-direct.spec.ts
index 53753b9176..12eb327a1b 100644
--- a/lib/modules/datasource/go/releases-direct.spec.ts
+++ b/lib/modules/datasource/go/releases-direct.spec.ts
@@ -1,6 +1,7 @@
 import * as httpMock from '../../../../test/http-mock';
 import { mocked } from '../../../../test/util';
 import * as _hostRules from '../../../util/host-rules';
+import { GitTagsDatasource } from '../git-tags';
 import { GithubTagsDatasource } from '../github-tags';
 import { BaseGoDatasource } from './base';
 import { GoDirectDatasource } from './releases-direct';
@@ -13,6 +14,7 @@ const getDatasourceSpy = jest.spyOn(BaseGoDatasource, 'getDatasource');
 const hostRules = mocked(_hostRules);
 
 describe('modules/datasource/go/releases-direct', () => {
+  const gitGetTags = jest.spyOn(GitTagsDatasource.prototype, 'getReleases');
   const githubGetTags = jest.spyOn(
     GithubTagsDatasource.prototype,
     'getReleases'
@@ -90,6 +92,25 @@ describe('modules/datasource/go/releases-direct', () => {
       expect(res).toBeDefined();
     });
 
+    it('support git', async () => {
+      getDatasourceSpy.mockResolvedValueOnce({
+        datasource: 'git-tags',
+        packageName: 'renovatebot.com/abc/def',
+      });
+      gitGetTags.mockResolvedValueOnce({
+        releases: [
+          { gitRef: 'v1.0.0', version: 'v1.0.0' },
+          { gitRef: 'v2.0.0', version: 'v2.0.0' },
+        ],
+      });
+      const res = await datasource.getReleases({
+        packageName: 'renovatebot.com/abc/def',
+      });
+      expect(res).toMatchSnapshot();
+      expect(res).not.toBeNull();
+      expect(res).toBeDefined();
+    });
+
     it('support self hosted gitlab private repositories', async () => {
       getDatasourceSpy.mockResolvedValueOnce({
         datasource: 'gitlab-tags',
diff --git a/lib/modules/datasource/go/releases-direct.ts b/lib/modules/datasource/go/releases-direct.ts
index 574455e68d..30f5068c7e 100644
--- a/lib/modules/datasource/go/releases-direct.ts
+++ b/lib/modules/datasource/go/releases-direct.ts
@@ -3,6 +3,7 @@ import { cache } from '../../../util/cache/package/decorator';
 import { regEx } from '../../../util/regex';
 import { BitBucketTagsDatasource } from '../bitbucket-tags';
 import { Datasource } from '../datasource';
+import { GitTagsDatasource } from '../git-tags';
 import { GithubTagsDatasource } from '../github-tags';
 import { GitlabTagsDatasource } from '../gitlab-tags';
 import type { DatasourceApi, GetReleasesConfig, ReleaseResult } from '../types';
@@ -12,12 +13,14 @@ import { getSourceUrl } from './common';
 export class GoDirectDatasource extends Datasource {
   static readonly id = 'go-direct';
 
+  git: GitTagsDatasource;
   github: GithubTagsDatasource;
   gitlab: DatasourceApi;
   bitbucket: DatasourceApi;
 
   constructor() {
     super(GoDirectDatasource.id);
+    this.git = new GitTagsDatasource();
     this.github = new GithubTagsDatasource();
     this.gitlab = new GitlabTagsDatasource();
     this.bitbucket = new BitBucketTagsDatasource();
@@ -55,6 +58,10 @@ export class GoDirectDatasource extends Datasource {
     }
 
     switch (source.datasource) {
+      case GitTagsDatasource.id: {
+        res = await this.git.getReleases(source);
+        break;
+      }
       case GithubTagsDatasource.id: {
         res = await this.github.getReleases(source);
         break;
diff --git a/lib/modules/datasource/go/types.ts b/lib/modules/datasource/go/types.ts
index 2f5bac9033..03472dd1ea 100644
--- a/lib/modules/datasource/go/types.ts
+++ b/lib/modules/datasource/go/types.ts
@@ -2,7 +2,7 @@ import type { GoproxyFallback } from './common';
 
 export interface DataSource {
   datasource: string;
-  registryUrl: string;
+  registryUrl?: string;
   packageName: string;
 }
 
-- 
GitLab