From 0eb5c6d2cde9df9654c1d2478e64917f63728d75 Mon Sep 17 00:00:00 2001
From: Michael Kriese <michael.kriese@visualon.de>
Date: Wed, 1 Sep 2021 13:07:55 +0200
Subject: [PATCH] fix(core/changelogs): pass though known project info (#11515)

---
 lib/datasource/github-releases/test/index.ts  |   2 +-
 lib/datasource/github-releases/types.ts       |   5 +
 lib/datasource/github-tags/index.ts           |   7 +-
 lib/datasource/github-tags/types.ts           |   4 +
 lib/datasource/gitlab-releases/types.ts       |   1 +
 .../__snapshots__/err-serializer.spec.ts.snap |   1 +
 lib/logger/utils.ts                           |   1 +
 lib/util/http/index.ts                        |   6 +-
 .../__snapshots__/github.spec.ts.snap         |  30 +-
 .../__snapshots__/gitlab.spec.ts.snap         |  30 +-
 .../__snapshots__/index.spec.ts.snap          |  35 +-
 .../__snapshots__/release-notes.spec.ts.snap  |  75 +++--
 lib/workers/pr/changelog/github/index.ts      |  20 +-
 lib/workers/pr/changelog/gitlab/index.ts      |  19 +-
 .../pr/changelog/release-notes.spec.ts        | 310 +++++++++++-------
 lib/workers/pr/changelog/release-notes.ts     | 175 ++++------
 lib/workers/pr/changelog/source-github.ts     |  10 +-
 lib/workers/pr/changelog/source-gitlab.ts     |   5 +-
 lib/workers/pr/changelog/types.ts             |   4 +-
 lib/workers/pr/index.spec.ts                  |  56 ++--
 lib/workers/pr/index.ts                       |   4 +-
 21 files changed, 446 insertions(+), 354 deletions(-)

diff --git a/lib/datasource/github-releases/test/index.ts b/lib/datasource/github-releases/test/index.ts
index 7c40f0962f..95c756a089 100644
--- a/lib/datasource/github-releases/test/index.ts
+++ b/lib/datasource/github-releases/test/index.ts
@@ -20,7 +20,7 @@ export class GitHubReleaseMocker {
       published_at: '2020-03-09T11:00:00Z',
       prerelease: false,
       assets: [],
-    };
+    } as GithubRelease;
     for (const assetFn of Object.keys(assets)) {
       const assetPath = `/repos/${this.lookupName}/releases/download/${version}/${assetFn}`;
       const assetData = assets[assetFn];
diff --git a/lib/datasource/github-releases/types.ts b/lib/datasource/github-releases/types.ts
index 78dd7af200..2989ddcaee 100644
--- a/lib/datasource/github-releases/types.ts
+++ b/lib/datasource/github-releases/types.ts
@@ -1,8 +1,13 @@
 export type GithubRelease = {
+  id: number;
   tag_name: string;
   published_at: string;
   prerelease: boolean;
   assets: GithubReleaseAsset[];
+
+  html_url: string;
+  name: string;
+  body: string;
 };
 
 export interface GithubReleaseAsset {
diff --git a/lib/datasource/github-tags/index.ts b/lib/datasource/github-tags/index.ts
index 2300a58e29..9099448e61 100644
--- a/lib/datasource/github-tags/index.ts
+++ b/lib/datasource/github-tags/index.ts
@@ -4,7 +4,7 @@ import { GithubHttp } from '../../util/http/github';
 import { ensureTrailingSlash } from '../../util/url';
 import * as githubReleases from '../github-releases';
 import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types';
-import type { TagResponse } from './types';
+import type { GitHubTag, TagResponse } from './types';
 
 export const id = 'github-tags';
 export const customRegistrySupport = true;
@@ -148,12 +148,9 @@ async function getTags({
       : `${sourceUrlBase}api/v3/`;
   // tag
   const url = `${apiBaseUrl}repos/${repo}/tags?per_page=100`;
-  type GitHubTag = {
-    name: string;
-  }[];
 
   const versions = (
-    await http.getJson<GitHubTag>(url, {
+    await http.getJson<GitHubTag[]>(url, {
       paginate: true,
     })
   ).body.map((o) => o.name);
diff --git a/lib/datasource/github-tags/types.ts b/lib/datasource/github-tags/types.ts
index 0c570048a2..e8b5888166 100644
--- a/lib/datasource/github-tags/types.ts
+++ b/lib/datasource/github-tags/types.ts
@@ -5,3 +5,7 @@ export interface TagResponse {
     sha: string;
   };
 }
+
+export interface GitHubTag {
+  name: string;
+}
diff --git a/lib/datasource/gitlab-releases/types.ts b/lib/datasource/gitlab-releases/types.ts
index d6b4cab4fb..246ba0a9ed 100644
--- a/lib/datasource/gitlab-releases/types.ts
+++ b/lib/datasource/gitlab-releases/types.ts
@@ -1,4 +1,5 @@
 export interface GitlabRelease {
+  description: string;
   name: string;
   tag_name: string;
   released_at: string;
diff --git a/lib/logger/__snapshots__/err-serializer.spec.ts.snap b/lib/logger/__snapshots__/err-serializer.spec.ts.snap
index d23c5513d6..6b2de4afa3 100644
--- a/lib/logger/__snapshots__/err-serializer.spec.ts.snap
+++ b/lib/logger/__snapshots__/err-serializer.spec.ts.snap
@@ -60,6 +60,7 @@ Object {
       "accept-encoding": "gzip, deflate, br",
       "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)",
     },
+    "hostType": "any",
     "http2": false,
     "method": "POST",
     "password": "***********",
diff --git a/lib/logger/utils.ts b/lib/logger/utils.ts
index 36945b4eed..34e7085c10 100644
--- a/lib/logger/utils.ts
+++ b/lib/logger/utils.ts
@@ -66,6 +66,7 @@ export default function prepareError(err: Error): Record<string, unknown> {
     const options: Record<string, unknown> = {
       headers: clone(err.options.headers),
       url: err.options.url?.toString(),
+      hostType: err.options.context.hostType,
     };
     response.options = options;
 
diff --git a/lib/util/http/index.ts b/lib/util/http/index.ts
index a60501444f..a3530c9526 100644
--- a/lib/util/http/index.ts
+++ b/lib/util/http/index.ts
@@ -105,7 +105,11 @@ async function gotRoutine<T>(
 }
 
 export class Http<GetOptions = HttpOptions, PostOptions = HttpPostOptions> {
-  constructor(private hostType: string, private options?: HttpOptions) {}
+  private options?: GotOptions;
+
+  constructor(private hostType: string, options?: HttpOptions) {
+    this.options = merge<GotOptions>(options, { context: { hostType } });
+  }
 
   protected async request<T>(
     requestUrl: string | URL,
diff --git a/lib/workers/pr/changelog/__snapshots__/github.spec.ts.snap b/lib/workers/pr/changelog/__snapshots__/github.spec.ts.snap
index 23bcff4cd3..a97678ef16 100644
--- a/lib/workers/pr/changelog/__snapshots__/github.spec.ts.snap
+++ b/lib/workers/pr/changelog/__snapshots__/github.spec.ts.snap
@@ -7,9 +7,10 @@ Object {
     "apiBaseUrl": "https://api.github.com/",
     "baseUrl": "https://github.com/",
     "depName": "@renovate/no",
-    "github": "chalk/chalk",
-    "repository": "https://github.com/chalk/chalk",
+    "repository": "chalk/chalk",
     "sourceDirectory": undefined,
+    "sourceUrl": "https://github.com/chalk/chalk",
+    "type": "github",
   },
   "versions": Array [
     Object {
@@ -59,9 +60,10 @@ Object {
     "apiBaseUrl": "https://github-enterprise.example.com/api/v3/",
     "baseUrl": "https://github-enterprise.example.com/",
     "depName": "renovate",
-    "github": "chalk/chalk",
-    "repository": "https://github-enterprise.example.com/chalk/chalk",
+    "repository": "chalk/chalk",
     "sourceDirectory": undefined,
+    "sourceUrl": "https://github-enterprise.example.com/chalk/chalk",
+    "type": "github",
   },
   "versions": Array [
     Object {
@@ -111,9 +113,10 @@ Object {
     "apiBaseUrl": "https://api.github.com/",
     "baseUrl": "https://github.com/",
     "depName": "renovate",
-    "github": "chalk/chalk",
-    "repository": "https://github.com/chalk/chalk",
+    "repository": "chalk/chalk",
     "sourceDirectory": undefined,
+    "sourceUrl": "https://github.com/chalk/chalk",
+    "type": "github",
   },
   "versions": Array [
     Object {
@@ -163,9 +166,10 @@ Object {
     "apiBaseUrl": "https://api.github.com/",
     "baseUrl": "https://github.com/",
     "depName": "renovate",
-    "github": "chalk/chalk",
-    "repository": "https://github.com/chalk/chalk",
+    "repository": "chalk/chalk",
     "sourceDirectory": undefined,
+    "sourceUrl": "https://github.com/chalk/chalk",
+    "type": "github",
   },
   "versions": Array [
     Object {
@@ -215,9 +219,10 @@ Object {
     "apiBaseUrl": "https://api.github.com/",
     "baseUrl": "https://github.com/",
     "depName": "renovate",
-    "github": "chalk/chalk",
-    "repository": "https://github.com/chalk/chalk",
+    "repository": "chalk/chalk",
     "sourceDirectory": undefined,
+    "sourceUrl": "https://github.com/chalk/chalk",
+    "type": "github",
   },
   "versions": Array [
     Object {
@@ -267,9 +272,10 @@ Object {
     "apiBaseUrl": "https://api.github.com/",
     "baseUrl": "https://github.com/",
     "depName": "renovate",
-    "github": "chalk/chalk",
-    "repository": "https://github.com/chalk/chalk",
+    "repository": "chalk/chalk",
     "sourceDirectory": undefined,
+    "sourceUrl": "https://github.com/chalk/chalk",
+    "type": "github",
   },
   "versions": Array [
     Object {
diff --git a/lib/workers/pr/changelog/__snapshots__/gitlab.spec.ts.snap b/lib/workers/pr/changelog/__snapshots__/gitlab.spec.ts.snap
index 427d3e9199..cc229009fe 100644
--- a/lib/workers/pr/changelog/__snapshots__/gitlab.spec.ts.snap
+++ b/lib/workers/pr/changelog/__snapshots__/gitlab.spec.ts.snap
@@ -7,8 +7,9 @@ Object {
     "apiBaseUrl": "https://gitlab.com/api/v4/",
     "baseUrl": "https://gitlab.com/",
     "depName": "renovate",
-    "gitlab": "meno/dropzone",
-    "repository": "https://gitlab.com/meno/dropzone/",
+    "repository": "meno/dropzone",
+    "sourceUrl": "https://gitlab.com/meno/dropzone/",
+    "type": "gitlab",
   },
   "versions": Array [
     Object {
@@ -154,8 +155,9 @@ Object {
     "apiBaseUrl": "https://gitlab-enterprise.example.com/api/v4/",
     "baseUrl": "https://gitlab-enterprise.example.com/",
     "depName": "renovate",
-    "gitlab": "meno/dropzone",
-    "repository": "https://gitlab-enterprise.example.com/meno/dropzone/",
+    "repository": "meno/dropzone",
+    "sourceUrl": "https://gitlab-enterprise.example.com/meno/dropzone/",
+    "type": "gitlab",
   },
   "versions": Array [
     Object {
@@ -197,8 +199,9 @@ Object {
     "apiBaseUrl": "https://git.test.com/api/v4/",
     "baseUrl": "https://git.test.com/",
     "depName": "renovate",
-    "gitlab": "meno/dropzone",
-    "repository": "https://git.test.com/meno/dropzone/",
+    "repository": "meno/dropzone",
+    "sourceUrl": "https://git.test.com/meno/dropzone/",
+    "type": "gitlab",
   },
   "versions": Array [
     Object {
@@ -240,8 +243,9 @@ Object {
     "apiBaseUrl": "https://gitlab.com/api/v4/",
     "baseUrl": "https://gitlab.com/",
     "depName": "renovate",
-    "gitlab": "meno/dropzone",
-    "repository": "https://gitlab.com/meno/dropzone/",
+    "repository": "meno/dropzone",
+    "sourceUrl": "https://gitlab.com/meno/dropzone/",
+    "type": "gitlab",
   },
   "versions": Array [
     Object {
@@ -403,8 +407,9 @@ Object {
     "apiBaseUrl": "https://gitlab.com/api/v4/",
     "baseUrl": "https://gitlab.com/",
     "depName": "renovate",
-    "gitlab": "meno/dropzone",
-    "repository": "https://gitlab.com/meno/dropzone/",
+    "repository": "meno/dropzone",
+    "sourceUrl": "https://gitlab.com/meno/dropzone/",
+    "type": "gitlab",
   },
   "versions": Array [
     Object {
@@ -550,8 +555,9 @@ Object {
     "apiBaseUrl": "https://gitlab.com/api/v4/",
     "baseUrl": "https://gitlab.com/",
     "depName": "renovate",
-    "gitlab": "meno/dropzone",
-    "repository": "https://gitlab.com/meno/dropzone/",
+    "repository": "meno/dropzone",
+    "sourceUrl": "https://gitlab.com/meno/dropzone/",
+    "type": "gitlab",
   },
   "versions": Array [
     Object {
diff --git a/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap b/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap
index 96be5e64cf..f57ff11e1b 100644
--- a/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap
+++ b/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap
@@ -7,9 +7,10 @@ Object {
     "apiBaseUrl": "https://api.github.com/",
     "baseUrl": "https://github.com/",
     "depName": "@renovate/no",
-    "github": "chalk/chalk",
-    "repository": "https://github.com/chalk/chalk",
+    "repository": "chalk/chalk",
     "sourceDirectory": undefined,
+    "sourceUrl": "https://github.com/chalk/chalk",
+    "type": "github",
   },
   "versions": Array [
     Object {
@@ -163,9 +164,10 @@ Object {
     "apiBaseUrl": "https://github-enterprise.example.com/api/v3/",
     "baseUrl": "https://github-enterprise.example.com/",
     "depName": "renovate",
-    "github": "chalk/chalk",
-    "repository": "https://github-enterprise.example.com/chalk/chalk",
+    "repository": "chalk/chalk",
     "sourceDirectory": undefined,
+    "sourceUrl": "https://github-enterprise.example.com/chalk/chalk",
+    "type": "github",
   },
   "versions": Array [
     Object {
@@ -363,9 +365,10 @@ Object {
     "apiBaseUrl": "https://api.github.com/",
     "baseUrl": "https://github.com/",
     "depName": "renovate",
-    "github": "chalk/chalk",
-    "repository": "https://github.com/chalk/chalk",
+    "repository": "chalk/chalk",
     "sourceDirectory": undefined,
+    "sourceUrl": "https://github.com/chalk/chalk",
+    "type": "github",
   },
   "versions": Array [
     Object {
@@ -563,9 +566,10 @@ Object {
     "apiBaseUrl": "https://github-enterprise.example.com/api/v3/",
     "baseUrl": "https://github-enterprise.example.com/",
     "depName": "renovate",
-    "github": "chalk/chalk",
-    "repository": "https://github-enterprise.example.com/chalk/chalk",
+    "repository": "chalk/chalk",
     "sourceDirectory": undefined,
+    "sourceUrl": "https://github-enterprise.example.com/chalk/chalk",
+    "type": "github",
   },
   "versions": Array [
     Object {
@@ -763,9 +767,10 @@ Object {
     "apiBaseUrl": "https://api.github.com/",
     "baseUrl": "https://github.com/",
     "depName": "renovate",
-    "github": "chalk/chalk",
-    "repository": "https://github.com/chalk/chalk",
+    "repository": "chalk/chalk",
     "sourceDirectory": undefined,
+    "sourceUrl": "https://github.com/chalk/chalk",
+    "type": "github",
   },
   "versions": Array [
     Object {
@@ -815,9 +820,10 @@ Object {
     "apiBaseUrl": "https://api.github.com/",
     "baseUrl": "https://github.com/",
     "depName": "renovate",
-    "github": "chalk/chalk",
-    "repository": "https://github.com/chalk/chalk",
+    "repository": "chalk/chalk",
     "sourceDirectory": undefined,
+    "sourceUrl": "https://github.com/chalk/chalk",
+    "type": "github",
   },
   "versions": Array [
     Object {
@@ -1019,9 +1025,10 @@ Object {
     "apiBaseUrl": "https://api.github.com/",
     "baseUrl": "https://github.com/",
     "depName": "renovate",
-    "github": "chalk/chalk",
-    "repository": "https://github.com/chalk/chalk",
+    "repository": "chalk/chalk",
     "sourceDirectory": undefined,
+    "sourceUrl": "https://github.com/chalk/chalk",
+    "type": "github",
   },
   "versions": Array [
     Object {
diff --git a/lib/workers/pr/changelog/__snapshots__/release-notes.spec.ts.snap b/lib/workers/pr/changelog/__snapshots__/release-notes.spec.ts.snap
index 53776c9742..976f87b5a5 100644
--- a/lib/workers/pr/changelog/__snapshots__/release-notes.spec.ts.snap
+++ b/lib/workers/pr/changelog/__snapshots__/release-notes.spec.ts.snap
@@ -5,7 +5,8 @@ Object {
   "a": 1,
   "hasReleaseNotes": false,
   "project": Object {
-    "github": "https://github.com/nodeca/js-yaml",
+    "repository": "https://github.com/nodeca/js-yaml",
+    "type": "github",
   },
   "versions": Array [
     Object {
@@ -24,7 +25,8 @@ Object {
   "a": 1,
   "hasReleaseNotes": false,
   "project": Object {
-    "gitlab": "https://gitlab.com/gitlab-org/gitter/webapp/",
+    "repository": "https://gitlab.com/gitlab-org/gitter/webapp/",
+    "type": "gitlab",
   },
   "versions": Array [
     Object {
@@ -137,7 +139,7 @@ Array [
 ]
 `;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 1`] = `
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "" 1`] = `
 Object {
   "body": "some body [#123](https://github.com/some/other-repository/issues/123), [#124](https://github.com/some/yet-other-repository/issues/124)
 ",
@@ -148,7 +150,7 @@ Object {
 }
 `;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 2`] = `
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "" 2`] = `
 Array [
   Object {
     "headers": Object {
@@ -163,18 +165,18 @@ Array [
 ]
 `;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 3`] = `
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "other@" 1`] = `
 Object {
   "body": "some body [#123](https://github.com/some/other-repository/issues/123), [#124](https://github.com/some/yet-other-repository/issues/124)
 ",
   "id": undefined,
   "name": undefined,
-  "tag": "v1.0.1",
-  "url": "https://github.com/some/other-repository/releases/v1.0.1",
+  "tag": "other@1.0.1",
+  "url": "https://github.com/some/other-repository/releases/other@1.0.1",
 }
 `;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 4`] = `
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "other@" 2`] = `
 Array [
   Object {
     "headers": Object {
@@ -189,18 +191,18 @@ Array [
 ]
 `;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 5`] = `
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "other_v" 1`] = `
 Object {
   "body": "some body [#123](https://github.com/some/other-repository/issues/123), [#124](https://github.com/some/yet-other-repository/issues/124)
 ",
   "id": undefined,
   "name": undefined,
-  "tag": "other-1.0.1",
-  "url": "https://github.com/some/other-repository/releases/other-1.0.1",
+  "tag": "other_v1.0.1",
+  "url": "https://github.com/some/other-repository/releases/other_v1.0.1",
 }
 `;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 6`] = `
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "other_v" 2`] = `
 Array [
   Object {
     "headers": Object {
@@ -215,18 +217,18 @@ Array [
 ]
 `;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 7`] = `
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "other-" 1`] = `
 Object {
   "body": "some body [#123](https://github.com/some/other-repository/issues/123), [#124](https://github.com/some/yet-other-repository/issues/124)
 ",
   "id": undefined,
   "name": undefined,
-  "tag": "other_v1.0.1",
-  "url": "https://github.com/some/other-repository/releases/other_v1.0.1",
+  "tag": "other-1.0.1",
+  "url": "https://github.com/some/other-repository/releases/other-1.0.1",
 }
 `;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 8`] = `
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "other-" 2`] = `
 Array [
   Object {
     "headers": Object {
@@ -241,18 +243,18 @@ Array [
 ]
 `;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 9`] = `
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "v" 1`] = `
 Object {
   "body": "some body [#123](https://github.com/some/other-repository/issues/123), [#124](https://github.com/some/yet-other-repository/issues/124)
 ",
   "id": undefined,
   "name": undefined,
-  "tag": "other@1.0.1",
-  "url": "https://github.com/some/other-repository/releases/other@1.0.1",
+  "tag": "v1.0.1",
+  "url": "https://github.com/some/other-repository/releases/v1.0.1",
 }
 `;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body 10`] = `
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body "v" 2`] = `
 Array [
   Object {
     "headers": Object {
@@ -267,9 +269,16 @@ Array [
 ]
 `;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo  1`] = `null`;
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo "" 1`] = `
+Object {
+  "body": "some body #123, [#124](https://gitlab.com/some/yet-other-repository/issues/124)",
+  "name": undefined,
+  "tag": "1.0.1",
+  "url": "https://gitlab.com/some/other-repository/tags/1.0.1",
+}
+`;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo  2`] = `
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo "" 2`] = `
 Array [
   Object {
     "headers": Object {
@@ -284,9 +293,16 @@ Array [
 ]
 `;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo other- 1`] = `null`;
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo "other-" 1`] = `
+Object {
+  "body": "some body #123, [#124](https://gitlab.com/some/yet-other-repository/issues/124)",
+  "name": undefined,
+  "tag": "other-1.0.1",
+  "url": "https://gitlab.com/some/other-repository/tags/other-1.0.1",
+}
+`;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo other- 2`] = `
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo "other-" 2`] = `
 Array [
   Object {
     "headers": Object {
@@ -301,9 +317,16 @@ Array [
 ]
 `;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo v 1`] = `null`;
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo "v" 1`] = `
+Object {
+  "body": "some body #123, [#124](https://gitlab.com/some/yet-other-repository/issues/124)",
+  "name": undefined,
+  "tag": "v1.0.1",
+  "url": "https://gitlab.com/some/other-repository/tags/v1.0.1",
+}
+`;
 
-exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo v 2`] = `
+exports[`workers/pr/changelog/release-notes getReleaseNotes() gets release notes with body from gitlab repo "v" 2`] = `
 Array [
   Object {
     "headers": Object {
diff --git a/lib/workers/pr/changelog/github/index.ts b/lib/workers/pr/changelog/github/index.ts
index 4232aa7550..354e7da27d 100644
--- a/lib/workers/pr/changelog/github/index.ts
+++ b/lib/workers/pr/changelog/github/index.ts
@@ -1,4 +1,6 @@
 import changelogFilenameRegex from 'changelog-filename-regex';
+import type { GithubRelease } from '../../../../datasource/github-releases/types';
+import type { GitHubTag } from '../../../../datasource/github-tags/types';
 import { logger } from '../../../../logger';
 import type {
   GithubGitBlob,
@@ -18,7 +20,7 @@ export async function getTags(
   logger.trace('github.getTags()');
   const url = `${endpoint}repos/${repository}/tags?per_page=100`;
   try {
-    const res = await http.getJson<{ name: string }[]>(url, {
+    const res = await http.getJson<GitHubTag[]>(url, {
       paginate: true,
     });
 
@@ -30,8 +32,10 @@ export async function getTags(
 
     return tags.map((tag) => tag.name).filter(Boolean);
   } catch (err) {
-    logger.debug({ sourceRepo: repository }, 'Failed to fetch Github tags');
-    logger.debug({ err });
+    logger.debug(
+      { sourceRepo: repository, err },
+      'Failed to fetch Github tags'
+    );
     // istanbul ignore if
     if (err.message?.includes('Bad credentials')) {
       logger.warn('Bad credentials triggering tag fail lookup in changelog');
@@ -108,15 +112,7 @@ export async function getReleaseList(
   const url = `${ensureTrailingSlash(
     apiBaseUrl
   )}repos/${repository}/releases?per_page=100`;
-  const res = await http.getJson<
-    {
-      html_url: string;
-      id: number;
-      tag_name: string;
-      name: string;
-      body: string;
-    }[]
-  >(url, { paginate: true });
+  const res = await http.getJson<GithubRelease[]>(url, { paginate: true });
   return res.body.map((release) => ({
     url: release.html_url,
     id: release.id,
diff --git a/lib/workers/pr/changelog/gitlab/index.ts b/lib/workers/pr/changelog/gitlab/index.ts
index d6a0b94fd5..ab7e71625d 100644
--- a/lib/workers/pr/changelog/gitlab/index.ts
+++ b/lib/workers/pr/changelog/gitlab/index.ts
@@ -1,4 +1,6 @@
 import changelogFilenameRegex from 'changelog-filename-regex';
+import type { GitlabRelease } from '../../../../datasource/gitlab-releases/types';
+import type { GitlabTag } from '../../../../datasource/gitlab-tags/types';
 import { logger } from '../../../../logger';
 import type { GitlabTreeNode } from '../../../../types/platform/gitlab';
 import { GitlabHttp } from '../../../../util/http/gitlab';
@@ -20,7 +22,7 @@ export async function getTags(
     repository
   )}/repository/tags?per_page=100`;
   try {
-    const res = await http.getJson<{ name: string }[]>(url, {
+    const res = await http.getJson<GitlabTag[]>(url, {
       paginate: true,
     });
 
@@ -32,7 +34,10 @@ export async function getTags(
 
     return tags.map((tag) => tag.name).filter(Boolean);
   } catch (err) {
-    logger.info({ sourceRepo: repository }, 'Failed to fetch Gitlab tags');
+    logger.debug(
+      { sourceRepo: repository, err },
+      'Failed to fetch Gitlab tags'
+    );
     // istanbul ignore if
     if (err.message?.includes('Bad credentials')) {
       logger.warn('Bad credentials triggering tag fail lookup in changelog');
@@ -101,14 +106,8 @@ export async function getReleaseList(
   const apiUrl = `${ensureTrailingSlash(
     apiBaseUrl
   )}projects/${repoId}/releases`;
-  const res = await http.getJson<
-    {
-      name: string;
-      release: string;
-      description: string;
-      tag_name: string;
-    }[]
-  >(`${apiUrl}?per_page=100`, {
+
+  const res = await http.getJson<GitlabRelease[]>(`${apiUrl}?per_page=100`, {
     paginate: true,
   });
   return res.body.map((release) => ({
diff --git a/lib/workers/pr/changelog/release-notes.spec.ts b/lib/workers/pr/changelog/release-notes.spec.ts
index 526e23d190..3a86c3db88 100644
--- a/lib/workers/pr/changelog/release-notes.spec.ts
+++ b/lib/workers/pr/changelog/release-notes.spec.ts
@@ -10,7 +10,12 @@ import {
   getReleaseNotesMd,
   releaseNotesCacheMinutes,
 } from './release-notes';
-import type { ChangeLogNotes } from './types';
+import type {
+  ChangeLogNotes,
+  ChangeLogProject,
+  ChangeLogRelease,
+  ChangeLogResult,
+} from './types';
 
 jest.mock('../../../util/host-rules');
 
@@ -37,6 +42,18 @@ const gitlabTreeResponse = [
   { path: 'README.md', type: 'blob' },
 ];
 
+const githubProject = {
+  type: 'github',
+  apiBaseUrl: 'https://api.github.com/',
+  baseUrl: 'https://github.com/',
+} as ChangeLogProject;
+
+const gitlabProject = {
+  type: 'gitlab',
+  apiBaseUrl: 'https://gitlab.com/api/v4/',
+  baseUrl: 'https://gitlab.com/',
+} as ChangeLogProject;
+
 describe('workers/pr/changelog/release-notes', () => {
   beforeEach(() => {
     hostRules.find.mockReturnValue({});
@@ -60,6 +77,7 @@ describe('workers/pr/changelog/release-notes', () => {
     it('handles date object', () => {
       expect(releaseNotesCacheMinutes(new Date())).toEqual(55);
     });
+
     it.each([null, undefined, 'fake', 123])('handles invalid: %s', (date) => {
       expect(releaseNotesCacheMinutes(date as never)).toEqual(55);
     });
@@ -69,31 +87,47 @@ describe('workers/pr/changelog/release-notes', () => {
     it('returns input if invalid', async () => {
       const input = { a: 1 };
       expect(await addReleaseNotes(input as never)).toEqual(input);
+      expect(await addReleaseNotes(null)).toBeNull();
+      expect(await addReleaseNotes({ versions: [] } as never)).toStrictEqual({
+        versions: [],
+      });
     });
+
     it('returns ChangeLogResult', async () => {
       const input = {
         a: 1,
-        project: { github: 'https://github.com/nodeca/js-yaml' },
+        project: {
+          type: 'github',
+          repository: 'https://github.com/nodeca/js-yaml',
+        },
         versions: [{ version: '3.10.0', compare: { url: '' } }],
       };
       // FIXME: explicit assert condition
       expect(await addReleaseNotes(input as never)).toMatchSnapshot();
     });
+
     it('returns ChangeLogResult without release notes', async () => {
       const input = {
         a: 1,
-        project: { gitlab: 'https://gitlab.com/gitlab-org/gitter/webapp/' },
-        versions: [{ version: '20.26.0', compare: { url: '' } }],
-      };
+        project: {
+          type: 'gitlab',
+          repository: 'https://gitlab.com/gitlab-org/gitter/webapp/',
+        } as ChangeLogProject,
+        versions: [
+          { version: '20.26.0', compare: { url: '' } } as ChangeLogRelease,
+        ],
+      } as ChangeLogResult;
       // FIXME: explicit assert condition
-      expect(await addReleaseNotes(input as never)).toMatchSnapshot();
+      expect(await addReleaseNotes(input)).toMatchSnapshot();
     });
   });
+
   describe('getReleaseList()', () => {
     it('should return empty array if no apiBaseUrl', async () => {
-      const res = await getReleaseList('', 'some/yet-other-repository');
+      const res = await getReleaseList({} as ChangeLogProject);
       expect(res).toEqual([]);
     });
+
     it('should return release list for github repo', async () => {
       httpMock
         .scope('https://api.github.com/')
@@ -106,14 +140,15 @@ describe('workers/pr/changelog/release-notes', () => {
           },
         ]);
 
-      const res = await getReleaseList(
-        'https://api.github.com/',
-        'some/yet-other-repository'
-      );
+      const res = await getReleaseList({
+        ...githubProject,
+        repository: 'some/yet-other-repository',
+      });
       // FIXME: explicit assert condition
       expect(res).toMatchSnapshot();
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
+
     it('should return release list for gitlab.com project', async () => {
       httpMock
         .scope('https://gitlab.com/')
@@ -127,14 +162,15 @@ describe('workers/pr/changelog/release-notes', () => {
             body: 'some body #123, [#124](https://gitlab.com/some/yet-other-repository/issues/124)',
           },
         ]);
-      const res = await getReleaseList(
-        'https://gitlab.com/api/v4/',
-        'some/yet-other-repository'
-      );
+      const res = await getReleaseList({
+        ...gitlabProject,
+        repository: 'some/yet-other-repository',
+      });
       // FIXME: explicit assert condition
       expect(res).toMatchSnapshot();
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
+
     it('should return release list for self hosted gitlab project', async () => {
       hostRules.find.mockReturnValue({ token: 'some-token' });
       httpMock
@@ -149,15 +185,18 @@ describe('workers/pr/changelog/release-notes', () => {
             body: 'some body #123, [#124](https://my.custom.domain/some/yet-other-repository/issues/124)',
           },
         ]);
-      const res = await getReleaseList(
-        'https://my.custom.domain/api/v4/',
-        'some/yet-other-repository'
-      );
+      const res = await getReleaseList({
+        ...gitlabProject,
+        repository: 'some/yet-other-repository',
+        apiBaseUrl: 'https://my.custom.domain/api/v4/',
+        baseUrl: 'https://my.custom.domain/',
+      });
       // FIXME: explicit assert condition
       expect(res).toMatchSnapshot();
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
   });
+
   describe('getReleaseNotes()', () => {
     it('should return null for release notes without body', async () => {
       httpMock
@@ -165,17 +204,19 @@ describe('workers/pr/changelog/release-notes', () => {
         .get('/repos/some/repository/releases?per_page=100')
         .reply(200, [{ tag_name: 'v1.0.0' }, { tag_name: 'v1.0.1' }]);
       const res = await getReleaseNotes(
-        'some/repository',
-        '1.0.0',
-        'some',
-        'https://github.com/',
-        'https://api.github.com/'
+        {
+          ...githubProject,
+          repository: 'some/repository',
+          depName: 'some',
+        },
+        '1.0.0'
       );
       expect(res).toBeNull();
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
+
     it.each([[''], ['v'], ['other-'], ['other_v'], ['other@']])(
-      'gets release notes with body',
+      'gets release notes with body "%s"',
       async (prefix) => {
         httpMock
           .scope('https://api.github.com/')
@@ -188,19 +229,21 @@ describe('workers/pr/changelog/release-notes', () => {
             },
           ]);
         const res = await getReleaseNotes(
-          'some/other-repository',
-          '1.0.1',
-          'other',
-          'https://github.com/',
-          'https://api.github.com/'
+          {
+            ...githubProject,
+            repository: 'some/other-repository',
+            depName: 'other',
+          },
+          '1.0.1'
         );
         // FIXME: explicit assert condition
         expect(res).toMatchSnapshot();
         expect(httpMock.getTrace()).toMatchSnapshot();
       }
     );
+
     it.each([[''], ['v'], ['other-']])(
-      'gets release notes with body from gitlab repo %s',
+      'gets release notes with body from gitlab repo "%s"',
       async (prefix) => {
         httpMock
           .scope('https://api.gitlab.com/')
@@ -209,52 +252,53 @@ describe('workers/pr/changelog/release-notes', () => {
             { tag_name: `${prefix}1.0.0` },
             {
               tag_name: `${prefix}1.0.1`,
-              body: 'some body #123, [#124](https://gitlab.com/some/yet-other-repository/issues/124)',
+              description:
+                'some body #123, [#124](https://gitlab.com/some/yet-other-repository/issues/124)',
             },
           ]);
 
         const res = await getReleaseNotes(
-          'some/other-repository',
-          '1.0.1',
-          'other',
-          'https://gitlab.com/',
-          'https://api.gitlab.com/'
+          {
+            ...gitlabProject,
+            repository: 'some/other-repository',
+            depName: 'other',
+            apiBaseUrl: 'https://api.gitlab.com/',
+          },
+          '1.0.1'
         );
         // FIXME: explicit assert condition
         expect(res).toMatchSnapshot();
         expect(httpMock.getTrace()).toMatchSnapshot();
       }
     );
-    it.each([[''], ['v'], ['other-']])(
-      'gets null from repository without gitlab/github in domain %s',
-      async (prefix) => {
-        // FIXME: Should not call `api.lol.lol` ?
-        httpMock
-          .scope('https://api.lol.lol')
-          .get('/repos/some/other-repository/releases?per_page=100')
-          .reply(404);
-        const res = await getReleaseNotes(
-          'some/other-repository',
-          '1.0.1',
-          'other',
-          'https://lol.lol/',
-          'https://api.lol.lol/'
-        );
-        expect(res).toBeNull();
-      }
-    );
+
+    it('gets null from repository without gitlab/github in domain', async () => {
+      const res = await getReleaseNotes(
+        {
+          repository: 'some/repository',
+          depName: 'other',
+          apiBaseUrl: 'https://api.lol.lol/',
+          baseUrl: 'https://lol.lol/',
+        } as ChangeLogProject,
+        '1.0.1'
+      );
+      expect(res).toBeNull();
+    });
   });
+
   describe('getReleaseNotesMd()', () => {
     it('handles not found', async () => {
       httpMock.scope('https://api.github.com').get('/repos/chalk').reply(404);
       const res = await getReleaseNotesMd(
-        'chalk',
-        '2.0.0',
-        'https://github.com/',
-        'https://api.github.com/'
+        {
+          ...githubProject,
+          repository: 'chalk',
+        },
+        '2.0.0'
       );
       expect(res).toBeNull();
     });
+
     it('handles files mismatch', async () => {
       httpMock
         .scope('https://api.github.com')
@@ -268,14 +312,16 @@ describe('workers/pr/changelog/release-notes', () => {
           ],
         });
       const res = await getReleaseNotesMd(
-        'chalk',
-        '2.0.0',
-        'https://github.com/',
-        'https://api.github.com/'
+        {
+          ...githubProject,
+          repository: 'chalk',
+        },
+        '2.0.0'
       );
       expect(res).toBeNull();
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
+
     it('handles wrong format', async () => {
       httpMock
         .scope('https://api.github.com')
@@ -288,14 +334,16 @@ describe('workers/pr/changelog/release-notes', () => {
           content: Buffer.from('not really markdown').toString('base64'),
         });
       const res = await getReleaseNotesMd(
-        'some/repository1',
-        '1.0.0',
-        'https://github.com/',
-        'https://api.github.com/'
+        {
+          ...githubProject,
+          repository: 'some/repository1',
+        },
+        '1.0.0'
       );
       expect(res).toBeNull();
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
+
     it('handles bad markdown', async () => {
       httpMock
         .scope('https://api.github.com')
@@ -308,14 +356,16 @@ describe('workers/pr/changelog/release-notes', () => {
           content: Buffer.from(`#\nha\nha\n#\nha\nha`).toString('base64'),
         });
       const res = await getReleaseNotesMd(
-        'some/repository2',
-        '1.0.0',
-        'https://github.com/',
-        'https://api.github.com/'
+        {
+          ...githubProject,
+          repository: 'some/repository2',
+        },
+        '1.0.0'
       );
       expect(res).toBeNull();
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
+
     it('parses angular.js', async () => {
       httpMock
         .scope('https://api.github.com')
@@ -328,16 +378,18 @@ describe('workers/pr/changelog/release-notes', () => {
           content: Buffer.from(angularJsChangelogMd).toString('base64'),
         });
       const res = await getReleaseNotesMd(
-        'angular/angular.js',
-        '1.6.9',
-        'https://github.com/',
-        'https://api.github.com/'
+        {
+          ...githubProject,
+          repository: 'angular/angular.js',
+        },
+        '1.6.9'
       );
       // FIXME: explicit assert condition
       expect(res).not.toBeNull();
       expect(res).toMatchSnapshot();
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
+
     it('parses gitlab.com/gitlab-org/gitter/webapp', async () => {
       jest.setTimeout(0);
       httpMock
@@ -349,16 +401,19 @@ describe('workers/pr/changelog/release-notes', () => {
         .get('/projects/gitlab-org%2fgitter%2fwebapp/repository/blobs/abcd/raw')
         .reply(200, gitterWebappChangelogMd);
       const res = await getReleaseNotesMd(
-        'gitlab-org/gitter/webapp',
-        '20.26.0',
-        'https://gitlab.com/',
-        'https://api.gitlab.com/'
+        {
+          ...gitlabProject,
+          repository: 'gitlab-org/gitter/webapp',
+          apiBaseUrl: 'https://api.gitlab.com/',
+        },
+        '20.26.0'
       );
       expect(httpMock.getTrace()).toMatchSnapshot();
       // FIXME: explicit assert condition
       expect(res).not.toBeNull();
       expect(res).toMatchSnapshot();
     });
+
     it('parses self hosted gitlab', async () => {
       hostRules.find.mockReturnValue({ token: 'some-token' });
       jest.setTimeout(0);
@@ -371,16 +426,20 @@ describe('workers/pr/changelog/release-notes', () => {
         .get('/projects/gitlab-org%2fgitter%2fwebapp/repository/blobs/abcd/raw')
         .reply(200, gitterWebappChangelogMd);
       const res = await getReleaseNotesMd(
-        'gitlab-org/gitter/webapp',
-        '20.26.0',
-        'https://my.custom.domain/',
-        'https://my.custom.domain/'
+        {
+          ...gitlabProject,
+          repository: 'gitlab-org/gitter/webapp',
+          apiBaseUrl: 'https://my.custom.domain/',
+          baseUrl: 'https://my.custom.domain/',
+        },
+        '20.26.0'
       );
       expect(httpMock.getTrace()).toMatchSnapshot();
       // FIXME: explicit assert condition
       expect(res).not.toBeNull();
       expect(res).toMatchSnapshot();
     });
+
     it('parses jest', async () => {
       httpMock
         .scope('https://api.github.com')
@@ -393,16 +452,18 @@ describe('workers/pr/changelog/release-notes', () => {
           content: Buffer.from(jestChangelogMd).toString('base64'),
         });
       const res = await getReleaseNotesMd(
-        'facebook/jest',
-        '22.0.0',
-        'https://github.com/',
-        'https://api.github.com/'
+        {
+          ...githubProject,
+          repository: 'facebook/jest',
+        },
+        '22.0.0'
       );
       expect(httpMock.getTrace()).toMatchSnapshot();
       // FIXME: explicit assert condition
       expect(res).not.toBeNull();
       expect(res).toMatchSnapshot();
     });
+
     it('handles github sourceDirectory', async () => {
       const sourceDirectory = 'packages/foo';
       const subdirTree = clone(githubTreeResponse);
@@ -420,17 +481,19 @@ describe('workers/pr/changelog/release-notes', () => {
           content: Buffer.from(jsYamlChangelogMd).toString('base64'),
         });
       const res = await getReleaseNotesMd(
-        'nodeca/js-yaml',
-        '3.10.0',
-        'https://github.com/',
-        'https://api.github.com/',
-        sourceDirectory
+        {
+          ...githubProject,
+          repository: 'nodeca/js-yaml',
+          sourceDirectory,
+        },
+        '3.10.0'
       );
       expect(httpMock.getTrace()).toMatchSnapshot();
       // FIXME: explicit assert condition
       expect(res).not.toBeNull();
       expect(res).toMatchSnapshot();
     });
+
     it('parses js-yaml', async () => {
       httpMock
         .scope('https://api.github.com')
@@ -443,16 +506,28 @@ describe('workers/pr/changelog/release-notes', () => {
           content: Buffer.from(jsYamlChangelogMd).toString('base64'),
         });
       const res = await getReleaseNotesMd(
-        'nodeca/js-yaml',
-        '3.10.0',
-        'https://github.com/',
-        'https://api.github.com/'
+        {
+          ...githubProject,
+          repository: 'nodeca/js-yaml',
+        },
+        '3.10.0'
       );
       expect(httpMock.getTrace()).toMatchSnapshot();
       // FIXME: explicit assert condition
       expect(res).not.toBeNull();
       expect(res).toMatchSnapshot();
     });
+
+    it('ignores invalid', async () => {
+      const res = await getReleaseNotesMd(
+        {
+          repository: 'nodeca/js-yaml',
+        } as ChangeLogProject,
+        '3.10.0'
+      );
+      expect(res).toBeNull();
+    });
+
     describe('ReleaseNotes Correctness', () => {
       let versionOneNotes: ChangeLogNotes;
       let versionTwoNotes: ChangeLogNotes;
@@ -468,10 +543,11 @@ describe('workers/pr/changelog/release-notes', () => {
             content: Buffer.from(yargsChangelogMd).toString('base64'),
           });
         const res = await getReleaseNotesMd(
-          'yargs/yargs',
-          '15.3.0',
-          'https://github.com/',
-          'https://api.github.com/'
+          {
+            ...githubProject,
+            repository: 'yargs/yargs',
+          },
+          '15.3.0'
         );
         versionOneNotes = res;
         expect(httpMock.getTrace()).toMatchSnapshot();
@@ -479,6 +555,7 @@ describe('workers/pr/changelog/release-notes', () => {
         expect(res).not.toBeNull();
         expect(res).toMatchSnapshot();
       });
+
       it('parses yargs 15.2.0', async () => {
         httpMock
           .scope('https://api.github.com')
@@ -491,10 +568,11 @@ describe('workers/pr/changelog/release-notes', () => {
             content: Buffer.from(yargsChangelogMd).toString('base64'),
           });
         const res = await getReleaseNotesMd(
-          'yargs/yargs',
-          '15.2.0',
-          'https://github.com/',
-          'https://api.github.com/'
+          {
+            ...githubProject,
+            repository: 'yargs/yargs',
+          },
+          '15.2.0'
         );
         versionTwoNotes = res;
         expect(httpMock.getTrace()).toMatchSnapshot();
@@ -502,6 +580,7 @@ describe('workers/pr/changelog/release-notes', () => {
         expect(res).not.toBeNull();
         expect(res).toMatchSnapshot();
       });
+
       it('parses adapter-utils 4.33.0', async () => {
         httpMock
           .scope('https://gitlab.com/')
@@ -514,10 +593,11 @@ describe('workers/pr/changelog/release-notes', () => {
           )
           .reply(200, adapterutilsChangelogMd);
         const res = await getReleaseNotesMd(
-          'itentialopensource/adapter-utils',
-          '4.33.0',
-          'https://gitlab.com/',
-          'https://gitlab.com/api/v4/'
+          {
+            ...gitlabProject,
+            repository: 'itentialopensource/adapter-utils',
+          },
+          '4.33.0'
         );
         versionTwoNotes = res;
         expect(httpMock.getTrace()).toMatchSnapshot();
@@ -525,6 +605,7 @@ describe('workers/pr/changelog/release-notes', () => {
         expect(res).not.toBeNull();
         expect(res).toMatchSnapshot();
       });
+
       it('handles gitlab sourceDirectory', async () => {
         const sourceDirectory = 'packages/foo';
         const response = clone(gitlabTreeResponse).map((file) => ({
@@ -542,11 +623,12 @@ describe('workers/pr/changelog/release-notes', () => {
           )
           .reply(200, adapterutilsChangelogMd);
         const res = await getReleaseNotesMd(
-          'itentialopensource/adapter-utils',
-          '4.33.0',
-          'https://gitlab.com/',
-          'https://gitlab.com/api/v4/',
-          sourceDirectory
+          {
+            ...gitlabProject,
+            repository: 'itentialopensource/adapter-utils',
+            sourceDirectory,
+          },
+          '4.33.0'
         );
         versionTwoNotes = res;
         expect(httpMock.getTrace()).toMatchSnapshot();
@@ -554,9 +636,11 @@ describe('workers/pr/changelog/release-notes', () => {
         expect(res).not.toBeNull();
         expect(res).toMatchSnapshot();
       });
+
       it('isUrl', () => {
         expect(versionOneNotes).not.toMatchObject(versionTwoNotes);
       });
+
       it('15.3.0 is not equal to 15.2.0', () => {
         expect(versionOneNotes).not.toMatchObject(versionTwoNotes);
       });
diff --git a/lib/workers/pr/changelog/release-notes.ts b/lib/workers/pr/changelog/release-notes.ts
index 51fba383c3..43067c9709 100644
--- a/lib/workers/pr/changelog/release-notes.ts
+++ b/lib/workers/pr/changelog/release-notes.ts
@@ -2,64 +2,61 @@ import * as URL from 'url';
 import is from '@sindresorhus/is';
 import { DateTime } from 'luxon';
 import MarkdownIt from 'markdown-it';
-import { PLATFORM_TYPE_GITLAB } from '../../../constants/platforms';
 import { logger } from '../../../logger';
 import * as memCache from '../../../util/cache/memory';
 import * as packageCache from '../../../util/cache/package';
-import * as hostRules from '../../../util/host-rules';
 import { linkify } from '../../../util/markdown';
 import * as github from './github';
 import * as gitlab from './gitlab';
-import type { ChangeLogFile, ChangeLogNotes, ChangeLogResult } from './types';
+import type {
+  ChangeLogFile,
+  ChangeLogNotes,
+  ChangeLogProject,
+  ChangeLogResult,
+} from './types';
 
 const markdown = new MarkdownIt('zero');
 markdown.enable(['heading', 'lheading']);
 
 export async function getReleaseList(
-  apiBaseUrl: string,
-  repository: string
+  project: ChangeLogProject
 ): Promise<ChangeLogNotes[]> {
   logger.trace('getReleaseList()');
-  // istanbul ignore if
-  if (!apiBaseUrl) {
-    logger.debug('No apiBaseUrl');
-    return [];
-  }
+  const { apiBaseUrl, repository, type } = project;
   try {
-    if (apiBaseUrl.includes('gitlab')) {
-      return await gitlab.getReleaseList(apiBaseUrl, repository);
-    }
+    switch (type) {
+      case 'gitlab':
+        return await gitlab.getReleaseList(apiBaseUrl, repository);
+      case 'github':
+        return await github.getReleaseList(apiBaseUrl, repository);
 
-    const opts = hostRules.find({
-      hostType: PLATFORM_TYPE_GITLAB,
-      url: apiBaseUrl,
-    });
-    if (opts.token) {
-      return await gitlab.getReleaseList(apiBaseUrl, repository);
+      default:
+        logger.warn({ apiBaseUrl, repository, type }, 'Invalid project type');
+        return [];
     }
-
-    return await github.getReleaseList(apiBaseUrl, repository);
   } catch (err) /* istanbul ignore next */ {
     if (err.statusCode === 404) {
-      logger.debug({ repository }, 'getReleaseList 404');
+      logger.debug({ repository, type, apiBaseUrl }, 'getReleaseList 404');
     } else {
-      logger.info({ repository, err }, 'getReleaseList error');
+      logger.debug(
+        { repository, type, apiBaseUrl, err },
+        'getReleaseList error'
+      );
     }
-    return [];
   }
+  return [];
 }
 
 export function getCachedReleaseList(
-  apiBaseUrl: string,
-  repository: string
+  project: ChangeLogProject
 ): Promise<ChangeLogNotes[]> {
-  const cacheKey = `getReleaseList-${apiBaseUrl}-${repository}`;
+  const cacheKey = `getReleaseList-${project.apiBaseUrl}-${project.repository}`;
   const cachedResult = memCache.get<Promise<ChangeLogNotes[]>>(cacheKey);
   // istanbul ignore if
   if (cachedResult !== undefined) {
     return cachedResult;
   }
-  const promisedRes = getReleaseList(apiBaseUrl, repository);
+  const promisedRes = getReleaseList(project);
   memCache.set(cacheKey, promisedRes);
   return promisedRes;
 }
@@ -94,14 +91,12 @@ export function massageBody(
 }
 
 export async function getReleaseNotes(
-  repository: string,
-  version: string,
-  depName: string,
-  baseUrl: string,
-  apiBaseUrl: string
+  project: ChangeLogProject,
+  version: string
 ): Promise<ChangeLogNotes | null> {
+  const { baseUrl, depName, repository } = project;
   logger.trace(`getReleaseNotes(${repository}, ${version}, ${depName})`);
-  const releaseList = await getCachedReleaseList(apiBaseUrl, repository);
+  const releaseList = await getCachedReleaseList(project);
   logger.trace({ releaseList }, 'Release list from getReleaseList');
   let releaseNotes: ChangeLogNotes | null = null;
   for (const release of releaseList) {
@@ -172,84 +167,70 @@ function isUrl(url: string): boolean {
 }
 
 export async function getReleaseNotesMdFileInner(
-  repository: string,
-  apiBaseUrl: string,
-  sourceDirectory?: string
+  project: ChangeLogProject
 ): Promise<ChangeLogFile> | null {
+  const { apiBaseUrl, repository, sourceDirectory, type } = project;
   try {
-    if (apiBaseUrl.includes('gitlab')) {
-      return await gitlab.getReleaseNotesMd(
-        repository,
-        apiBaseUrl,
-        sourceDirectory
-      );
-    }
+    switch (type) {
+      case 'gitlab':
+        return await gitlab.getReleaseNotesMd(
+          repository,
+          apiBaseUrl,
+          sourceDirectory
+        );
+      case 'github':
+        return await github.getReleaseNotesMd(
+          repository,
+          apiBaseUrl,
+          sourceDirectory
+        );
 
-    const opts = hostRules.find({
-      hostType: PLATFORM_TYPE_GITLAB,
-      url: apiBaseUrl,
-    });
-    if (opts.token) {
-      return await gitlab.getReleaseNotesMd(
-        repository,
-        apiBaseUrl,
-        sourceDirectory
-      );
+      default:
+        logger.warn({ apiBaseUrl, repository, type }, 'Invalid project type');
+        return null;
     }
-
-    return await github.getReleaseNotesMd(
-      repository,
-      apiBaseUrl,
-      sourceDirectory
-    );
   } catch (err) /* istanbul ignore next */ {
     if (err.statusCode === 404) {
-      logger.debug('Error 404 getting changelog md');
+      logger.debug(
+        { repository, type, apiBaseUrl },
+        'Error 404 getting changelog md'
+      );
     } else {
-      logger.debug({ err, repository }, 'Error getting changelog md');
+      logger.debug(
+        { err, repository, type, apiBaseUrl },
+        'Error getting changelog md'
+      );
     }
-    return null;
   }
+  return null;
 }
 
 export function getReleaseNotesMdFile(
-  repository: string,
-  apiBaseUrl: string,
-  sourceDirectory?: string
+  project: ChangeLogProject
 ): Promise<ChangeLogFile | null> {
-  const cacheKey = `getReleaseNotesMdFile-${repository}-${apiBaseUrl}`;
+  const cacheKey = `getReleaseNotesMdFile-${project.repository}-${project.apiBaseUrl}`;
   const cachedResult = memCache.get<Promise<ChangeLogFile | null>>(cacheKey);
   // istanbul ignore if
   if (cachedResult !== undefined) {
     return cachedResult;
   }
-  const promisedRes = getReleaseNotesMdFileInner(
-    repository,
-    apiBaseUrl,
-    sourceDirectory
-  );
+  const promisedRes = getReleaseNotesMdFileInner(project);
   memCache.set(cacheKey, promisedRes);
   return promisedRes;
 }
 
 export async function getReleaseNotesMd(
-  repository: string,
-  version: string,
-  baseUrl: string,
-  apiBaseUrl: string,
-  sourceDirectory?: string
+  project: ChangeLogProject,
+  version: string
 ): Promise<ChangeLogNotes | null> {
+  const { baseUrl, repository } = project;
   logger.trace(`getReleaseNotesMd(${repository}, ${version})`);
   const skippedRepos = ['facebook/react-native'];
   // istanbul ignore if
   if (skippedRepos.includes(repository)) {
     return null;
   }
-  const changelog = await getReleaseNotesMdFile(
-    repository,
-    apiBaseUrl,
-    sourceDirectory
-  );
+  const changelog = await getReleaseNotesMdFile(project);
   if (!changelog) {
     return null;
   }
@@ -332,20 +313,13 @@ export function releaseNotesCacheMinutes(releaseDate?: string | Date): number {
 export async function addReleaseNotes(
   input: ChangeLogResult
 ): Promise<ChangeLogResult> {
-  if (
-    !input?.versions ||
-    (!input?.project?.github && !input?.project?.gitlab)
-  ) {
+  if (!input?.versions || !input.project?.type) {
     logger.debug('Missing project or versions');
     return input;
   }
   const output: ChangeLogResult = { ...input, versions: [] };
-  const repository = input.project.github
-    ? input.project.github.replace(/\.git$/, '')
-    : input.project.gitlab;
-  const cacheNamespace = input.project.github
-    ? 'changelog-github-notes'
-    : 'changelog-gitlab-notes';
+  const repository = input.project.repository;
+  const cacheNamespace = `changelog-${input.project.type}-notes`;
   function getCacheKey(version: string): string {
     return `${repository}:${version}`;
   }
@@ -355,23 +329,10 @@ export async function addReleaseNotes(
     releaseNotes = await packageCache.get(cacheNamespace, cacheKey);
     // istanbul ignore else: no cache tests
     if (!releaseNotes) {
-      const { sourceDirectory } = input.project;
-      releaseNotes = await getReleaseNotesMd(
-        repository,
-        v.version,
-        input.project.baseUrl,
-        input.project.apiBaseUrl,
-        sourceDirectory
-      );
+      releaseNotes = await getReleaseNotesMd(input.project, v.version);
       // istanbul ignore else: should be tested
       if (!releaseNotes) {
-        releaseNotes = await getReleaseNotes(
-          repository,
-          v.version,
-          input.project.depName,
-          input.project.baseUrl,
-          input.project.apiBaseUrl
-        );
+        releaseNotes = await getReleaseNotes(input.project, v.version);
       }
       // Small hack to force display of release notes when there is a compare url
       if (!releaseNotes && v.compare.url) {
diff --git a/lib/workers/pr/changelog/source-github.ts b/lib/workers/pr/changelog/source-github.ts
index b4577088b4..3dee765d2e 100644
--- a/lib/workers/pr/changelog/source-github.ts
+++ b/lib/workers/pr/changelog/source-github.ts
@@ -68,7 +68,10 @@ export async function getChangeLogJSON({
   const apiBaseUrl = sourceUrl.startsWith('https://github.com/')
     ? 'https://api.github.com/'
     : baseUrl + 'api/v3/';
-  const repository = pathname.slice(1).replace(/\/$/, '');
+  const repository = pathname
+    .slice(1)
+    .replace(/\/$/, '')
+    .replace(/\.git$/, '');
   if (repository.split('/').length !== 2) {
     logger.debug({ sourceUrl }, 'Invalid github URL found');
     return null;
@@ -154,8 +157,9 @@ export async function getChangeLogJSON({
     project: {
       apiBaseUrl,
       baseUrl,
-      github: repository,
-      repository: sourceUrl,
+      type: 'github',
+      repository,
+      sourceUrl,
       sourceDirectory,
       depName,
     },
diff --git a/lib/workers/pr/changelog/source-gitlab.ts b/lib/workers/pr/changelog/source-gitlab.ts
index c63c91c485..c241bb9a04 100644
--- a/lib/workers/pr/changelog/source-gitlab.ts
+++ b/lib/workers/pr/changelog/source-gitlab.ts
@@ -130,8 +130,9 @@ export async function getChangeLogJSON({
     project: {
       apiBaseUrl,
       baseUrl,
-      gitlab: repository,
-      repository: sourceUrl,
+      type: 'gitlab',
+      repository,
+      sourceUrl,
       depName,
     },
     versions: changelogReleases,
diff --git a/lib/workers/pr/changelog/types.ts b/lib/workers/pr/changelog/types.ts
index 554eefc2c4..48217fc59e 100644
--- a/lib/workers/pr/changelog/types.ts
+++ b/lib/workers/pr/changelog/types.ts
@@ -22,11 +22,11 @@ export interface ChangeLogRelease {
 
 export interface ChangeLogProject {
   depName?: string;
-  github?: string;
-  gitlab?: string;
+  type: 'github' | 'gitlab';
   apiBaseUrl?: string;
   baseUrl: string;
   repository: string;
+  sourceUrl: string;
   sourceDirectory?: string;
 }
 
diff --git a/lib/workers/pr/index.spec.ts b/lib/workers/pr/index.spec.ts
index 746c5dff8f..d822834793 100644
--- a/lib/workers/pr/index.spec.ts
+++ b/lib/workers/pr/index.spec.ts
@@ -1,21 +1,18 @@
-import { git, mocked, partial } from '../../../test/util';
-import { getConfig } from '../../config/defaults';
+import { getConfig, git, mocked, partial, platform } from '../../../test/util';
 import { PLATFORM_TYPE_GITLAB } from '../../constants/platforms';
-import { Pr, platform as _platform } from '../../platform';
+import type { Pr } from '../../platform/types';
 import { BranchStatus } from '../../types';
 import * as _limits from '../global/limits';
 import type { BranchConfig } from '../types';
 import * as prAutomerge from './automerge';
 import * as _changelogHelper from './changelog';
-import { getChangeLogJSON } from './changelog';
+import type { ChangeLogResult } from './changelog';
 import * as codeOwners from './code-owners';
 import * as prWorker from '.';
 
 const codeOwnersMock = mocked(codeOwners);
 const changelogHelper = mocked(_changelogHelper);
 const gitlabChangelogHelper = mocked(_changelogHelper);
-const platform = mocked(_platform);
-const defaultConfig = getConfig();
 const limits = mocked(_limits);
 
 jest.mock('../../util/git');
@@ -24,12 +21,12 @@ jest.mock('./code-owners');
 jest.mock('../global/limits');
 
 function setupChangelogMock() {
-  changelogHelper.getChangeLogJSON = jest.fn();
   const resultValue = {
     project: {
+      type: 'github',
       baseUrl: 'https://github.com/',
-      github: 'renovateapp/dummy',
-      repository: 'https://github.com/renovateapp/dummy',
+      repository: 'renovateapp/dummy',
+      sourceUrl: 'https://github.com/renovateapp/dummy',
     },
     hasReleaseNotes: true,
     versions: [
@@ -51,7 +48,7 @@ function setupChangelogMock() {
         },
       },
     ],
-  };
+  } as ChangeLogResult;
   const errorValue = {
     error: _changelogHelper.ChangeLogError.MissingGithubToken,
   };
@@ -61,12 +58,12 @@ function setupChangelogMock() {
 }
 
 function setupGitlabChangelogMock() {
-  gitlabChangelogHelper.getChangeLogJSON = jest.fn();
   const resultValue = {
     project: {
+      type: 'gitlab',
       baseUrl: 'https://gitlab.com/',
-      gitlab: 'renovateapp/gitlabdummy',
-      repository: 'https://gitlab.com/renovateapp/gitlabdummy',
+      repository: 'renovateapp/gitlabdummy',
+      sourceUrl: 'https://gitlab.com/renovateapp/gitlabdummy',
     },
     hasReleaseNotes: true,
     versions: [
@@ -88,7 +85,7 @@ function setupGitlabChangelogMock() {
         },
       },
     ],
-  };
+  } as ChangeLogResult;
   const errorValue = {
     error: _changelogHelper.ChangeLogError.MissingGithubToken,
   };
@@ -103,7 +100,7 @@ describe('workers/pr/index', () => {
     let pr: Pr;
     beforeEach(() => {
       config = partial<BranchConfig>({
-        ...defaultConfig,
+        ...getConfig(),
       });
       pr = partial<Pr>({
         canMerge: true,
@@ -180,7 +177,7 @@ describe('workers/pr/index', () => {
       jest.resetAllMocks();
       setupChangelogMock();
       config = partial<BranchConfig>({
-        ...defaultConfig,
+        ...getConfig(),
       });
       config.branchName = 'renovate/dummy-1.x';
       config.prTitle = 'Update dependency dummy to v1.1.0';
@@ -199,9 +196,7 @@ describe('workers/pr/index', () => {
         displayNumber: 'New Pull Request',
       } as never);
       config.upgrades = [config];
-      platform.massageMarkdown = jest.fn((input) => input);
-      platform.getBranchPr = jest.fn();
-      platform.getBranchStatus = jest.fn();
+      platform.massageMarkdown.mockImplementation((input) => input);
     });
     afterEach(() => {
       jest.clearAllMocks();
@@ -252,7 +247,7 @@ describe('workers/pr/index', () => {
     });
     it('should create PR if success', async () => {
       platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green);
-      config.logJSON = await getChangeLogJSON(config);
+      config.logJSON = await changelogHelper.getChangeLogJSON(config);
       config.prCreation = 'status-success';
       config.automerge = true;
       config.schedule = ['before 5am'];
@@ -264,7 +259,7 @@ describe('workers/pr/index', () => {
     });
     it('should not create PR if limit is reached', async () => {
       platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green);
-      config.logJSON = await getChangeLogJSON(config);
+      config.logJSON = await changelogHelper.getChangeLogJSON(config);
       config.prCreation = 'status-success';
       config.automerge = true;
       config.schedule = ['before 5am'];
@@ -275,7 +270,7 @@ describe('workers/pr/index', () => {
     });
     it('should create PR if limit is reached but dashboard checked', async () => {
       platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green);
-      config.logJSON = await getChangeLogJSON(config);
+      config.logJSON = await changelogHelper.getChangeLogJSON(config);
       config.prCreation = 'status-success';
       config.automerge = true;
       config.schedule = ['before 5am'];
@@ -315,7 +310,7 @@ describe('workers/pr/index', () => {
       config.recreateClosed = true;
       config.rebaseWhen = 'never';
       for (const upgrade of config.upgrades) {
-        upgrade.logJSON = await getChangeLogJSON(upgrade);
+        upgrade.logJSON = await changelogHelper.getChangeLogJSON(upgrade);
       }
       const { pr } = await prWorker.ensurePr(config);
       expect(pr).toMatchObject({ displayNumber: 'New Pull Request' });
@@ -330,7 +325,7 @@ describe('workers/pr/index', () => {
       config.schedule = ['before 5am'];
       config.timezone = 'some timezone';
       config.rebaseWhen = 'behind-base-branch';
-      config.logJSON = await getChangeLogJSON(config);
+      config.logJSON = await changelogHelper.getChangeLogJSON(config);
       const { pr } = await prWorker.ensurePr(config);
       expect(pr).toMatchObject({ displayNumber: 'New Pull Request' });
       // FIXME: explicit assert condition
@@ -341,7 +336,6 @@ describe('workers/pr/index', () => {
     });
     it('should return null if creating PR fails', async () => {
       platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green);
-      platform.createPr = jest.fn();
       platform.createPr.mockImplementationOnce(() => {
         throw new Error('Validation Failed (422)');
       });
@@ -405,6 +399,7 @@ describe('workers/pr/index', () => {
       config.assignees = ['@foo', 'bar'];
       config.reviewers = ['foo', '@bar', 'foo@bar.com'];
       config.filterUnavailableUsers = true;
+      // optional function is undefined by jest
       platform.filterUnavailableUsers = jest.fn();
       platform.filterUnavailableUsers.mockResolvedValue(['foo']);
       await prWorker.ensurePr(config);
@@ -523,7 +518,7 @@ describe('workers/pr/index', () => {
       config.semanticCommitScope = null;
       config.automerge = true;
       config.schedule = ['before 5am'];
-      config.logJSON = await getChangeLogJSON(config);
+      config.logJSON = await changelogHelper.getChangeLogJSON(config);
       const { pr } = await prWorker.ensurePr(config);
       expect(platform.updatePr.mock.calls).toMatchSnapshot();
       expect(platform.updatePr).toHaveBeenCalledTimes(0);
@@ -537,7 +532,7 @@ describe('workers/pr/index', () => {
       config.semanticCommitScope = null;
       config.automerge = true;
       config.schedule = ['before 5am'];
-      config.logJSON = await getChangeLogJSON(config);
+      config.logJSON = await changelogHelper.getChangeLogJSON(config);
       const { pr } = await prWorker.ensurePr(config);
       expect(platform.updatePr).toHaveBeenCalledTimes(0);
       expect(pr).toMatchObject(modifiedPr);
@@ -546,7 +541,7 @@ describe('workers/pr/index', () => {
       config.newValue = '1.2.0';
       config.automerge = true;
       config.schedule = ['before 5am'];
-      config.logJSON = await getChangeLogJSON(config);
+      config.logJSON = await changelogHelper.getChangeLogJSON(config);
       platform.getBranchPr.mockResolvedValueOnce(existingPr);
       const { pr } = await prWorker.ensurePr(config);
       // FIXME: explicit assert condition
@@ -614,9 +609,8 @@ describe('workers/pr/index', () => {
       platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green);
       config.prCreation = 'status-success';
       config.privateRepo = false;
-      config.logJSON = await getChangeLogJSON(config);
-      config.logJSON.project.gitlab = 'someproject';
-      delete config.logJSON.project.github;
+      config.logJSON = await changelogHelper.getChangeLogJSON(config);
+      config.logJSON.project.repository = 'someproject';
       const { pr } = await prWorker.ensurePr(config);
       expect(pr).toMatchObject({ displayNumber: 'New Pull Request' });
       expect(platform.createPr.mock.calls[0]).toMatchSnapshot();
diff --git a/lib/workers/pr/index.ts b/lib/workers/pr/index.ts
index b3cf078dae..f6f317cfdd 100644
--- a/lib/workers/pr/index.ts
+++ b/lib/workers/pr/index.ts
@@ -278,9 +278,7 @@ export async function ensurePr(
     if (logJSON) {
       if (typeof logJSON.error === 'undefined') {
         if (logJSON.project) {
-          upgrade.repoName = logJSON.project.github
-            ? logJSON.project.github
-            : logJSON.project.gitlab;
+          upgrade.repoName = logJSON.project.repository;
         }
         upgrade.hasReleaseNotes = logJSON.hasReleaseNotes;
         upgrade.releases = [];
-- 
GitLab