From fcced24a6ad6dda6edfb216e09c07edc2c4fcffe Mon Sep 17 00:00:00 2001
From: Sergio Zharinov <zharinov@users.noreply.github.com>
Date: Sat, 16 May 2020 12:53:11 +0400
Subject: [PATCH] refactor(github): Remove old Github platform wrappers (#6203)

* refactor(github): Remove old Github platform wrappers

* Refactor 'util/cache/run' imports

* Fix pod http client

* Fix test

* refactor(pod): Split request functions

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
Co-authored-by: Rhys Arkins <rhys@arkins.net>
---
 .../__snapshots__/index.spec.ts.snap          |   16 +
 lib/datasource/github-releases/index.spec.ts  |   44 +-
 lib/datasource/github-releases/index.ts       |    8 +-
 .../__snapshots__/index.spec.ts.snap          |  123 ++
 lib/datasource/github-tags/index.spec.ts      |   98 +-
 lib/datasource/github-tags/index.ts           |   23 +-
 .../pod/__snapshots__/index.spec.ts.snap      |  107 ++
 lib/datasource/pod/index.spec.ts              |  131 +-
 lib/datasource/pod/index.ts                   |   69 +-
 .../github/__snapshots__/index.spec.ts.snap   | 1280 ++++++-----------
 lib/platform/github/gh-got-wrapper.spec.ts    |  239 ---
 lib/platform/github/gh-got-wrapper.ts         |  214 ---
 .../github/gh-graphql-wrapper.spec.ts         |  158 --
 lib/platform/github/gh-graphql-wrapper.ts     |  100 --
 lib/platform/github/index.spec.ts             |   51 +-
 lib/platform/github/index.ts                  |  289 ++--
 .../__snapshots__/index.spec.ts.snap          |  256 ++++
 lib/workers/pr/changelog/index.spec.ts        |  113 +-
 lib/workers/pr/changelog/release-notes.ts     |   12 +-
 lib/workers/pr/changelog/source-github.ts     |    6 +-
 20 files changed, 1392 insertions(+), 1945 deletions(-)
 create mode 100644 lib/datasource/pod/__snapshots__/index.spec.ts.snap
 delete mode 100644 lib/platform/github/gh-got-wrapper.spec.ts
 delete mode 100644 lib/platform/github/gh-got-wrapper.ts
 delete mode 100644 lib/platform/github/gh-graphql-wrapper.spec.ts
 delete mode 100644 lib/platform/github/gh-graphql-wrapper.ts

diff --git a/lib/datasource/github-releases/__snapshots__/index.spec.ts.snap b/lib/datasource/github-releases/__snapshots__/index.spec.ts.snap
index 3ec77f6047..28c3887d35 100644
--- a/lib/datasource/github-releases/__snapshots__/index.spec.ts.snap
+++ b/lib/datasource/github-releases/__snapshots__/index.spec.ts.snap
@@ -27,3 +27,19 @@ Object {
   "sourceUrl": "https://github.com/some/dep",
 }
 `;
+
+exports[`datasource/github-releases getReleases returns releases 2`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token some-token",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/some/dep/releases?per_page=100",
+  },
+]
+`;
diff --git a/lib/datasource/github-releases/index.spec.ts b/lib/datasource/github-releases/index.spec.ts
index 7044733aaa..5e4da876cc 100644
--- a/lib/datasource/github-releases/index.spec.ts
+++ b/lib/datasource/github-releases/index.spec.ts
@@ -1,26 +1,41 @@
-import { api } from '../../platform/github/gh-got-wrapper';
+import * as httpMock from '../../../test/httpMock';
 import * as globalCache from '../../util/cache/global';
-
+import * as runCache from '../../util/cache/run';
+import * as _hostRules from '../../util/host-rules';
 import * as github from '.';
 
-jest.mock('../../platform/github/gh-got-wrapper');
-jest.mock('../../util/got');
 jest.mock('../../util/host-rules');
+const hostRules: any = _hostRules;
 
-const ghGot: any = api.get;
+const githubApiHost = 'https://api.github.com';
 
 describe('datasource/github-releases', () => {
-  beforeEach(() => globalCache.rmAll());
+  beforeEach(async () => {
+    await globalCache.rmAll();
+    hostRules.hosts = jest.fn(() => []);
+    hostRules.find.mockReturnValue({
+      token: 'some-token',
+    });
+    httpMock.setup();
+  });
+
+  afterEach(() => {
+    httpMock.reset();
+    runCache.clear();
+  });
+
   describe('getReleases', () => {
-    beforeAll(() => globalCache.rmAll());
     it('returns releases', async () => {
-      const body = [
-        { tag_name: 'a', published_at: '2020-03-09T13:00:00Z' },
-        { tag_name: 'v', published_at: '2020-03-09T12:00:00Z' },
-        { tag_name: '1.0.0', published_at: '2020-03-09T11:00:00Z' },
-        { tag_name: 'v1.1.0', published_at: '2020-03-09T10:00:00Z' },
-      ];
-      ghGot.mockReturnValueOnce({ headers: {}, body });
+      httpMock
+        .scope(githubApiHost)
+        .get('/repos/some/dep/releases?per_page=100')
+        .reply(200, [
+          { tag_name: 'a', published_at: '2020-03-09T13:00:00Z' },
+          { tag_name: 'v', published_at: '2020-03-09T12:00:00Z' },
+          { tag_name: '1.0.0', published_at: '2020-03-09T11:00:00Z' },
+          { tag_name: 'v1.1.0', published_at: '2020-03-09T10:00:00Z' },
+        ]);
+
       const res = await github.getReleases({
         lookupName: 'some/dep',
       });
@@ -29,6 +44,7 @@ describe('datasource/github-releases', () => {
       expect(
         res.releases.find((release) => release.version === 'v1.1.0')
       ).toBeDefined();
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
   });
 });
diff --git a/lib/datasource/github-releases/index.ts b/lib/datasource/github-releases/index.ts
index aca91e305e..8b4c2c89dc 100644
--- a/lib/datasource/github-releases/index.ts
+++ b/lib/datasource/github-releases/index.ts
@@ -1,14 +1,14 @@
 import { logger } from '../../logger';
-import { api } from '../../platform/github/gh-got-wrapper';
 import * as globalCache from '../../util/cache/global';
+import { GithubHttp } from '../../util/http/github';
 import { GetReleasesConfig, ReleaseResult } from '../common';
 
-const { get: ghGot } = api;
-
 export const id = 'github-releases';
 
 const cacheNamespace = 'datasource-github-releases';
 
+const http = new GithubHttp();
+
 type GithubRelease = {
   tag_name: string;
   published_at: string;
@@ -38,7 +38,7 @@ export async function getReleases({
   }
   try {
     const url = `https://api.github.com/repos/${repo}/releases?per_page=100`;
-    const res = await ghGot<GithubRelease[]>(url, {
+    const res = await http.getJson<GithubRelease[]>(url, {
       paginate: true,
     });
     githubReleases = res.body;
diff --git a/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap b/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap
index 527501df58..14cf30b51d 100644
--- a/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap
+++ b/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap
@@ -1,5 +1,112 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`datasource/github-tags getDigest returns commit digest 1`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token some-token",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/some/dep/git/refs/tags/v1.2.0",
+  },
+]
+`;
+
+exports[`datasource/github-tags getDigest returns digest 1`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token some-token",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/some/dep/commits?per_page=1",
+  },
+]
+`;
+
+exports[`datasource/github-tags getDigest returns null for missed tagged digest 1`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token some-token",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/some/dep/git/refs/tags/v1.2.0",
+  },
+]
+`;
+
+exports[`datasource/github-tags getDigest returns null if no token 1`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token some-token",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/some/dep/commits?per_page=1",
+  },
+]
+`;
+
+exports[`datasource/github-tags getDigest returns tagged commit digest 1`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token some-token",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/some/dep/git/refs/tags/v1.2.0",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token some-token",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/some-url",
+  },
+]
+`;
+
+exports[`datasource/github-tags getDigest warns if unknown ref 1`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token some-token",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/some/dep/git/refs/tags/v1.2.0",
+  },
+]
+`;
+
 exports[`datasource/github-tags getReleases returns tags 1`] = `
 Object {
   "releases": Array [
@@ -15,3 +122,19 @@ Object {
   "sourceUrl": "https://github.com/some/dep2",
 }
 `;
+
+exports[`datasource/github-tags getReleases returns tags 2`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token some-token",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/some/dep2/tags?per_page=100",
+  },
+]
+`;
diff --git a/lib/datasource/github-tags/index.spec.ts b/lib/datasource/github-tags/index.spec.ts
index 8fb288b979..c47b0526f2 100644
--- a/lib/datasource/github-tags/index.spec.ts
+++ b/lib/datasource/github-tags/index.spec.ts
@@ -1,75 +1,111 @@
-import { api } from '../../platform/github/gh-got-wrapper';
+import * as httpMock from '../../../test/httpMock';
 import * as globalCache from '../../util/cache/global';
 import * as runCache from '../../util/cache/run';
 import * as _hostRules from '../../util/host-rules';
 import * as github from '.';
 
-jest.mock('../../platform/github/gh-got-wrapper');
-jest.mock('../../util/got');
 jest.mock('../../util/host-rules');
-
-const ghGot: any = api.get;
 const hostRules: any = _hostRules;
 
+const githubApiHost = 'https://api.github.com';
+
 describe('datasource/github-tags', () => {
-  beforeEach(() => globalCache.rmAll());
+  beforeEach(async () => {
+    httpMock.setup();
+    await globalCache.rmAll();
+  });
+
+  afterEach(() => {
+    runCache.clear();
+    httpMock.reset();
+  });
+
   describe('getDigest', () => {
+    const lookupName = 'some/dep';
+    const tag = 'v1.2.0';
+
     beforeEach(() => {
       jest.resetAllMocks();
       hostRules.hosts = jest.fn(() => []);
-      runCache.clear();
+      hostRules.find.mockReturnValue({
+        token: 'some-token',
+      });
       return globalCache.rmAll();
     });
+
     it('returns null if no token', async () => {
-      ghGot.mockReturnValueOnce({ body: [] });
-      const res = await github.getDigest({ lookupName: 'some/dep' }, null);
+      httpMock
+        .scope(githubApiHost)
+        .get(`/repos/${lookupName}/commits?per_page=1`)
+        .reply(200, []);
+      const res = await github.getDigest({ lookupName }, null);
       expect(res).toBeNull();
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('returns digest', async () => {
-      ghGot.mockReturnValueOnce({ body: [{ sha: 'abcdef' }] });
-      const res = await github.getDigest({ lookupName: 'some/dep' }, null);
+      httpMock
+        .scope(githubApiHost)
+        .get(`/repos/${lookupName}/commits?per_page=1`)
+        .reply(200, [{ sha: 'abcdef' }]);
+      const res = await github.getDigest({ lookupName }, null);
       expect(res).toBe('abcdef');
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('returns commit digest', async () => {
-      ghGot.mockReturnValueOnce({
-        body: { object: { type: 'commit', sha: 'ddd111' } },
-      });
-      const res = await github.getDigest({ lookupName: 'some/dep' }, 'v1.2.0');
+      httpMock
+        .scope(githubApiHost)
+        .get(`/repos/${lookupName}/git/refs/tags/${tag}`)
+        .reply(200, { object: { type: 'commit', sha: 'ddd111' } });
+      const res = await github.getDigest({ lookupName }, tag);
       expect(res).toBe('ddd111');
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('returns tagged commit digest', async () => {
-      ghGot.mockReturnValueOnce({
-        body: { object: { type: 'tag', url: 'some-url' } },
-      });
-      ghGot.mockReturnValueOnce({
-        body: { object: { type: 'commit', sha: 'ddd111' } },
-      });
-      const res = await github.getDigest({ lookupName: 'some/dep' }, 'v1.2.0');
+      httpMock
+        .scope(githubApiHost)
+        .get(`/repos/${lookupName}/git/refs/tags/${tag}`)
+        .reply(200, {
+          object: { type: 'tag', url: `${githubApiHost}/some-url` },
+        })
+        .get('/some-url')
+        .reply(200, { object: { type: 'commit', sha: 'ddd111' } });
+      const res = await github.getDigest({ lookupName }, tag);
       expect(res).toBe('ddd111');
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('warns if unknown ref', async () => {
-      ghGot.mockReturnValueOnce({
-        body: { object: { sha: 'ddd111' } },
-      });
-      const res = await github.getDigest({ lookupName: 'some/dep' }, 'v1.2.0');
+      httpMock
+        .scope(githubApiHost)
+        .get(`/repos/${lookupName}/git/refs/tags/${tag}`)
+        .reply(200, { object: { sha: 'ddd111' } });
+      const res = await github.getDigest({ lookupName }, tag);
       expect(res).toBeNull();
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('returns null for missed tagged digest', async () => {
-      ghGot.mockReturnValueOnce({});
+      httpMock
+        .scope(githubApiHost)
+        .get(`/repos/${lookupName}/git/refs/tags/${tag}`)
+        .reply(200, {});
       const res = await github.getDigest({ lookupName: 'some/dep' }, 'v1.2.0');
       expect(res).toBeNull();
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
   });
   describe('getReleases', () => {
+    const lookupName = 'some/dep2';
+
     beforeAll(() => globalCache.rmAll());
     it('returns tags', async () => {
       const body = [{ name: 'v1.0.0' }, { name: 'v1.1.0' }];
-      ghGot.mockReturnValueOnce({ headers: {}, body });
-      const res = await github.getReleases({
-        lookupName: 'some/dep2',
-      });
+      httpMock
+        .scope(githubApiHost)
+        .get(`/repos/${lookupName}/tags?per_page=100`)
+        .reply(200, body);
+      const res = await github.getReleases({ lookupName });
       expect(res).toMatchSnapshot();
       expect(res.releases).toHaveLength(2);
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
   });
 });
diff --git a/lib/datasource/github-tags/index.ts b/lib/datasource/github-tags/index.ts
index 56054f1aca..fea2bae705 100644
--- a/lib/datasource/github-tags/index.ts
+++ b/lib/datasource/github-tags/index.ts
@@ -1,17 +1,25 @@
 import { logger } from '../../logger';
-import { api } from '../../platform/github/gh-got-wrapper';
 import * as globalCache from '../../util/cache/global';
+import { GithubHttp } from '../../util/http/github';
 import { DigestConfig, GetReleasesConfig, ReleaseResult } from '../common';
 
-const { get: ghGot } = api;
-
 export const id = 'github-tags';
 
+const http = new GithubHttp();
+
 const cacheNamespace = 'datasource-github-tags';
 function getCacheKey(repo: string, type: string): string {
   return `${repo}:${type}`;
 }
 
+interface TagResponse {
+  object: {
+    type: string;
+    url: string;
+    sha: string;
+  };
+}
+
 async function getTagCommit(
   githubRepo: string,
   tag: string
@@ -27,11 +35,11 @@ async function getTagCommit(
   let digest: string;
   try {
     const url = `https://api.github.com/repos/${githubRepo}/git/refs/tags/${tag}`;
-    const res = (await ghGot(url)).body.object;
+    const res = (await http.getJson<TagResponse>(url)).body.object;
     if (res.type === 'commit') {
       digest = res.sha;
     } else if (res.type === 'tag') {
-      digest = (await ghGot(res.url)).body.object.sha;
+      digest = (await http.getJson<TagResponse>(res.url)).body.object.sha;
     } else {
       logger.warn({ res }, 'Unknown git tag refs type');
     }
@@ -79,7 +87,8 @@ export async function getDigest(
   let digest: string;
   try {
     const url = `https://api.github.com/repos/${githubRepo}/commits?per_page=1`;
-    digest = (await ghGot(url)).body[0].sha;
+    const res = await http.getJson<{ sha: string }[]>(url);
+    digest = res.body[0].sha;
   } catch (err) {
     logger.debug(
       { githubRepo, err },
@@ -129,7 +138,7 @@ export async function getReleases({
     }[];
 
     versions = (
-      await ghGot<GitHubTag>(url, {
+      await http.getJson<GitHubTag>(url, {
         paginate: true,
       })
     ).body.map((o) => o.name);
diff --git a/lib/datasource/pod/__snapshots__/index.spec.ts.snap b/lib/datasource/pod/__snapshots__/index.spec.ts.snap
new file mode 100644
index 0000000000..be773d988f
--- /dev/null
+++ b/lib/datasource/pod/__snapshots__/index.spec.ts.snap
@@ -0,0 +1,107 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`datasource/cocoapods getReleases processes real data from CDN 1`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept-encoding": "gzip, deflate",
+      "host": "cdn.cocoapods.org",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://cdn.cocoapods.org/all_pods_versions_a_c_b.txt",
+  },
+]
+`;
+
+exports[`datasource/cocoapods getReleases processes real data from Github 1`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/Artsy/Specs/contents/Specs/foo",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/Artsy/Specs/contents/Specs/a/c/b/foo",
+  },
+]
+`;
+
+exports[`datasource/cocoapods getReleases returns null for 401 1`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept-encoding": "gzip, deflate",
+      "host": "cdn.cocoapods.org",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://cdn.cocoapods.org/all_pods_versions_a_c_b.txt",
+  },
+]
+`;
+
+exports[`datasource/cocoapods getReleases returns null for 404 1`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/foo/bar/contents/Specs/foo",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/foo/bar/contents/Specs/a/c/b/foo",
+  },
+]
+`;
+
+exports[`datasource/cocoapods getReleases returns null for unknown error 1`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept-encoding": "gzip, deflate",
+      "host": "cdn.cocoapods.org",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://cdn.cocoapods.org/all_pods_versions_a_c_b.txt",
+  },
+]
+`;
+
+exports[`datasource/cocoapods getReleases throws for 429 1`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept-encoding": "gzip, deflate",
+      "host": "cdn.cocoapods.org",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://cdn.cocoapods.org/all_pods_versions_a_c_b.txt",
+  },
+]
+`;
diff --git a/lib/datasource/pod/index.spec.ts b/lib/datasource/pod/index.spec.ts
index 49c6500254..5161bbcdeb 100644
--- a/lib/datasource/pod/index.spec.ts
+++ b/lib/datasource/pod/index.spec.ts
@@ -1,16 +1,10 @@
 import { getPkgReleases } from '..';
-import { mocked } from '../../../test/util';
-import { GotResponse } from '../../platform';
-import { api as _api } from '../../platform/github/gh-got-wrapper';
+import * as httpMock from '../../../test/httpMock';
 import * as globalCache from '../../util/cache/global';
 import * as runCache from '../../util/cache/run';
 import * as rubyVersioning from '../../versioning/ruby';
 import * as pod from '.';
 
-const api = mocked(_api);
-
-jest.mock('../../platform/github/gh-got-wrapper');
-
 const config = {
   versioning: rubyVersioning.id,
   datasource: pod.id,
@@ -18,16 +12,23 @@ const config = {
   registryUrls: [],
 };
 
+const githubApiHost = 'https://api.github.com';
+const cocoapodsHost = 'https://cdn.cocoapods.org';
+
 describe('datasource/cocoapods', () => {
   describe('getReleases', () => {
     beforeEach(() => {
       jest.resetAllMocks();
-      runCache.clear();
+      httpMock.setup();
       return globalCache.rmAll();
     });
 
+    afterEach(() => {
+      httpMock.reset();
+      runCache.clear();
+    });
+
     it('returns null for invalid inputs', async () => {
-      api.get.mockResolvedValueOnce(null);
       expect(
         await getPkgReleases({
           datasource: pod.id,
@@ -37,77 +38,53 @@ describe('datasource/cocoapods', () => {
       ).toBeNull();
     });
     it('returns null for empty result', async () => {
-      api.get.mockResolvedValueOnce(null);
-      expect(await getPkgReleases(config)).toBeNull();
-    });
-    it('returns null for missing fields', async () => {
-      api.get.mockResolvedValueOnce({} as GotResponse);
-      expect(await getPkgReleases(config)).toBeNull();
-
-      api.get.mockResolvedValueOnce({ body: '' } as GotResponse);
       expect(await getPkgReleases(config)).toBeNull();
     });
     it('returns null for 404', async () => {
-      api.get.mockImplementation(() =>
-        Promise.reject({
-          statusCode: 404,
-        })
-      );
-      expect(
-        await getPkgReleases({
-          ...config,
-          registryUrls: [
-            ...config.registryUrls,
-            'invalid',
-            'https://github.com/foo/bar',
-          ],
-        })
-      ).toBeNull();
+      httpMock
+        .scope(githubApiHost)
+        .get('/repos/foo/bar/contents/Specs/foo')
+        .reply(404)
+        .get('/repos/foo/bar/contents/Specs/a/c/b/foo')
+        .reply(404);
+      const res = await getPkgReleases({
+        ...config,
+        registryUrls: [...config.registryUrls, 'https://github.com/foo/bar'],
+      });
+      expect(res).toBeNull();
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('returns null for 401', async () => {
-      api.get.mockImplementationOnce(() =>
-        Promise.reject({
-          statusCode: 401,
-        })
-      );
+      httpMock
+        .scope(cocoapodsHost)
+        .get('/all_pods_versions_a_c_b.txt')
+        .reply(401);
       expect(await getPkgReleases(config)).toBeNull();
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('throws for 429', async () => {
-      api.get.mockImplementationOnce(() =>
-        Promise.reject({
-          statusCode: 429,
-        })
-      );
-      await expect(
-        getPkgReleases({
-          ...config,
-          registryUrls: ['https://cdn.cocoapods.org'],
-        })
-      ).rejects.toThrowError('registry-failure');
-    });
-    it('throws for 5xx', async () => {
-      api.get.mockImplementationOnce(() =>
-        Promise.reject({
-          statusCode: 502,
-        })
+      httpMock
+        .scope(cocoapodsHost)
+        .get('/all_pods_versions_a_c_b.txt')
+        .reply(429);
+      await expect(getPkgReleases(config)).rejects.toThrowError(
+        'registry-failure'
       );
-      await expect(
-        getPkgReleases({
-          ...config,
-          registryUrls: ['https://cdn.cocoapods.org'],
-        })
-      ).rejects.toThrowError('registry-failure');
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('returns null for unknown error', async () => {
-      api.get.mockImplementationOnce(() => {
-        throw new Error();
-      });
+      httpMock
+        .scope(cocoapodsHost)
+        .get('/all_pods_versions_a_c_b.txt')
+        .replyWithError('foobar');
       expect(await getPkgReleases(config)).toBeNull();
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('processes real data from CDN', async () => {
-      api.get.mockResolvedValueOnce({
-        body: 'foo/1.2.3',
-      } as GotResponse);
+      httpMock
+        .scope(cocoapodsHost)
+        .get('/all_pods_versions_a_c_b.txt')
+        .reply(200, 'foo/1.2.3');
       expect(
         await getPkgReleases({
           ...config,
@@ -120,23 +97,27 @@ describe('datasource/cocoapods', () => {
           },
         ],
       });
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('processes real data from Github', async () => {
-      api.get.mockResolvedValueOnce({
-        body: [{ name: '1.2.3' }],
-      } as GotResponse);
-      expect(
-        await getPkgReleases({
-          ...config,
-          registryUrls: ['https://github.com/Artsy/Specs'],
-        })
-      ).toEqual({
+      httpMock
+        .scope(githubApiHost)
+        .get('/repos/Artsy/Specs/contents/Specs/foo')
+        .reply(404)
+        .get('/repos/Artsy/Specs/contents/Specs/a/c/b/foo')
+        .reply(200, [{ name: '1.2.3' }]);
+      const res = await getPkgReleases({
+        ...config,
+        registryUrls: ['https://github.com/Artsy/Specs'],
+      });
+      expect(res).toEqual({
         releases: [
           {
             version: '1.2.3',
           },
         ],
       });
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
   });
 });
diff --git a/lib/datasource/pod/index.ts b/lib/datasource/pod/index.ts
index e21cf5c768..ce957dad14 100644
--- a/lib/datasource/pod/index.ts
+++ b/lib/datasource/pod/index.ts
@@ -1,7 +1,8 @@
 import crypto from 'crypto';
 import { logger } from '../../logger';
-import { api } from '../../platform/github/gh-got-wrapper';
 import * as globalCache from '../../util/cache/global';
+import { Http } from '../../util/http';
+import { GithubHttp } from '../../util/http/github';
 import { GetReleasesConfig, ReleaseResult } from '../common';
 
 export const id = 'pod';
@@ -11,6 +12,9 @@ export const defaultRegistryUrls = ['https://cdn.cocoapods.org'];
 const cacheNamespace = `datasource-${id}`;
 const cacheMinutes = 30;
 
+const githubHttp = new GithubHttp();
+const http = new Http(id);
+
 function shardParts(lookupName: string): string[] {
   return crypto
     .createHash('md5')
@@ -31,34 +35,53 @@ function releasesGithubUrl(
   return `${prefix}/${account}/${repo}/contents/Specs/${suffix}`;
 }
 
-async function makeRequest<T = unknown>(
+function handleError(lookupName: string, err: Error): void {
+  const errorData = { lookupName, err };
+
+  if (
+    err.statusCode === 429 ||
+    (err.statusCode >= 500 && err.statusCode < 600)
+  ) {
+    logger.warn({ lookupName, err }, `CocoaPods registry failure`);
+    throw new Error('registry-failure');
+  }
+
+  if (err.statusCode === 401) {
+    logger.debug(errorData, 'Authorization error');
+  } else if (err.statusCode === 404) {
+    logger.debug(errorData, 'Package lookup error');
+  } else {
+    logger.warn(errorData, 'CocoaPods lookup failure: Unknown error');
+  }
+}
+
+async function requestCDN(
   url: string,
-  lookupName: string,
-  json = true
-): Promise<T | null> {
+  lookupName: string
+): Promise<string | null> {
   try {
-    const resp = await api.get(url, { json });
+    const resp = await http.get(url);
     if (resp && resp.body) {
       return resp.body;
     }
   } catch (err) {
-    const errorData = { lookupName, err };
-
-    if (
-      err.statusCode === 429 ||
-      (err.statusCode >= 500 && err.statusCode < 600)
-    ) {
-      logger.warn({ lookupName, err }, `CocoaPods registry failure`);
-      throw new Error('registry-failure');
-    }
+    handleError(lookupName, err);
+  }
 
-    if (err.statusCode === 401) {
-      logger.debug(errorData, 'Authorization error');
-    } else if (err.statusCode === 404) {
-      logger.debug(errorData, 'Package lookup error');
-    } else {
-      logger.warn(errorData, 'CocoaPods lookup failure: Unknown error');
+  return null;
+}
+
+async function requestGithub<T = unknown>(
+  url: string,
+  lookupName: string
+): Promise<T | null> {
+  try {
+    const resp = await githubHttp.getJson<T>(url);
+    if (resp && resp.body) {
+      return resp.body;
     }
+  } catch (err) {
+    handleError(lookupName, err);
   }
 
   return null;
@@ -75,7 +98,7 @@ async function getReleasesFromGithub(
   const { account, repo } = (match && match.groups) || {};
   const opts = { account, repo, useShard };
   const url = releasesGithubUrl(lookupName, opts);
-  const resp = await makeRequest<{ name: string }[]>(url, lookupName);
+  const resp = await requestGithub<{ name: string }[]>(url, lookupName);
   if (resp) {
     const releases = resp.map(({ name }) => ({ version: name }));
     return { releases };
@@ -98,7 +121,7 @@ async function getReleasesFromCDN(
   registryUrl: string
 ): Promise<ReleaseResult | null> {
   const url = releasesCDNUrl(lookupName, registryUrl);
-  const resp = await makeRequest<string>(url, lookupName, false);
+  const resp = await requestCDN(url, lookupName);
   if (resp) {
     const lines = resp.split('\n');
     for (let idx = 0; idx < lines.length; idx += 1) {
diff --git a/lib/platform/github/__snapshots__/index.spec.ts.snap b/lib/platform/github/__snapshots__/index.spec.ts.snap
index 9b6a588d17..9fc7c8a76b 100644
--- a/lib/platform/github/__snapshots__/index.spec.ts.snap
+++ b/lib/platform/github/__snapshots__/index.spec.ts.snap
@@ -271,50 +271,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
-      "host": "api.github.com",
-      "user-agent": "https://github.com/renovatebot/renovate",
-    },
-    "method": "POST",
-    "url": "https://api.github.com/graphql",
-  },
-  Object {
-    "graphql": Object {
-      "query": Object {
-        "repository": Object {
-          "__args": Object {
-            "name": "repo",
-            "owner": "some",
-          },
-          "pullRequests": Object {
-            "__args": Object {
-              "first": "25",
-            },
-            "nodes": Object {
-              "comments": Object {
-                "__args": Object {
-                  "last": "100",
-                },
-                "nodes": Object {
-                  "body": null,
-                  "databaseId": null,
-                },
-              },
-              "headRefName": null,
-              "number": null,
-              "state": null,
-              "title": null,
-            },
-          },
-        },
-      },
-    },
-    "headers": Object {
-      "accept-encoding": "gzip, deflate",
-      "authorization": "token abc123",
-      "content-length": 512,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -394,50 +355,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
-      "host": "api.github.com",
-      "user-agent": "https://github.com/renovatebot/renovate",
-    },
-    "method": "POST",
-    "url": "https://api.github.com/graphql",
-  },
-  Object {
-    "graphql": Object {
-      "query": Object {
-        "repository": Object {
-          "__args": Object {
-            "name": "repo",
-            "owner": "some",
-          },
-          "pullRequests": Object {
-            "__args": Object {
-              "first": "25",
-            },
-            "nodes": Object {
-              "comments": Object {
-                "__args": Object {
-                  "last": "100",
-                },
-                "nodes": Object {
-                  "body": null,
-                  "databaseId": null,
-                },
-              },
-              "headRefName": null,
-              "number": null,
-              "state": null,
-              "title": null,
-            },
-          },
-        },
-      },
-    },
-    "headers": Object {
-      "accept-encoding": "gzip, deflate",
-      "authorization": "token abc123",
-      "content-length": 512,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -517,50 +439,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
-      "host": "api.github.com",
-      "user-agent": "https://github.com/renovatebot/renovate",
-    },
-    "method": "POST",
-    "url": "https://api.github.com/graphql",
-  },
-  Object {
-    "graphql": Object {
-      "query": Object {
-        "repository": Object {
-          "__args": Object {
-            "name": "repo",
-            "owner": "some",
-          },
-          "pullRequests": Object {
-            "__args": Object {
-              "first": "25",
-            },
-            "nodes": Object {
-              "comments": Object {
-                "__args": Object {
-                  "last": "100",
-                },
-                "nodes": Object {
-                  "body": null,
-                  "databaseId": null,
-                },
-              },
-              "headRefName": null,
-              "number": null,
-              "state": null,
-              "title": null,
-            },
-          },
-        },
-      },
-    },
-    "headers": Object {
-      "accept-encoding": "gzip, deflate",
-      "authorization": "token abc123",
-      "content-length": 512,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -629,50 +512,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
-      "host": "api.github.com",
-      "user-agent": "https://github.com/renovatebot/renovate",
-    },
-    "method": "POST",
-    "url": "https://api.github.com/graphql",
-  },
-  Object {
-    "graphql": Object {
-      "query": Object {
-        "repository": Object {
-          "__args": Object {
-            "name": "repo",
-            "owner": "some",
-          },
-          "pullRequests": Object {
-            "__args": Object {
-              "first": "25",
-            },
-            "nodes": Object {
-              "comments": Object {
-                "__args": Object {
-                  "last": "100",
-                },
-                "nodes": Object {
-                  "body": null,
-                  "databaseId": null,
-                },
-              },
-              "headRefName": null,
-              "number": null,
-              "state": null,
-              "title": null,
-            },
-          },
-        },
-      },
-    },
-    "headers": Object {
-      "accept-encoding": "gzip, deflate",
-      "authorization": "token abc123",
-      "content-length": 512,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -738,50 +582,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
-      "host": "api.github.com",
-      "user-agent": "https://github.com/renovatebot/renovate",
-    },
-    "method": "POST",
-    "url": "https://api.github.com/graphql",
-  },
-  Object {
-    "graphql": Object {
-      "query": Object {
-        "repository": Object {
-          "__args": Object {
-            "name": "repo",
-            "owner": "some",
-          },
-          "pullRequests": Object {
-            "__args": Object {
-              "first": "25",
-            },
-            "nodes": Object {
-              "comments": Object {
-                "__args": Object {
-                  "last": "100",
-                },
-                "nodes": Object {
-                  "body": null,
-                  "databaseId": null,
-                },
-              },
-              "headRefName": null,
-              "number": null,
-              "state": null,
-              "title": null,
-            },
-          },
-        },
-      },
-    },
-    "headers": Object {
-      "accept-encoding": "gzip, deflate",
-      "authorization": "token abc123",
-      "content-length": 512,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -847,50 +652,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
-      "host": "api.github.com",
-      "user-agent": "https://github.com/renovatebot/renovate",
-    },
-    "method": "POST",
-    "url": "https://api.github.com/graphql",
-  },
-  Object {
-    "graphql": Object {
-      "query": Object {
-        "repository": Object {
-          "__args": Object {
-            "name": "repo",
-            "owner": "some",
-          },
-          "pullRequests": Object {
-            "__args": Object {
-              "first": "25",
-            },
-            "nodes": Object {
-              "comments": Object {
-                "__args": Object {
-                  "last": "100",
-                },
-                "nodes": Object {
-                  "body": null,
-                  "databaseId": null,
-                },
-              },
-              "headRefName": null,
-              "number": null,
-              "state": null,
-              "title": null,
-            },
-          },
-        },
-      },
-    },
-    "headers": Object {
-      "accept-encoding": "gzip, deflate",
-      "authorization": "token abc123",
-      "content-length": 512,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -967,50 +733,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
-      "host": "api.github.com",
-      "user-agent": "https://github.com/renovatebot/renovate",
-    },
-    "method": "POST",
-    "url": "https://api.github.com/graphql",
-  },
-  Object {
-    "graphql": Object {
-      "query": Object {
-        "repository": Object {
-          "__args": Object {
-            "name": "repo",
-            "owner": "some",
-          },
-          "pullRequests": Object {
-            "__args": Object {
-              "first": "25",
-            },
-            "nodes": Object {
-              "comments": Object {
-                "__args": Object {
-                  "last": "100",
-                },
-                "nodes": Object {
-                  "body": null,
-                  "databaseId": null,
-                },
-              },
-              "headRefName": null,
-              "number": null,
-              "state": null,
-              "title": null,
-            },
-          },
-        },
-      },
-    },
-    "headers": Object {
-      "accept-encoding": "gzip, deflate",
-      "authorization": "token abc123",
-      "content-length": 512,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -1045,12 +772,36 @@ Array [
 exports[`platform/github ensureIssue() closes others if ensuring only once 1`] = `
 Array [
   Object {
-    "body": "{\\"query\\":\\"\\\\n    query {\\\\n      repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n        issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n          pageInfo {\\\\n            endCursor\\\\n            hasNextPage\\\\n          }\\\\n          nodes {\\\\n            number\\\\n            state\\\\n            title\\\\n            body\\\\n          }\\\\n        }\\\\n      }\\\\n    }\\\\n  \\"}",
+    "graphql": Object {
+      "query": Object {
+        "repository": Object {
+          "__args": Object {
+            "name": "undefined",
+            "owner": "undefined",
+          },
+          "issues": Object {
+            "__args": Object {
+              "first": "100",
+            },
+            "nodes": Object {
+              "body": null,
+              "number": null,
+              "state": null,
+              "title": null,
+            },
+            "pageInfo": Object {
+              "endCursor": null,
+              "hasNextPage": null,
+            },
+          },
+        },
+      },
+    },
     "headers": Object {
       "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 423,
+      "content-length": 425,
       "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
@@ -1075,12 +826,36 @@ Array [
 exports[`platform/github ensureIssue() creates issue 1`] = `
 Array [
   Object {
-    "body": "{\\"query\\":\\"\\\\n    query {\\\\n      repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n        issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n          pageInfo {\\\\n            endCursor\\\\n            hasNextPage\\\\n          }\\\\n          nodes {\\\\n            number\\\\n            state\\\\n            title\\\\n            body\\\\n          }\\\\n        }\\\\n      }\\\\n    }\\\\n  \\"}",
+    "graphql": Object {
+      "query": Object {
+        "repository": Object {
+          "__args": Object {
+            "name": "undefined",
+            "owner": "undefined",
+          },
+          "issues": Object {
+            "__args": Object {
+              "first": "100",
+            },
+            "nodes": Object {
+              "body": null,
+              "number": null,
+              "state": null,
+              "title": null,
+            },
+            "pageInfo": Object {
+              "endCursor": null,
+              "hasNextPage": null,
+            },
+          },
+        },
+      },
+    },
     "headers": Object {
       "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 423,
+      "content-length": 425,
       "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
@@ -1108,12 +883,36 @@ Array [
 exports[`platform/github ensureIssue() creates issue if not ensuring only once 1`] = `
 Array [
   Object {
-    "body": "{\\"query\\":\\"\\\\n    query {\\\\n      repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n        issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n          pageInfo {\\\\n            endCursor\\\\n            hasNextPage\\\\n          }\\\\n          nodes {\\\\n            number\\\\n            state\\\\n            title\\\\n            body\\\\n          }\\\\n        }\\\\n      }\\\\n    }\\\\n  \\"}",
+    "graphql": Object {
+      "query": Object {
+        "repository": Object {
+          "__args": Object {
+            "name": "undefined",
+            "owner": "undefined",
+          },
+          "issues": Object {
+            "__args": Object {
+              "first": "100",
+            },
+            "nodes": Object {
+              "body": null,
+              "number": null,
+              "state": null,
+              "title": null,
+            },
+            "pageInfo": Object {
+              "endCursor": null,
+              "hasNextPage": null,
+            },
+          },
+        },
+      },
+    },
     "headers": Object {
       "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 423,
+      "content-length": 425,
       "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
@@ -1138,12 +937,36 @@ Array [
 exports[`platform/github ensureIssue() creates issue if reopen flag false and issue is not open 1`] = `
 Array [
   Object {
-    "body": "{\\"query\\":\\"\\\\n    query {\\\\n      repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n        issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n          pageInfo {\\\\n            endCursor\\\\n            hasNextPage\\\\n          }\\\\n          nodes {\\\\n            number\\\\n            state\\\\n            title\\\\n            body\\\\n          }\\\\n        }\\\\n      }\\\\n    }\\\\n  \\"}",
+    "graphql": Object {
+      "query": Object {
+        "repository": Object {
+          "__args": Object {
+            "name": "undefined",
+            "owner": "undefined",
+          },
+          "issues": Object {
+            "__args": Object {
+              "first": "100",
+            },
+            "nodes": Object {
+              "body": null,
+              "number": null,
+              "state": null,
+              "title": null,
+            },
+            "pageInfo": Object {
+              "endCursor": null,
+              "hasNextPage": null,
+            },
+          },
+        },
+      },
+    },
     "headers": Object {
       "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 423,
+      "content-length": 425,
       "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
@@ -1182,12 +1005,36 @@ Array [
 exports[`platform/github ensureIssue() deletes if duplicate 1`] = `
 Array [
   Object {
-    "body": "{\\"query\\":\\"\\\\n    query {\\\\n      repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n        issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n          pageInfo {\\\\n            endCursor\\\\n            hasNextPage\\\\n          }\\\\n          nodes {\\\\n            number\\\\n            state\\\\n            title\\\\n            body\\\\n          }\\\\n        }\\\\n      }\\\\n    }\\\\n  \\"}",
+    "graphql": Object {
+      "query": Object {
+        "repository": Object {
+          "__args": Object {
+            "name": "undefined",
+            "owner": "undefined",
+          },
+          "issues": Object {
+            "__args": Object {
+              "first": "100",
+            },
+            "nodes": Object {
+              "body": null,
+              "number": null,
+              "state": null,
+              "title": null,
+            },
+            "pageInfo": Object {
+              "endCursor": null,
+              "hasNextPage": null,
+            },
+          },
+        },
+      },
+    },
     "headers": Object {
       "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 423,
+      "content-length": 425,
       "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
@@ -1226,12 +1073,36 @@ Array [
 exports[`platform/github ensureIssue() does not create issue if ensuring only once 1`] = `
 Array [
   Object {
-    "body": "{\\"query\\":\\"\\\\n    query {\\\\n      repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n        issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n          pageInfo {\\\\n            endCursor\\\\n            hasNextPage\\\\n          }\\\\n          nodes {\\\\n            number\\\\n            state\\\\n            title\\\\n            body\\\\n          }\\\\n        }\\\\n      }\\\\n    }\\\\n  \\"}",
+    "graphql": Object {
+      "query": Object {
+        "repository": Object {
+          "__args": Object {
+            "name": "undefined",
+            "owner": "undefined",
+          },
+          "issues": Object {
+            "__args": Object {
+              "first": "100",
+            },
+            "nodes": Object {
+              "body": null,
+              "number": null,
+              "state": null,
+              "title": null,
+            },
+            "pageInfo": Object {
+              "endCursor": null,
+              "hasNextPage": null,
+            },
+          },
+        },
+      },
+    },
     "headers": Object {
       "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 423,
+      "content-length": 425,
       "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
@@ -1245,12 +1116,36 @@ Array [
 exports[`platform/github ensureIssue() does not create issue if reopen flag false and issue is already open 1`] = `
 Array [
   Object {
-    "body": "{\\"query\\":\\"\\\\n    query {\\\\n      repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n        issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n          pageInfo {\\\\n            endCursor\\\\n            hasNextPage\\\\n          }\\\\n          nodes {\\\\n            number\\\\n            state\\\\n            title\\\\n            body\\\\n          }\\\\n        }\\\\n      }\\\\n    }\\\\n  \\"}",
+    "graphql": Object {
+      "query": Object {
+        "repository": Object {
+          "__args": Object {
+            "name": "undefined",
+            "owner": "undefined",
+          },
+          "issues": Object {
+            "__args": Object {
+              "first": "100",
+            },
+            "nodes": Object {
+              "body": null,
+              "number": null,
+              "state": null,
+              "title": null,
+            },
+            "pageInfo": Object {
+              "endCursor": null,
+              "hasNextPage": null,
+            },
+          },
+        },
+      },
+    },
     "headers": Object {
       "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 423,
+      "content-length": 425,
       "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
@@ -1275,12 +1170,36 @@ Array [
 exports[`platform/github ensureIssue() skips update if unchanged 1`] = `
 Array [
   Object {
-    "body": "{\\"query\\":\\"\\\\n    query {\\\\n      repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n        issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n          pageInfo {\\\\n            endCursor\\\\n            hasNextPage\\\\n          }\\\\n          nodes {\\\\n            number\\\\n            state\\\\n            title\\\\n            body\\\\n          }\\\\n        }\\\\n      }\\\\n    }\\\\n  \\"}",
+    "graphql": Object {
+      "query": Object {
+        "repository": Object {
+          "__args": Object {
+            "name": "undefined",
+            "owner": "undefined",
+          },
+          "issues": Object {
+            "__args": Object {
+              "first": "100",
+            },
+            "nodes": Object {
+              "body": null,
+              "number": null,
+              "state": null,
+              "title": null,
+            },
+            "pageInfo": Object {
+              "endCursor": null,
+              "hasNextPage": null,
+            },
+          },
+        },
+      },
+    },
     "headers": Object {
       "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 423,
+      "content-length": 425,
       "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
@@ -1305,12 +1224,36 @@ Array [
 exports[`platform/github ensureIssue() updates issue 1`] = `
 Array [
   Object {
-    "body": "{\\"query\\":\\"\\\\n    query {\\\\n      repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n        issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n          pageInfo {\\\\n            endCursor\\\\n            hasNextPage\\\\n          }\\\\n          nodes {\\\\n            number\\\\n            state\\\\n            title\\\\n            body\\\\n          }\\\\n        }\\\\n      }\\\\n    }\\\\n  \\"}",
+    "graphql": Object {
+      "query": Object {
+        "repository": Object {
+          "__args": Object {
+            "name": "undefined",
+            "owner": "undefined",
+          },
+          "issues": Object {
+            "__args": Object {
+              "first": "100",
+            },
+            "nodes": Object {
+              "body": null,
+              "number": null,
+              "state": null,
+              "title": null,
+            },
+            "pageInfo": Object {
+              "endCursor": null,
+              "hasNextPage": null,
+            },
+          },
+        },
+      },
+    },
     "headers": Object {
       "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 423,
+      "content-length": 425,
       "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
@@ -1349,12 +1292,36 @@ Array [
 exports[`platform/github ensureIssueClosing() closes issue 1`] = `
 Array [
   Object {
-    "body": "{\\"query\\":\\"\\\\n    query {\\\\n      repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n        issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n          pageInfo {\\\\n            endCursor\\\\n            hasNextPage\\\\n          }\\\\n          nodes {\\\\n            number\\\\n            state\\\\n            title\\\\n            body\\\\n          }\\\\n        }\\\\n      }\\\\n    }\\\\n  \\"}",
+    "graphql": Object {
+      "query": Object {
+        "repository": Object {
+          "__args": Object {
+            "name": "undefined",
+            "owner": "undefined",
+          },
+          "issues": Object {
+            "__args": Object {
+              "first": "100",
+            },
+            "nodes": Object {
+              "body": null,
+              "number": null,
+              "state": null,
+              "title": null,
+            },
+            "pageInfo": Object {
+              "endCursor": null,
+              "hasNextPage": null,
+            },
+          },
+        },
+      },
+    },
     "headers": Object {
       "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 423,
+      "content-length": 425,
       "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
@@ -1382,12 +1349,36 @@ Array [
 exports[`platform/github findIssue() finds issue 1`] = `
 Array [
   Object {
-    "body": "{\\"query\\":\\"\\\\n    query {\\\\n      repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n        issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n          pageInfo {\\\\n            endCursor\\\\n            hasNextPage\\\\n          }\\\\n          nodes {\\\\n            number\\\\n            state\\\\n            title\\\\n            body\\\\n          }\\\\n        }\\\\n      }\\\\n    }\\\\n  \\"}",
+    "graphql": Object {
+      "query": Object {
+        "repository": Object {
+          "__args": Object {
+            "name": "undefined",
+            "owner": "undefined",
+          },
+          "issues": Object {
+            "__args": Object {
+              "first": "100",
+            },
+            "nodes": Object {
+              "body": null,
+              "number": null,
+              "state": null,
+              "title": null,
+            },
+            "pageInfo": Object {
+              "endCursor": null,
+              "hasNextPage": null,
+            },
+          },
+        },
+      },
+    },
     "headers": Object {
       "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 423,
+      "content-length": 425,
       "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
@@ -1412,12 +1403,36 @@ Array [
 exports[`platform/github findIssue() returns null if no issue 1`] = `
 Array [
   Object {
-    "body": "{\\"query\\":\\"\\\\n    query {\\\\n      repository(owner: \\\\\\"undefined\\\\\\", name: \\\\\\"undefined\\\\\\") {\\\\n        issues(first: 100orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: \\\\\\"undefined\\\\\\"}) {\\\\n          pageInfo {\\\\n            endCursor\\\\n            hasNextPage\\\\n          }\\\\n          nodes {\\\\n            number\\\\n            state\\\\n            title\\\\n            body\\\\n          }\\\\n        }\\\\n      }\\\\n    }\\\\n  \\"}",
+    "graphql": Object {
+      "query": Object {
+        "repository": Object {
+          "__args": Object {
+            "name": "undefined",
+            "owner": "undefined",
+          },
+          "issues": Object {
+            "__args": Object {
+              "first": "100",
+            },
+            "nodes": Object {
+              "body": null,
+              "number": null,
+              "state": null,
+              "title": null,
+            },
+            "pageInfo": Object {
+              "endCursor": null,
+              "hasNextPage": null,
+            },
+          },
+        },
+      },
+    },
     "headers": Object {
       "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 423,
+      "content-length": 425,
       "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
@@ -1604,136 +1619,16 @@ Array [
                 },
               },
               "mergeStateStatus": null,
-              "mergeable": null,
-              "number": null,
-              "reviews": Object {
-                "__args": Object {
-                  "first": "1",
-                },
-                "nodes": Object {
-                  "state": null,
-                },
-              },
-              "title": null,
-            },
-          },
-        },
-      },
-    },
-    "headers": Object {
-      "accept": "application/vnd.github.merge-info-preview+json",
-      "accept-encoding": "gzip, deflate",
-      "authorization": "token abc123",
-      "content-length": 1264,
-      "host": "api.github.com",
-      "user-agent": "https://github.com/renovatebot/renovate",
-    },
-    "method": "POST",
-    "url": "https://api.github.com/graphql",
-  },
-  Object {
-    "graphql": Object {
-      "query": Object {
-        "repository": Object {
-          "__args": Object {
-            "name": "repo",
-            "owner": "some",
-          },
-          "pullRequests": Object {
-            "__args": Object {
-              "first": "25",
-            },
-            "nodes": Object {
-              "baseRefName": null,
-              "body": null,
-              "commits": Object {
-                "__args": Object {
-                  "first": "2",
-                },
-                "nodes": Object {
-                  "commit": Object {
-                    "author": Object {
-                      "email": null,
-                    },
-                    "committer": Object {
-                      "email": null,
-                    },
-                    "parents": Object {
-                      "__args": Object {
-                        "last": "1",
-                      },
-                      "edges": Object {
-                        "node": Object {
-                          "abbreviatedOid": null,
-                          "oid": null,
-                        },
-                      },
-                    },
-                  },
-                },
-              },
-              "headRefName": null,
-              "labels": Object {
-                "__args": Object {
-                  "last": "100",
-                },
-                "nodes": Object {
-                  "name": null,
-                },
-              },
-              "mergeStateStatus": null,
-              "mergeable": null,
-              "number": null,
-              "reviews": Object {
-                "__args": Object {
-                  "first": "1",
-                },
-                "nodes": Object {
-                  "state": null,
-                },
-              },
-              "title": null,
-            },
-          },
-        },
-      },
-    },
-    "headers": Object {
-      "accept": "application/vnd.github.merge-info-preview+json",
-      "accept-encoding": "gzip, deflate",
-      "authorization": "token abc123",
-      "content-length": 1263,
-      "host": "api.github.com",
-      "user-agent": "https://github.com/renovatebot/renovate",
-    },
-    "method": "POST",
-    "url": "https://api.github.com/graphql",
-  },
-  Object {
-    "graphql": Object {
-      "query": Object {
-        "repository": Object {
-          "__args": Object {
-            "name": "repo",
-            "owner": "some",
-          },
-          "pullRequests": Object {
-            "__args": Object {
-              "first": "100",
-            },
-            "nodes": Object {
-              "comments": Object {
+              "mergeable": null,
+              "number": null,
+              "reviews": Object {
                 "__args": Object {
-                  "last": "100",
+                  "first": "1",
                 },
                 "nodes": Object {
-                  "body": null,
-                  "databaseId": null,
+                  "state": null,
                 },
               },
-              "headRefName": null,
-              "number": null,
-              "state": null,
               "title": null,
             },
           },
@@ -1741,9 +1636,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 513,
+      "content-length": 1264,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -1760,7 +1657,7 @@ Array [
           },
           "pullRequests": Object {
             "__args": Object {
-              "first": "25",
+              "first": "100",
             },
             "nodes": Object {
               "comments": Object {
@@ -1782,9 +1679,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 512,
+      "content-length": 513,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -1985,84 +1884,7 @@ Array [
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 1264,
-      "host": "api.github.com",
-      "user-agent": "https://github.com/renovatebot/renovate",
-    },
-    "method": "POST",
-    "url": "https://api.github.com/graphql",
-  },
-  Object {
-    "graphql": Object {
-      "query": Object {
-        "repository": Object {
-          "__args": Object {
-            "name": "repo",
-            "owner": "some",
-          },
-          "pullRequests": Object {
-            "__args": Object {
-              "first": "25",
-            },
-            "nodes": Object {
-              "baseRefName": null,
-              "body": null,
-              "commits": Object {
-                "__args": Object {
-                  "first": "2",
-                },
-                "nodes": Object {
-                  "commit": Object {
-                    "author": Object {
-                      "email": null,
-                    },
-                    "committer": Object {
-                      "email": null,
-                    },
-                    "parents": Object {
-                      "__args": Object {
-                        "last": "1",
-                      },
-                      "edges": Object {
-                        "node": Object {
-                          "abbreviatedOid": null,
-                          "oid": null,
-                        },
-                      },
-                    },
-                  },
-                },
-              },
-              "headRefName": null,
-              "labels": Object {
-                "__args": Object {
-                  "last": "100",
-                },
-                "nodes": Object {
-                  "name": null,
-                },
-              },
-              "mergeStateStatus": null,
-              "mergeable": null,
-              "number": null,
-              "reviews": Object {
-                "__args": Object {
-                  "first": "1",
-                },
-                "nodes": Object {
-                  "state": null,
-                },
-              },
-              "title": null,
-            },
-          },
-        },
-      },
-    },
-    "headers": Object {
-      "accept": "application/vnd.github.merge-info-preview+json",
-      "accept-encoding": "gzip, deflate",
-      "authorization": "token abc123",
-      "content-length": 1263,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -2101,50 +1923,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
-      "host": "api.github.com",
-      "user-agent": "https://github.com/renovatebot/renovate",
-    },
-    "method": "POST",
-    "url": "https://api.github.com/graphql",
-  },
-  Object {
-    "graphql": Object {
-      "query": Object {
-        "repository": Object {
-          "__args": Object {
-            "name": "repo",
-            "owner": "some",
-          },
-          "pullRequests": Object {
-            "__args": Object {
-              "first": "25",
-            },
-            "nodes": Object {
-              "comments": Object {
-                "__args": Object {
-                  "last": "100",
-                },
-                "nodes": Object {
-                  "body": null,
-                  "databaseId": null,
-                },
-              },
-              "headRefName": null,
-              "number": null,
-              "state": null,
-              "title": null,
-            },
-          },
-        },
-      },
-    },
-    "headers": Object {
-      "accept-encoding": "gzip, deflate",
-      "authorization": "token abc123",
-      "content-length": 512,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -2473,136 +2256,58 @@ Array [
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
-    "method": "GET",
-    "url": "https://api.github.com/repos/some/repo",
-  },
-  Object {
-    "headers": Object {
-      "accept": "application/json",
-      "accept-encoding": "gzip, deflate",
-      "authorization": "token abc123",
-      "host": "api.github.com",
-      "user-agent": "https://github.com/renovatebot/renovate",
-    },
-    "method": "GET",
-    "url": "https://api.github.com/repos/some/repo/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e/statuses",
-  },
-]
-`;
-
-exports[`platform/github getPr(prNo) should return PR from closed graphql result 1`] = `
-Object {
-  "body": "dummy body",
-  "branchName": "renovate/delay-4.x",
-  "comments": Array [
-    Object {
-      "body": ":tada: This PR is included in version 13.63.5 :tada:
-
-The release is available on:
-- [npm package (@latest dist-tag)](https://www.npmjs.com/package/renovate)
-- [GitHub release](https://github.com/renovatebot/renovate/releases/tag/13.63.5)
-
-Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:",
-      "id": 420006957,
-    },
-  ],
-  "displayNumber": "Pull Request #2499",
-  "number": 2499,
-  "state": "merged",
-  "title": "build(deps): update dependency delay to v4.0.1",
-}
-`;
-
-exports[`platform/github getPr(prNo) should return PR from closed graphql result 2`] = `
-Array [
-  Object {
-    "headers": Object {
-      "accept": "application/json",
-      "accept-encoding": "gzip, deflate",
-      "authorization": "token abc123",
-      "host": "api.github.com",
-      "user-agent": "https://github.com/renovatebot/renovate",
-    },
-    "method": "GET",
-    "url": "https://api.github.com/repos/some/repo",
-  },
-  Object {
-    "graphql": Object {
-      "query": Object {
-        "repository": Object {
-          "__args": Object {
-            "name": "repo",
-            "owner": "some",
-          },
-          "pullRequests": Object {
-            "__args": Object {
-              "first": "100",
-            },
-            "nodes": Object {
-              "baseRefName": null,
-              "body": null,
-              "commits": Object {
-                "__args": Object {
-                  "first": "2",
-                },
-                "nodes": Object {
-                  "commit": Object {
-                    "author": Object {
-                      "email": null,
-                    },
-                    "committer": Object {
-                      "email": null,
-                    },
-                    "parents": Object {
-                      "__args": Object {
-                        "last": "1",
-                      },
-                      "edges": Object {
-                        "node": Object {
-                          "abbreviatedOid": null,
-                          "oid": null,
-                        },
-                      },
-                    },
-                  },
-                },
-              },
-              "headRefName": null,
-              "labels": Object {
-                "__args": Object {
-                  "last": "100",
-                },
-                "nodes": Object {
-                  "name": null,
-                },
-              },
-              "mergeStateStatus": null,
-              "mergeable": null,
-              "number": null,
-              "reviews": Object {
-                "__args": Object {
-                  "first": "1",
-                },
-                "nodes": Object {
-                  "state": null,
-                },
-              },
-              "title": null,
-            },
-          },
-        },
-      },
+    "method": "GET",
+    "url": "https://api.github.com/repos/some/repo",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc123",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/some/repo/commits/0d9c7726c3d628b7e28af234595cfd20febdbf8e/statuses",
+  },
+]
+`;
+
+exports[`platform/github getPr(prNo) should return PR from closed graphql result 1`] = `
+Object {
+  "body": "dummy body",
+  "branchName": "renovate/delay-4.x",
+  "comments": Array [
+    Object {
+      "body": ":tada: This PR is included in version 13.63.5 :tada:
+
+The release is available on:
+- [npm package (@latest dist-tag)](https://www.npmjs.com/package/renovate)
+- [GitHub release](https://github.com/renovatebot/renovate/releases/tag/13.63.5)
+
+Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:",
+      "id": 420006957,
     },
+  ],
+  "displayNumber": "Pull Request #2499",
+  "number": 2499,
+  "state": "merged",
+  "title": "build(deps): update dependency delay to v4.0.1",
+}
+`;
+
+exports[`platform/github getPr(prNo) should return PR from closed graphql result 2`] = `
+Array [
+  Object {
     "headers": Object {
-      "accept": "application/vnd.github.merge-info-preview+json",
+      "accept": "application/json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 1264,
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
-    "method": "POST",
-    "url": "https://api.github.com/graphql",
+    "method": "GET",
+    "url": "https://api.github.com/repos/some/repo",
   },
   Object {
     "graphql": Object {
@@ -2614,7 +2319,7 @@ Array [
           },
           "pullRequests": Object {
             "__args": Object {
-              "first": "25",
+              "first": "100",
             },
             "nodes": Object {
               "baseRefName": null,
@@ -2675,7 +2380,8 @@ Array [
       "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 1263,
+      "content-length": 1264,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -2683,45 +2389,15 @@ Array [
     "url": "https://api.github.com/graphql",
   },
   Object {
-    "graphql": Object {
-      "query": Object {
-        "repository": Object {
-          "__args": Object {
-            "name": "repo",
-            "owner": "some",
-          },
-          "pullRequests": Object {
-            "__args": Object {
-              "first": "100",
-            },
-            "nodes": Object {
-              "comments": Object {
-                "__args": Object {
-                  "last": "100",
-                },
-                "nodes": Object {
-                  "body": null,
-                  "databaseId": null,
-                },
-              },
-              "headRefName": null,
-              "number": null,
-              "state": null,
-              "title": null,
-            },
-          },
-        },
-      },
-    },
     "headers": Object {
+      "accept": "application/json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 513,
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
-    "method": "POST",
-    "url": "https://api.github.com/graphql",
+    "method": "GET",
+    "url": "https://api.github.com/repos/some/repo/git/refs/heads/master",
   },
   Object {
     "graphql": Object {
@@ -2733,7 +2409,7 @@ Array [
           },
           "pullRequests": Object {
             "__args": Object {
-              "first": "25",
+              "first": "100",
             },
             "nodes": Object {
               "comments": Object {
@@ -2755,9 +2431,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
-      "content-length": 512,
+      "content-length": 513,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -2868,84 +2546,7 @@ Array [
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 1264,
-      "host": "api.github.com",
-      "user-agent": "https://github.com/renovatebot/renovate",
-    },
-    "method": "POST",
-    "url": "https://api.github.com/graphql",
-  },
-  Object {
-    "graphql": Object {
-      "query": Object {
-        "repository": Object {
-          "__args": Object {
-            "name": "repo",
-            "owner": "some",
-          },
-          "pullRequests": Object {
-            "__args": Object {
-              "first": "25",
-            },
-            "nodes": Object {
-              "baseRefName": null,
-              "body": null,
-              "commits": Object {
-                "__args": Object {
-                  "first": "2",
-                },
-                "nodes": Object {
-                  "commit": Object {
-                    "author": Object {
-                      "email": null,
-                    },
-                    "committer": Object {
-                      "email": null,
-                    },
-                    "parents": Object {
-                      "__args": Object {
-                        "last": "1",
-                      },
-                      "edges": Object {
-                        "node": Object {
-                          "abbreviatedOid": null,
-                          "oid": null,
-                        },
-                      },
-                    },
-                  },
-                },
-              },
-              "headRefName": null,
-              "labels": Object {
-                "__args": Object {
-                  "last": "100",
-                },
-                "nodes": Object {
-                  "name": null,
-                },
-              },
-              "mergeStateStatus": null,
-              "mergeable": null,
-              "number": null,
-              "reviews": Object {
-                "__args": Object {
-                  "first": "1",
-                },
-                "nodes": Object {
-                  "state": null,
-                },
-              },
-              "title": null,
-            },
-          },
-        },
-      },
-    },
-    "headers": Object {
-      "accept": "application/vnd.github.merge-info-preview+json",
-      "accept-encoding": "gzip, deflate",
-      "authorization": "token abc123",
-      "content-length": 1263,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -3064,6 +2665,7 @@ Array [
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 1264,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -3102,9 +2704,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -3229,6 +2833,7 @@ Array [
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 1264,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -3267,9 +2872,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -3404,6 +3011,7 @@ Array [
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 1264,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -3442,9 +3050,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -3580,6 +3190,7 @@ Array [
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 1264,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -3618,9 +3229,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -3767,6 +3380,7 @@ Array [
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 1264,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -3805,9 +3419,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -3954,6 +3570,7 @@ Array [
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 1264,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -3992,9 +3609,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -4141,6 +3760,7 @@ Array [
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 1264,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -4179,9 +3799,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -4328,6 +3950,7 @@ Array [
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 1264,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -4366,9 +3989,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -4496,6 +4121,7 @@ Array [
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 1264,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -4534,9 +4160,11 @@ Array [
       },
     },
     "headers": Object {
+      "accept": "application/vnd.github.merge-info-preview+json",
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 513,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -4759,6 +4387,7 @@ Array [
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 697,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -4820,6 +4449,7 @@ Array [
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 697,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -4881,6 +4511,7 @@ Array [
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 697,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
@@ -4942,62 +4573,7 @@ Array [
       "accept-encoding": "gzip, deflate",
       "authorization": "token abc123",
       "content-length": 697,
-      "host": "api.github.com",
-      "user-agent": "https://github.com/renovatebot/renovate",
-    },
-    "method": "POST",
-    "url": "https://api.github.com/graphql",
-  },
-  Object {
-    "graphql": Object {
-      "query": Object {
-        "repository": Object {
-          "__args": Object {
-            "name": "undefined",
-            "owner": "undefined",
-          },
-          "vulnerabilityAlerts": Object {
-            "__args": Object {
-              "last": "100",
-            },
-            "edges": Object {
-              "node": Object {
-                "dismissReason": null,
-                "securityAdvisory": Object {
-                  "description": null,
-                  "identifiers": Object {
-                    "type": null,
-                    "value": null,
-                  },
-                  "references": Object {
-                    "url": null,
-                  },
-                  "severity": null,
-                },
-                "securityVulnerability": Object {
-                  "firstPatchedVersion": Object {
-                    "identifier": null,
-                  },
-                  "package": Object {
-                    "ecosystem": null,
-                    "name": null,
-                  },
-                  "vulnerableVersionRange": null,
-                },
-                "vulnerableManifestFilename": null,
-                "vulnerableManifestPath": null,
-                "vulnerableRequirements": null,
-              },
-            },
-          },
-        },
-      },
-    },
-    "headers": Object {
-      "accept": "application/vnd.github.vixen-preview+json",
-      "accept-encoding": "gzip, deflate",
-      "authorization": "token abc123",
-      "content-length": 697,
+      "content-type": "application/json",
       "host": "api.github.com",
       "user-agent": "https://github.com/renovatebot/renovate",
     },
diff --git a/lib/platform/github/gh-got-wrapper.spec.ts b/lib/platform/github/gh-got-wrapper.spec.ts
deleted file mode 100644
index 9bb82f78f0..0000000000
--- a/lib/platform/github/gh-got-wrapper.spec.ts
+++ /dev/null
@@ -1,239 +0,0 @@
-import delay from 'delay';
-import { Response } from 'got';
-import {
-  PLATFORM_BAD_CREDENTIALS,
-  PLATFORM_FAILURE,
-  PLATFORM_INTEGRATION_UNAUTHORIZED,
-  PLATFORM_RATE_LIMIT_EXCEEDED,
-  REPOSITORY_CHANGED,
-} from '../../constants/error-messages';
-import _got from '../../util/got';
-import { api } from './gh-got-wrapper';
-
-jest.mock('../../util/got');
-jest.mock('delay');
-
-const got: any = _got;
-
-const get: <T extends object = any>(
-  path: string,
-  options?: any,
-  okToRetry?: boolean
-) => Promise<Response<T>> = api as any;
-
-async function getError(): Promise<Error> {
-  try {
-    await get('some-url', {}, false);
-  } catch (err) {
-    return err;
-  }
-  return null;
-}
-
-describe('platform/gh-got-wrapper', () => {
-  beforeEach(() => {
-    jest.resetAllMocks();
-    delete global.appMode;
-    (delay as any).mockImplementation(() => Promise.resolve());
-  });
-  it('supports app mode', async () => {
-    global.appMode = true;
-    await api.get('some-url', { headers: { accept: 'some-accept' } });
-    expect(got.mock.calls[0][1].headers.accept).toBe(
-      'application/vnd.github.machine-man-preview+json, some-accept'
-    );
-  });
-  it('strips v3 for graphql', async () => {
-    got.mockImplementationOnce(() => ({
-      body: '{"data":{',
-    }));
-    api.setBaseUrl('https://ghe.mycompany.com/api/v3/');
-    await api.post('graphql', {
-      body: 'abc',
-    });
-    expect(got.mock.calls[0][0].includes('/v3')).toBe(false);
-  });
-  it('paginates', async () => {
-    got.mockReturnValueOnce({
-      headers: {
-        link:
-          '<https://api.github.com/search/code?q=addClass+user%3Amozilla&page=2>; rel="next", <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=3>; rel="last"',
-      },
-      body: ['a'],
-    });
-    got.mockReturnValueOnce({
-      headers: {
-        link:
-          '<https://api.github.com/search/code?q=addClass+user%3Amozilla&page=3>; rel="next", <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=3>; rel="last"',
-      },
-      body: ['b', 'c'],
-    });
-    got.mockReturnValueOnce({
-      headers: {},
-      body: ['d'],
-    });
-    const res = await api.get('some-url', { paginate: true });
-    expect(res.body).toEqual(['a', 'b', 'c', 'd']);
-    expect(got).toHaveBeenCalledTimes(3);
-  });
-  it('attempts to paginate', async () => {
-    got.mockReturnValueOnce({
-      headers: {
-        link:
-          '<https://api.github.com/search/code?q=addClass+user%3Amozilla&page=34>; rel="last"',
-      },
-      body: ['a'],
-    });
-    got.mockReturnValueOnce({
-      headers: {},
-      body: ['b'],
-    });
-    const res = await api.get('some-url', { paginate: true });
-    expect(res.body).toHaveLength(1);
-    expect(got).toHaveBeenCalledTimes(1);
-  });
-  it('should throw rate limit exceeded', async () => {
-    got.mockImplementationOnce(() =>
-      Promise.reject({
-        statusCode: 403,
-        message:
-          'Error updating branch: API rate limit exceeded for installation ID 48411. (403)',
-      })
-    );
-    await expect(api.get('some-url')).rejects.toThrow();
-  });
-  it('should throw Bad credentials', async () => {
-    got.mockImplementationOnce(() =>
-      Promise.reject({
-        statusCode: 401,
-        message: 'Bad credentials. (401)',
-      })
-    );
-    const e = await getError();
-    expect(e).toBeDefined();
-    expect(e.message).toEqual(PLATFORM_BAD_CREDENTIALS);
-  });
-  it('should throw platform failure', async () => {
-    got.mockImplementationOnce(() =>
-      Promise.reject({
-        statusCode: 401,
-        message: 'Bad credentials. (401)',
-        headers: {
-          'x-ratelimit-limit': '60',
-        },
-      })
-    );
-    const e = await getError();
-    expect(e).toBeDefined();
-    expect(e.message).toEqual(PLATFORM_FAILURE);
-  });
-  it('should throw platform failure for ENOTFOUND, ETIMEDOUT or EAI_AGAIN', async () => {
-    const codes = ['ENOTFOUND', 'ETIMEDOUT', 'EAI_AGAIN'];
-    for (let idx = 0; idx < codes.length; idx += 1) {
-      const code = codes[idx];
-      got.mockImplementationOnce(() =>
-        Promise.reject({
-          name: 'RequestError',
-          code,
-        })
-      );
-      const e = await getError();
-      expect(e).toBeDefined();
-      expect(e.message).toEqual(PLATFORM_FAILURE);
-    }
-  });
-  it('should throw platform failure for 500', async () => {
-    got.mockImplementationOnce(() =>
-      Promise.reject({
-        statusCode: 500,
-        message: 'Internal Server Error',
-      })
-    );
-    const e = await getError();
-    expect(e).toBeDefined();
-    expect(e.message).toEqual(PLATFORM_FAILURE);
-  });
-  it('should throw platform failure ParseError', async () => {
-    got.mockImplementationOnce(() =>
-      Promise.reject({
-        name: 'ParseError',
-      })
-    );
-    const e = await getError();
-    expect(e).toBeDefined();
-    expect(e.message).toEqual(PLATFORM_FAILURE);
-  });
-  it('should throw for unauthorized integration', async () => {
-    got.mockImplementationOnce(() =>
-      Promise.reject({
-        statusCode: 403,
-        message: 'Resource not accessible by integration (403)',
-      })
-    );
-    const e = await getError();
-    expect(e).toBeDefined();
-    expect(e.message).toEqual(PLATFORM_INTEGRATION_UNAUTHORIZED);
-  });
-  it('should throw for unauthorized integration', async () => {
-    const gotErr = {
-      statusCode: 403,
-      body: { message: 'Upgrade to GitHub Pro' },
-    };
-    got.mockRejectedValueOnce(gotErr);
-    const e = await getError();
-    expect(e).toBeDefined();
-    expect(e).toBe(gotErr);
-  });
-  it('should throw on abuse', async () => {
-    const gotErr = {
-      statusCode: 403,
-      message: 'You have triggered an abuse detection mechanism',
-    };
-    got.mockRejectedValueOnce(gotErr);
-    const e = await getError();
-    expect(e).toBeDefined();
-    expect(e.message).toEqual(PLATFORM_RATE_LIMIT_EXCEEDED);
-  });
-  it('should throw on repository change', async () => {
-    const gotErr = {
-      statusCode: 422,
-      body: {
-        message: 'foobar',
-        errors: [{ code: 'invalid' }],
-      },
-    };
-    got.mockRejectedValueOnce(gotErr);
-    const e = await getError();
-    expect(e).toBeDefined();
-    expect(e.message).toEqual(REPOSITORY_CHANGED);
-  });
-  it('should throw platform failure on 422 response', async () => {
-    const gotErr = {
-      statusCode: 422,
-      message: 'foobar',
-    };
-    got.mockRejectedValueOnce(gotErr);
-    const e = await getError();
-    expect(e).toBeDefined();
-    expect(e.message).toEqual(PLATFORM_FAILURE);
-  });
-  it('should throw original error when failed to add reviewers', async () => {
-    const gotErr = {
-      statusCode: 422,
-      message: 'Review cannot be requested from pull request author.',
-    };
-    got.mockRejectedValueOnce(gotErr);
-    const e = await getError();
-    expect(e).toBeDefined();
-    expect(e).toStrictEqual(gotErr);
-  });
-  it('should throw original error of unknown type', async () => {
-    const gotErr = {
-      statusCode: 418,
-      message: 'Sorry, this is a teapot',
-    };
-    got.mockRejectedValueOnce(gotErr);
-    const e = await getError();
-    expect(e).toBe(gotErr);
-  });
-});
diff --git a/lib/platform/github/gh-got-wrapper.ts b/lib/platform/github/gh-got-wrapper.ts
deleted file mode 100644
index b6e1e94dbc..0000000000
--- a/lib/platform/github/gh-got-wrapper.ts
+++ /dev/null
@@ -1,214 +0,0 @@
-import URL from 'url';
-import { GotError } from 'got';
-import pAll from 'p-all';
-import parseLinkHeader from 'parse-link-header';
-
-import {
-  PLATFORM_BAD_CREDENTIALS,
-  PLATFORM_FAILURE,
-  PLATFORM_INTEGRATION_UNAUTHORIZED,
-  PLATFORM_RATE_LIMIT_EXCEEDED,
-  REPOSITORY_CHANGED,
-} from '../../constants/error-messages';
-import { PLATFORM_TYPE_GITHUB } from '../../constants/platforms';
-import { logger } from '../../logger';
-import got, { GotJSONOptions } from '../../util/got';
-import { maskToken } from '../../util/mask';
-import { GotApi, GotResponse } from '../common';
-
-const hostType = PLATFORM_TYPE_GITHUB;
-export const getHostType = (): string => hostType;
-
-let baseUrl = 'https://api.github.com/';
-export const getBaseUrl = (): string => baseUrl;
-
-type GotRequestError<E = unknown, T = unknown> = GotError & {
-  body: {
-    message?: string;
-    errors?: E[];
-  };
-  headers?: Record<string, T>;
-};
-
-type GotRequestOptions = GotJSONOptions & {
-  token?: string;
-};
-
-export function dispatchError(
-  err: GotRequestError,
-  path: string,
-  opts: GotRequestOptions
-): never {
-  let message = err.message;
-  if (err.body && err.body.message) {
-    message = err.body.message;
-  }
-  if (
-    err.name === 'RequestError' &&
-    (err.code === 'ENOTFOUND' ||
-      err.code === 'ETIMEDOUT' ||
-      err.code === 'EAI_AGAIN')
-  ) {
-    logger.debug({ err }, 'GitHub failure: RequestError');
-    throw new Error(PLATFORM_FAILURE);
-  }
-  if (err.name === 'ParseError') {
-    logger.debug({ err }, 'GitHub failure: ParseError');
-    throw new Error(PLATFORM_FAILURE);
-  }
-  if (err.statusCode >= 500 && err.statusCode < 600) {
-    logger.debug({ err }, 'GitHub failure: 5xx');
-    throw new Error(PLATFORM_FAILURE);
-  }
-  if (
-    err.statusCode === 403 &&
-    message.startsWith('You have triggered an abuse detection mechanism')
-  ) {
-    logger.debug({ err }, 'GitHub failure: abuse detection');
-    throw new Error(PLATFORM_RATE_LIMIT_EXCEEDED);
-  }
-  if (err.statusCode === 403 && message.includes('Upgrade to GitHub Pro')) {
-    logger.debug({ path }, 'Endpoint needs paid GitHub plan');
-    throw err;
-  }
-  if (err.statusCode === 403 && message.includes('rate limit exceeded')) {
-    logger.debug({ err }, 'GitHub failure: rate limit');
-    throw new Error(PLATFORM_RATE_LIMIT_EXCEEDED);
-  }
-  if (
-    err.statusCode === 403 &&
-    message.startsWith('Resource not accessible by integration')
-  ) {
-    logger.debug(
-      { err },
-      'GitHub failure: Resource not accessible by integration'
-    );
-    throw new Error(PLATFORM_INTEGRATION_UNAUTHORIZED);
-  }
-  if (err.statusCode === 401 && message.includes('Bad credentials')) {
-    const rateLimit = err.headers ? err.headers['x-ratelimit-limit'] : -1;
-    logger.debug(
-      {
-        token: maskToken(opts.token),
-        err,
-      },
-      'GitHub failure: Bad credentials'
-    );
-    if (rateLimit === '60') {
-      throw new Error(PLATFORM_FAILURE);
-    }
-    throw new Error(PLATFORM_BAD_CREDENTIALS);
-  }
-  if (err.statusCode === 422) {
-    if (
-      message.includes('Review cannot be requested from pull request author')
-    ) {
-      throw err;
-    } else if (
-      err.body &&
-      err.body.errors &&
-      err.body.errors.find((e: any) => e.code === 'invalid')
-    ) {
-      throw new Error(REPOSITORY_CHANGED);
-    }
-    logger.debug({ err }, '422 Error thrown from GitHub');
-    throw new Error(PLATFORM_FAILURE);
-  }
-  throw err;
-}
-
-async function get(
-  path: string,
-  options?: any,
-  okToRetry = true
-): Promise<GotResponse> {
-  let result = null;
-
-  const opts = {
-    hostType,
-    baseUrl,
-    json: true,
-    ...options,
-  };
-  const method = opts.method || 'get';
-  if (method.toLowerCase() === 'post' && path === 'graphql') {
-    // GitHub Enterprise uses unversioned graphql path
-    opts.baseUrl = opts.baseUrl.replace('/v3/', '/');
-  }
-  logger.trace(`${method.toUpperCase()} ${path}`);
-  try {
-    if (global.appMode) {
-      const appAccept = 'application/vnd.github.machine-man-preview+json';
-      opts.headers = {
-        accept: appAccept,
-        'user-agent':
-          process.env.RENOVATE_USER_AGENT ||
-          'https://github.com/renovatebot/renovate',
-        ...opts.headers,
-      };
-      if (opts.headers.accept !== appAccept) {
-        opts.headers.accept = `${appAccept}, ${opts.headers.accept}`;
-      }
-    }
-    result = await got(path, opts);
-    if (opts.paginate) {
-      // Check if result is paginated
-      const pageLimit = opts.pageLimit || 10;
-      const linkHeader = parseLinkHeader(result.headers.link as string);
-      if (linkHeader && linkHeader.next && linkHeader.last) {
-        let lastPage = +linkHeader.last.page;
-        if (!process.env.RENOVATE_PAGINATE_ALL && opts.paginate !== 'all') {
-          lastPage = Math.min(pageLimit, lastPage);
-        }
-        const pageNumbers = Array.from(
-          new Array(lastPage),
-          (x, i) => i + 1
-        ).slice(1);
-        const queue = pageNumbers.map((page) => (): Promise<GotResponse> => {
-          const nextUrl = URL.parse(linkHeader.next.url, true);
-          delete nextUrl.search;
-          nextUrl.query.page = page.toString();
-          return get(
-            URL.format(nextUrl),
-            { ...opts, paginate: false },
-            okToRetry
-          );
-        });
-        const pages = await pAll<{ body: any[] }>(queue, { concurrency: 5 });
-        result.body = result.body.concat(
-          ...pages.filter(Boolean).map((page) => page.body)
-        );
-      }
-    }
-    // istanbul ignore if
-    if (method === 'POST' && path === 'graphql') {
-      const goodResult = '{"data":{';
-      if (result.body.startsWith(goodResult)) {
-        if (!okToRetry) {
-          logger.debug('Recovered graphql query');
-        }
-      } else if (okToRetry) {
-        logger.debug('Retrying graphql query');
-        opts.body = opts.body.replace('first: 100', 'first: 25');
-        return get(path, opts, !okToRetry);
-      }
-    }
-  } catch (gotErr) {
-    dispatchError(gotErr, path, opts);
-  }
-  return result;
-}
-
-const helpers = ['get', 'post', 'put', 'patch', 'head', 'delete'];
-
-for (const x of helpers) {
-  (get as any)[x] = (url: string, opts: any): Promise<GotResponse> =>
-    get(url, { ...opts, method: x.toUpperCase() });
-}
-
-get.setBaseUrl = (u: string): void => {
-  baseUrl = u;
-};
-
-export const api: GotApi = get as any;
-export default api;
diff --git a/lib/platform/github/gh-graphql-wrapper.spec.ts b/lib/platform/github/gh-graphql-wrapper.spec.ts
deleted file mode 100644
index d3a0626eaf..0000000000
--- a/lib/platform/github/gh-graphql-wrapper.spec.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import { getGraphqlNodes } from './gh-graphql-wrapper';
-
-/** @type any */
-const got = require('../../util/got').default;
-
-jest.mock('../../util/got');
-
-const query = `
-      query {
-        repository(owner: "testOwner", name: "testName") {
-          testItem (orderBy: {field: UPDATED_AT, direction: DESC}, filterBy: {createdBy: "someone"}) {
-            pageInfo {
-              endCursor
-              hasNextPage
-            }
-            nodes {
-              number state title body
-            }
-          }
-        }
-      }`;
-
-async function getError(q: string, f: string) {
-  let error;
-  try {
-    await getGraphqlNodes(q, f);
-  } catch (err) {
-    error = err;
-  }
-  return error;
-}
-
-describe('platform/gh-graphql-wrapper', () => {
-  beforeEach(() => {
-    jest.resetAllMocks();
-    delete global.appMode;
-  });
-  it('supports app mode', async () => {
-    global.appMode = true;
-    await getGraphqlNodes(query, 'testItem');
-    expect(got.mock.calls[0][1].headers.accept).toEqual(
-      'application/vnd.github.machine-man-preview+json, application/vnd.github.merge-info-preview+json'
-    );
-  });
-  it('returns empty array for undefined data', async () => {
-    got.mockReturnValue({
-      body: {
-        data: {
-          someprop: 'someval',
-        },
-      },
-    });
-    expect(await getGraphqlNodes(query, 'testItem')).toEqual([]);
-  });
-  it('returns empty array for undefined data.', async () => {
-    got.mockReturnValue({
-      body: {
-        data: { repository: { otherField: 'someval' } },
-      },
-    });
-    expect(await getGraphqlNodes(query, 'testItem')).toEqual([]);
-  });
-  it('throws errors for invalid responses', async () => {
-    const gotErr = {
-      statusCode: 418,
-      message: 'Sorry, this is a teapot',
-    };
-    got.mockImplementationOnce(() => Promise.reject(gotErr));
-    const e = await getError(query, 'someItem');
-    expect(e).toBe(gotErr);
-  });
-  it('halves node count and retries request', async () => {
-    got.mockReturnValue({
-      body: {
-        data: {
-          someprop: 'someval',
-        },
-      },
-    });
-
-    await getGraphqlNodes(query, 'testItem');
-    expect(got).toHaveBeenCalledTimes(7);
-  });
-  it('retrieves all data from all pages', async () => {
-    got.mockReturnValueOnce({
-      body: {
-        data: {
-          repository: {
-            testItem: {
-              pageInfo: {
-                endCursor: 'cursor1',
-                hasNextPage: true,
-              },
-              nodes: [
-                {
-                  number: 1,
-                  state: 'OPEN',
-                  title: 'title-1',
-                  body: 'the body 1',
-                },
-              ],
-            },
-          },
-        },
-      },
-    });
-
-    got.mockReturnValueOnce({
-      body: {
-        data: {
-          repository: {
-            testItem: {
-              pageInfo: {
-                endCursor: 'cursor2',
-                hasNextPage: true,
-              },
-              nodes: [
-                {
-                  number: 2,
-                  state: 'CLOSED',
-                  title: 'title-2',
-                  body: 'the body 2',
-                },
-              ],
-            },
-          },
-        },
-      },
-    });
-
-    got.mockReturnValueOnce({
-      body: {
-        data: {
-          repository: {
-            testItem: {
-              pageInfo: {
-                endCursor: 'cursor3',
-                hasNextPage: false,
-              },
-              nodes: [
-                {
-                  number: 3,
-                  state: 'OPEN',
-                  title: 'title-3',
-                  body: 'the body 3',
-                },
-              ],
-            },
-          },
-        },
-      },
-    });
-
-    const items = await getGraphqlNodes(query, 'testItem');
-    expect(got).toHaveBeenCalledTimes(3);
-    expect(items.length).toEqual(3);
-  });
-});
diff --git a/lib/platform/github/gh-graphql-wrapper.ts b/lib/platform/github/gh-graphql-wrapper.ts
deleted file mode 100644
index 9b63e4824a..0000000000
--- a/lib/platform/github/gh-graphql-wrapper.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import { logger } from '../../logger';
-import got, { GotJSONOptions } from '../../util/got';
-import { dispatchError, getBaseUrl, getHostType } from './gh-got-wrapper';
-
-const accept = 'application/vnd.github.merge-info-preview+json';
-
-const gqlOpts: GotJSONOptions = {
-  json: true,
-  method: 'POST',
-  headers: {
-    accept,
-  },
-};
-
-interface GithubGraphqlResponse<T = unknown> {
-  data?: {
-    repository?: T;
-  };
-  errors?: { message: string; locations: unknown }[];
-}
-
-async function get<T = unknown>(
-  query: string
-): Promise<GithubGraphqlResponse<T>> {
-  let result = null;
-
-  const path = 'graphql';
-  const options: GotJSONOptions = {
-    ...gqlOpts,
-    hostType: getHostType(),
-    baseUrl: (getBaseUrl() || '').replace('/v3/', '/'), // GitHub Enterprise uses unversioned graphql path
-    body: { query },
-  };
-
-  if (global.appMode) {
-    options.headers = {
-      ...options.headers,
-      accept: `application/vnd.github.machine-man-preview+json, ${accept}`,
-      'user-agent':
-        process.env.RENOVATE_USER_AGENT ||
-        'https://github.com/renovatebot/renovate',
-    };
-  }
-
-  logger.trace(`Performing Github GraphQL request`);
-
-  try {
-    const res = await got('graphql', options);
-    result = res && res.body;
-  } catch (gotErr) {
-    dispatchError(gotErr, path, options);
-  }
-  return result;
-}
-
-export async function getGraphqlNodes<T = Record<string, unknown>>(
-  queryOrig: string,
-  fieldName: string
-): Promise<T[]> {
-  const result: T[] = [];
-
-  const regex = new RegExp(`(\\W)${fieldName}(\\s*)\\(`);
-
-  let cursor = null;
-  let count = 100;
-  let canIterate = true;
-
-  while (canIterate) {
-    let replacement = `$1${fieldName}$2(first: ${count}`;
-    if (cursor) {
-      replacement += `, after: "${cursor}", `;
-    }
-    const query = queryOrig.replace(regex, replacement);
-    const gqlRes = await get<T>(query);
-    if (
-      gqlRes &&
-      gqlRes.data &&
-      gqlRes.data.repository &&
-      gqlRes.data.repository[fieldName]
-    ) {
-      const { nodes, pageInfo } = gqlRes.data.repository[fieldName];
-      result.push(...nodes);
-
-      const { hasNextPage, endCursor } = pageInfo;
-      if (hasNextPage && endCursor) {
-        cursor = endCursor;
-      } else {
-        canIterate = false;
-      }
-    } else {
-      count = Math.floor(count / 2);
-      if (count === 0) {
-        logger.error('Error fetching GraphQL nodes');
-        canIterate = false;
-      }
-    }
-  }
-
-  return result;
-}
diff --git a/lib/platform/github/index.spec.ts b/lib/platform/github/index.spec.ts
index f617748be4..9234301c2f 100644
--- a/lib/platform/github/index.spec.ts
+++ b/lib/platform/github/index.spec.ts
@@ -64,7 +64,7 @@ describe('platform/github', () => {
     'lib/platform/github/__fixtures__/graphql/pullrequest-1.json',
     'utf8'
   );
-  const graphqlClosedPullrequests = fs.readFileSync(
+  const graphqlClosedPullRequests = fs.readFileSync(
     'lib/platform/github/__fixtures__/graphql/pullrequests-closed.json',
     'utf8'
   );
@@ -426,8 +426,8 @@ describe('platform/github', () => {
       const scope = httpMock.scope(githubApiHost);
       initRepoMock(scope, 'some/repo');
       scope
-        .persist()
         .post('/graphql')
+        .twice()
         .reply(200, {})
         .get('/repos/some/repo/pulls?per_page=100&state=all')
         .reply(200, [
@@ -468,8 +468,8 @@ describe('platform/github', () => {
       const scope = httpMock.scope(githubApiHost);
       forkInitRepoMock(scope, 'some/repo', 'forked/repo');
       scope
-        .persist()
         .post('/graphql')
+        .twice()
         .reply(200, {})
         .get('/repos/some/repo/pulls?per_page=100&state=all')
         .reply(200, [
@@ -1329,7 +1329,6 @@ describe('platform/github', () => {
       initRepoMock(scope, 'some/repo');
       scope
         .post('/graphql')
-        .twice()
         .reply(200, {})
         .get('/repos/some/repo/issues/42/comments?per_page=100')
         .reply(200, [])
@@ -1351,8 +1350,7 @@ describe('platform/github', () => {
       initRepoMock(scope, 'some/repo');
       scope
         .post('/graphql')
-        .twice()
-        .reply(200, graphqlClosedPullrequests)
+        .reply(200, graphqlClosedPullRequests)
         .post('/repos/some/repo/issues/2499/comments')
         .reply(200);
       await github.initRepo({
@@ -1370,7 +1368,6 @@ describe('platform/github', () => {
       initRepoMock(scope, 'some/repo');
       scope
         .post('/graphql')
-        .twice()
         .reply(200, {})
         .get('/repos/some/repo/issues/42/comments?per_page=100')
         .reply(200, [{ id: 1234, body: '### some-subject\n\nblablabla' }])
@@ -1391,7 +1388,6 @@ describe('platform/github', () => {
       initRepoMock(scope, 'some/repo');
       scope
         .post('/graphql')
-        .twice()
         .reply(200, {})
         .get('/repos/some/repo/issues/42/comments?per_page=100')
         .reply(200, [{ id: 1234, body: '### some-subject\n\nsome\ncontent' }]);
@@ -1410,7 +1406,6 @@ describe('platform/github', () => {
       initRepoMock(scope, 'some/repo');
       scope
         .post('/graphql')
-        .twice()
         .reply(200, {})
         .get('/repos/some/repo/issues/42/comments?per_page=100')
         .reply(200, [{ id: 1234, body: '!merge' }]);
@@ -1431,7 +1426,6 @@ describe('platform/github', () => {
       initRepoMock(scope, 'some/repo');
       scope
         .post('/graphql')
-        .twice()
         .reply(200, {})
         .get('/repos/some/repo/issues/42/comments?per_page=100')
         .reply(200, [{ id: 1234, body: '### some-subject\n\nblablabla' }])
@@ -1446,7 +1440,6 @@ describe('platform/github', () => {
       initRepoMock(scope, 'some/repo');
       scope
         .post('/graphql')
-        .twice()
         .reply(200, {})
         .get('/repos/some/repo/issues/42/comments?per_page=100')
         .reply(200, [{ id: 1234, body: 'some-content' }])
@@ -1596,7 +1589,6 @@ describe('platform/github', () => {
       const scope = httpMock.scope(githubApiHost);
       initRepoMock(scope, 'some/repo');
       scope
-        .persist()
         .post('/graphql')
         .reply(200, graphqlOpenPullRequests)
         .get('/repos/some/repo/git/refs/heads/master')
@@ -1624,11 +1616,13 @@ describe('platform/github', () => {
         .post('/graphql')
         .reply(200, graphqlOpenPullRequests)
         .post('/graphql')
-        .times(2)
-        .reply(200, {})
-
-        .post('/graphql')
-        .reply(200, graphqlClosedPullrequests);
+        .reply(200, graphqlClosedPullRequests)
+        .get('/repos/some/repo/git/refs/heads/master')
+        .reply(200, {
+          object: {
+            sha: '1234123412341234123412341234123412341234',
+          },
+        });
       await github.initRepo({
         repository: 'some/repo',
       } as any);
@@ -2108,28 +2102,43 @@ describe('platform/github', () => {
   });
   describe('getVulnerabilityAlerts()', () => {
     it('returns empty if error', async () => {
-      httpMock.scope(githubApiHost).post('/graphql').twice().reply(200, {});
+      httpMock.scope(githubApiHost).post('/graphql').reply(200, {});
       const res = await github.getVulnerabilityAlerts();
       expect(res).toHaveLength(0);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('returns array if found', async () => {
       // prettier-ignore
-      httpMock.scope(githubApiHost).post('/graphql').reply(200, "{\"data\":{\"repository\":{\"vulnerabilityAlerts\":{\"edges\":[{\"node\":{\"externalIdentifier\":\"CVE-2018-1000136\",\"externalReference\":\"https://nvd.nist.gov/vuln/detail/CVE-2018-1000136\",\"affectedRange\":\">= 1.8, < 1.8.3\",\"fixedIn\":\"1.8.3\",\"id\":\"MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQ1MzE3NDk4MQ==\",\"packageName\":\"electron\"}}]}}}}");
+      httpMock.scope(githubApiHost).post('/graphql').reply(200, {
+        "data": {
+          "repository": {
+            "vulnerabilityAlerts": {
+              "edges": [{
+                "node": {
+                  "externalIdentifier": "CVE-2018-1000136",
+                  "externalReference": "https://nvd.nist.gov/vuln/detail/CVE-2018-1000136",
+                  "affectedRange": ">= 1.8, < 1.8.3", "fixedIn": "1.8.3",
+                  "id": "MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQ1MzE3NDk4MQ==", "packageName": "electron"
+                }
+              }]
+            }
+          }
+        }
+      });
       const res = await github.getVulnerabilityAlerts();
       expect(res).toHaveLength(1);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('returns empty if disabled', async () => {
       // prettier-ignore
-      httpMock.scope(githubApiHost).post('/graphql').reply(200, "{\"data\":{\"repository\":{}}}");
+      httpMock.scope(githubApiHost).post('/graphql').reply(200, {data: { repository: {} }} );
       const res = await github.getVulnerabilityAlerts();
       expect(res).toHaveLength(0);
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('handles network error', async () => {
       // prettier-ignore
-      httpMock.scope(githubApiHost).persist().post('/graphql').replyWithError('unknown error');
+      httpMock.scope(githubApiHost).post('/graphql').replyWithError('unknown error');
       const res = await github.getVulnerabilityAlerts();
       expect(res).toHaveLength(0);
       expect(httpMock.getTrace()).toMatchSnapshot();
diff --git a/lib/platform/github/index.ts b/lib/platform/github/index.ts
index 3ad40973f2..93da5144f3 100644
--- a/lib/platform/github/index.ts
+++ b/lib/platform/github/index.ts
@@ -25,6 +25,7 @@ import {
 import { logger } from '../../logger';
 import { BranchStatus } from '../../types';
 import * as hostRules from '../../util/host-rules';
+import * as githubHttp from '../../util/http/github';
 import { sanitize } from '../../util/sanitize';
 import { ensureTrailingSlash } from '../../util/url';
 import {
@@ -45,8 +46,6 @@ import {
 } from '../common';
 import GitStorage, { StatusResult } from '../git/storage';
 import { smartTruncate } from '../utils/pr-body';
-import { api } from './gh-got-wrapper';
-import { getGraphqlNodes } from './gh-graphql-wrapper';
 import {
   BranchProtection,
   CombinedBranchStatus,
@@ -57,6 +56,8 @@ import {
   PrList,
 } from './types';
 
+const githubApi = new githubHttp.GithubHttp();
+
 const defaultConfigFile = configFileNames[0];
 
 let config: LocalRepoConfig = {} as any;
@@ -82,7 +83,7 @@ export async function initPlatform({
 
   if (endpoint) {
     defaults.endpoint = ensureTrailingSlash(endpoint);
-    api.setBaseUrl(defaults.endpoint);
+    githubHttp.setBaseUrl(defaults.endpoint);
   } else {
     logger.debug('Using default github endpoint: ' + defaults.endpoint);
   }
@@ -90,9 +91,12 @@ export async function initPlatform({
   let renovateUsername: string;
   try {
     const userData = (
-      await api.get(defaults.endpoint + 'user', {
-        token,
-      })
+      await githubApi.getJson<{ login: string; name: string }>(
+        defaults.endpoint + 'user',
+        {
+          token,
+        }
+      )
     ).body;
     renovateUsername = userData.login;
     gitAuthor = userData.name;
@@ -102,9 +106,12 @@ export async function initPlatform({
   }
   try {
     const userEmail = (
-      await api.get(defaults.endpoint + 'user/emails', {
-        token,
-      })
+      await githubApi.getJson<{ email: string }[]>(
+        defaults.endpoint + 'user/emails',
+        {
+          token,
+        }
+      )
     ).body;
     if (userEmail.length && userEmail[0].email) {
       gitAuthor += ` <${userEmail[0].email}>`;
@@ -131,8 +138,11 @@ export async function initPlatform({
 export async function getRepos(): Promise<string[]> {
   logger.debug('Autodiscovering GitHub repositories');
   try {
-    const res = await api.get('user/repos?per_page=100', { paginate: true });
-    return res.body.map((repo: { full_name: string }) => repo.full_name);
+    const res = await githubApi.getJson<{ full_name: string }[]>(
+      'user/repos?per_page=100',
+      { paginate: true }
+    );
+    return res.body.map((repo) => repo.full_name);
   } catch (err) /* istanbul ignore next */ {
     logger.error({ err }, `GitHub getRepos error`);
     throw err;
@@ -156,7 +166,7 @@ async function getBranchProtection(
   if (config.parentRepo) {
     return {};
   }
-  const res = await api.get(
+  const res = await githubApi.getJson<BranchProtection>(
     `repos/${config.repository}/branches/${escapeHash(branchName)}/protection`
   );
   return res.body;
@@ -165,7 +175,7 @@ async function getBranchProtection(
 // Return the commit SHA for a branch
 async function getBranchCommit(branchName: string): Promise<string> {
   try {
-    const res = await api.get(
+    const res = await githubApi.getJson<{ object: { sha: string } }>(
       `repos/${config.repository}/git/refs/heads/${escapeHash(branchName)}`
     );
     return res.body.object.sha;
@@ -208,7 +218,7 @@ export async function initRepo({
     // Necessary for Renovate Pro - do not remove
     logger.debug('Overriding default GitHub endpoint');
     defaults.endpoint = endpoint;
-    api.setBaseUrl(endpoint);
+    githubHttp.setBaseUrl(endpoint);
   }
   const opts = hostRules.find({
     hostType: PLATFORM_TYPE_GITHUB,
@@ -222,7 +232,7 @@ export async function initRepo({
   config.gitPrivateKey = gitPrivateKey;
   let res;
   try {
-    res = await api.get(`repos/${repository}`);
+    res = await githubApi.getJson<{ fork: boolean }>(`repos/${repository}`);
     logger.trace({ repositoryDetails: res.body }, 'Repository details');
     config.enterpriseVersion =
       res.headers && (res.headers['x-github-enterprise-version'] as string);
@@ -232,7 +242,7 @@ export async function initRepo({
         const renovateConfig = JSON.parse(
           Buffer.from(
             (
-              await api.get(
+              await githubApi.getJson<{ content: string }>(
                 `repos/${config.repository}/contents/${defaultConfigFile}`
               )
             ).body.content,
@@ -265,7 +275,7 @@ export async function initRepo({
         renovateConfig = JSON.parse(
           Buffer.from(
             (
-              await api.get(
+              await githubApi.getJson<{ content: string }>(
                 `repos/${config.repository}/contents/${defaultConfigFile}`
               )
             ).body.content,
@@ -344,16 +354,22 @@ export async function initRepo({
     config.repository = null;
     // Get list of existing repos
     const existingRepos = (
-      await api.get<{ full_name: string }[]>('user/repos?per_page=100', {
-        token: forkToken || opts.token,
-        paginate: true,
-      })
+      await githubApi.getJson<{ full_name: string }[]>(
+        'user/repos?per_page=100',
+        {
+          token: forkToken || opts.token,
+          paginate: true,
+        }
+      )
     ).body.map((r) => r.full_name);
     try {
       config.repository = (
-        await api.post(`repos/${repository}/forks`, {
-          token: forkToken || opts.token,
-        })
+        await githubApi.postJson<{ full_name: string }>(
+          `repos/${repository}/forks`,
+          {
+            token: forkToken || opts.token,
+          }
+        )
       ).body.full_name;
     } catch (err) /* istanbul ignore next */ {
       logger.debug({ err }, 'Error forking repository');
@@ -372,7 +388,7 @@ export async function initRepo({
       // This is a lovely "hack" by GitHub that lets us force update our fork's master
       // with the base commit from the parent repository
       try {
-        await api.patch(
+        await githubApi.patchJson(
           `repos/${config.repository}/git/refs/heads/${config.baseBranch}`,
           {
             body: {
@@ -576,7 +592,6 @@ async function getClosedPrs(): Promise<PrList> {
     config.closedPrList = {};
     let query;
     try {
-      const url = 'graphql';
       // prettier-ignore
       query = `
       query {
@@ -598,21 +613,18 @@ async function getClosedPrs(): Promise<PrList> {
         }
       }
       `;
-      const options = {
-        body: JSON.stringify({ query }),
-        json: false,
-      };
-      const res = JSON.parse((await api.post(url, options)).body);
+      const nodes = await githubApi.getGraphqlNodes<any>(
+        query,
+        'pullRequests',
+        { paginate: false }
+      );
       const prNumbers: number[] = [];
       // istanbul ignore if
-      if (!res.data) {
-        logger.debug(
-          { query, res },
-          'No graphql res.data, returning empty list'
-        );
+      if (!nodes?.length) {
+        logger.debug({ query }, 'No graphql data, returning empty list');
         return {};
       }
-      for (const pr of res.data.repository.pullRequests.nodes) {
+      for (const pr of nodes) {
         // https://developer.github.com/v4/object/pullrequest/
         pr.displayNumber = `Pull Request #${pr.number}`;
         pr.state = pr.state.toLowerCase();
@@ -649,11 +661,6 @@ async function getOpenPrs(): Promise<PrList> {
     config.openPrList = {};
     let query;
     try {
-      const url = 'graphql';
-      // https://developer.github.com/v4/previews/#mergeinfopreview---more-detailed-information-about-a-pull-requests-merge-state
-      const headers = {
-        accept: 'application/vnd.github.merge-info-preview+json',
-      };
       // prettier-ignore
       query = `
       query {
@@ -702,19 +709,18 @@ async function getOpenPrs(): Promise<PrList> {
         }
       }
       `;
-      const options = {
-        headers,
-        body: JSON.stringify({ query }),
-        json: false,
-      };
-      const res = JSON.parse((await api.post(url, options)).body);
+      const nodes = await githubApi.getGraphqlNodes<any>(
+        query,
+        'pullRequests',
+        { paginate: false }
+      );
       const prNumbers: number[] = [];
       // istanbul ignore if
-      if (!res.data) {
-        logger.debug({ query, res }, 'No graphql res.data');
+      if (!nodes?.length) {
+        logger.debug({ query }, 'No graphql res.data');
         return {};
       }
-      for (const pr of res.data.repository.pullRequests.nodes) {
+      for (const pr of nodes) {
         // https://developer.github.com/v4/object/pullrequest/
         pr.displayNumber = `Pull Request #${pr.number}`;
         pr.state = PR_STATE_OPEN;
@@ -816,12 +822,14 @@ export async function getPr(prNo: number): Promise<Pr | null> {
   if (!prNo) {
     return null;
   }
-  const openPr = (await getOpenPrs())[prNo];
+  const openPrs = await getOpenPrs();
+  const openPr = openPrs[prNo];
   if (openPr) {
     logger.debug('Returning from graphql open PR list');
     return openPr;
   }
-  const closedPr = (await getClosedPrs())[prNo];
+  const closedPrs = await getClosedPrs();
+  const closedPr = closedPrs[prNo];
   if (closedPr) {
     logger.debug('Returning from graphql closed PR list');
     return closedPr;
@@ -831,7 +839,7 @@ export async function getPr(prNo: number): Promise<Pr | null> {
     'PR not found in open or closed PRs list - trying to fetch it directly'
   );
   const pr = (
-    await api.get(
+    await githubApi.getJson<any>(
       `repos/${config.parentRepo || config.repository}/pulls/${prNo}`
     )
   ).body;
@@ -858,7 +866,7 @@ export async function getPr(prNo: number): Promise<Pr | null> {
       if (global.gitAuthor) {
         // Check against gitAuthor
         const commitAuthorEmail = (
-          await api.get(
+          await githubApi.getJson<{ commit: { author: { email } } }[]>(
             `repos/${
               config.parentRepo || config.repository
             }/pulls/${prNo}/commits`
@@ -892,7 +900,9 @@ export async function getPr(prNo: number): Promise<Pr | null> {
       // Check if only one author of all commits
       logger.debug({ prNo }, 'Checking all commits');
       const prCommits = (
-        await api.get(
+        await githubApi.getJson<
+          { committer: { login: string }; commit: { message: string } }[]
+        >(
           `repos/${
             config.parentRepo || config.repository
           }/pulls/${prNo}/commits`
@@ -900,10 +910,7 @@ export async function getPr(prNo: number): Promise<Pr | null> {
       ).body;
       // Filter out "Update branch" presses
       const remainingCommits = prCommits.filter(
-        (commit: {
-          committer: { login: string };
-          commit: { message: string };
-        }) => {
+        (commit: { committer; commit }) => {
           const isWebflow =
             commit.committer && commit.committer.login === 'web-flow';
           if (!isWebflow) {
@@ -950,7 +957,15 @@ export async function getPrList(): Promise<Pr[]> {
     logger.debug('Retrieving PR list');
     let res;
     try {
-      res = await api.get(
+      res = await githubApi.getJson<{
+        number: number;
+        head: { ref: string; sha: string; repo: { full_name: string } };
+        title: string;
+        state: string;
+        merged_at: string;
+        created_at: string;
+        closed_at: string;
+      }>(
         `repos/${
           config.parentRepo || config.repository
         }/pulls?per_page=100&state=all`,
@@ -960,30 +975,19 @@ export async function getPrList(): Promise<Pr[]> {
       logger.debug({ err }, 'getPrList err');
       throw new Error('platform-failure');
     }
-    config.prList = res.body.map(
-      (pr: {
-        number: number;
-        head: { ref: string; sha: string; repo: { full_name: string } };
-        title: string;
-        state: string;
-        merged_at: string;
-        created_at: string;
-        closed_at: string;
-      }) => ({
-        number: pr.number,
-        branchName: pr.head.ref,
-        sha: pr.head.sha,
-        title: pr.title,
-        state:
-          pr.state === PR_STATE_CLOSED && pr.merged_at && pr.merged_at.length
-            ? /* istanbul ignore next */ 'merged'
-            : pr.state,
-        createdAt: pr.created_at,
-        closed_at: pr.closed_at,
-        sourceRepo:
-          pr.head && pr.head.repo ? pr.head.repo.full_name : undefined,
-      })
-    );
+    config.prList = res.body.map((pr) => ({
+      number: pr.number,
+      branchName: pr.head.ref,
+      sha: pr.head.sha,
+      title: pr.title,
+      state:
+        pr.state === PR_STATE_CLOSED && pr.merged_at && pr.merged_at.length
+          ? /* istanbul ignore next */ 'merged'
+          : pr.state,
+      createdAt: pr.created_at,
+      closed_at: pr.closed_at,
+      sourceRepo: pr.head && pr.head.repo ? pr.head.repo.full_name : undefined,
+    }));
     logger.debug(`Retrieved ${config.prList.length} Pull Requests`);
   }
   return config.prList;
@@ -1027,7 +1031,9 @@ async function getStatus(
     branchName
   )}/status`;
 
-  return (await api.get(commitStatusUrl, { useCache })).body;
+  return (
+    await githubApi.getJson<CombinedBranchStatus>(commitStatusUrl, { useCache })
+  ).body;
 }
 
 // Returns the combined status for a branch.
@@ -1074,15 +1080,17 @@ export async function getBranchStatus(
           Accept: 'application/vnd.github.antiope-preview+json',
         },
       };
-      const checkRunsRaw = (await api.get(checkRunsUrl, opts)).body;
+      const checkRunsRaw = (
+        await githubApi.getJson<{
+          check_runs: { name: string; status: string; conclusion: string }[];
+        }>(checkRunsUrl, opts)
+      ).body;
       if (checkRunsRaw.check_runs && checkRunsRaw.check_runs.length) {
-        checkRuns = checkRunsRaw.check_runs.map(
-          (run: { name: string; status: string; conclusion: string }) => ({
-            name: run.name,
-            status: run.status,
-            conclusion: run.conclusion,
-          })
-        );
+        checkRuns = checkRunsRaw.check_runs.map((run) => ({
+          name: run.name,
+          status: run.status,
+          conclusion: run.conclusion,
+        }));
         logger.debug({ checkRuns }, 'check runs result');
       } else {
         // istanbul ignore next
@@ -1136,7 +1144,7 @@ async function getStatusCheck(
 
   const url = `repos/${config.repository}/commits/${branchCommit}/statuses`;
 
-  return (await api.get(url, { useCache })).body;
+  return (await githubApi.getJson<GhBranchStatus[]>(url, { useCache })).body;
 }
 
 const githubToRenovateStatusMapping = {
@@ -1202,7 +1210,7 @@ export async function setBranchStatus({
     if (targetUrl) {
       options.target_url = targetUrl;
     }
-    await api.post(url, { body: options });
+    await githubApi.postJson(url, { body: options });
 
     // update status cache
     await getStatus(branchName, false);
@@ -1237,7 +1245,7 @@ async function getIssues(): Promise<Issue[]> {
     }
   `;
 
-  const result = await getGraphqlNodes<Issue>(query, 'issues');
+  const result = await githubApi.getGraphqlNodes<Issue>(query, 'issues');
 
   logger.debug(`Retrieved ${result.length} issues`);
   return result.map((issue) => ({
@@ -1264,7 +1272,7 @@ export async function findIssue(title: string): Promise<Issue | null> {
   }
   logger.debug('Found issue ' + issue.number);
   const issueBody = (
-    await api.get(
+    await githubApi.getJson<{ body: string }>(
       `repos/${config.parentRepo || config.repository}/issues/${issue.number}`
     )
   ).body.body;
@@ -1276,7 +1284,7 @@ export async function findIssue(title: string): Promise<Issue | null> {
 
 async function closeIssue(issueNumber: number): Promise<void> {
   logger.debug(`closeIssue(${issueNumber})`);
-  await api.patch(
+  await githubApi.patchJson(
     `repos/${config.parentRepo || config.repository}/issues/${issueNumber}`,
     {
       body: { state: 'closed' },
@@ -1314,7 +1322,7 @@ export async function ensureIssue({
         }
       }
       const issueBody = (
-        await api.get(
+        await githubApi.getJson<{ body: string }>(
           `repos/${config.parentRepo || config.repository}/issues/${
             issue.number
           }`
@@ -1326,7 +1334,7 @@ export async function ensureIssue({
       }
       if (shouldReOpen) {
         logger.debug('Patching issue');
-        await api.patch(
+        await githubApi.patchJson(
           `repos/${config.parentRepo || config.repository}/issues/${
             issue.number
           }`,
@@ -1338,12 +1346,15 @@ export async function ensureIssue({
         return 'updated';
       }
     }
-    await api.post(`repos/${config.parentRepo || config.repository}/issues`, {
-      body: {
-        title,
-        body,
-      },
-    });
+    await githubApi.postJson(
+      `repos/${config.parentRepo || config.repository}/issues`,
+      {
+        body: {
+          title,
+          body,
+        },
+      }
+    );
     logger.info('Issue created');
     // reset issueList so that it will be fetched again as-needed
     delete config.issueList;
@@ -1381,7 +1392,7 @@ export async function addAssignees(
 ): Promise<void> {
   logger.debug(`Adding assignees ${assignees} to #${issueNo}`);
   const repository = config.parentRepo || config.repository;
-  await api.post(`repos/${repository}/issues/${issueNo}/assignees`, {
+  await githubApi.postJson(`repos/${repository}/issues/${issueNo}/assignees`, {
     body: {
       assignees,
     },
@@ -1399,7 +1410,7 @@ export async function addReviewers(
     .filter((e) => e.startsWith('team:'))
     .map((e) => e.replace(/^team:/, ''));
   try {
-    await api.post(
+    await githubApi.postJson(
       `repos/${
         config.parentRepo || config.repository
       }/pulls/${prNo}/requested_reviewers`,
@@ -1422,7 +1433,7 @@ async function addLabels(
   logger.debug(`Adding labels ${labels} to #${issueNo}`);
   const repository = config.parentRepo || config.repository;
   if (is.array(labels) && labels.length) {
-    await api.post(`repos/${repository}/issues/${issueNo}/labels`, {
+    await githubApi.postJson(`repos/${repository}/issues/${issueNo}/labels`, {
       body: labels,
     });
   }
@@ -1435,7 +1446,9 @@ export async function deleteLabel(
   logger.debug(`Deleting label ${label} from #${issueNo}`);
   const repository = config.parentRepo || config.repository;
   try {
-    await api.delete(`repos/${repository}/issues/${issueNo}/labels/${label}`);
+    await githubApi.deleteJson(
+      `repos/${repository}/issues/${issueNo}/labels/${label}`
+    );
   } catch (err) /* istanbul ignore next */ {
     logger.warn({ err, issueNo, label }, 'Failed to delete label');
   }
@@ -1443,7 +1456,7 @@ export async function deleteLabel(
 
 async function addComment(issueNo: number, body: string): Promise<void> {
   // POST /repos/:owner/:repo/issues/:number/comments
-  await api.post(
+  await githubApi.postJson(
     `repos/${
       config.parentRepo || config.repository
     }/issues/${issueNo}/comments`,
@@ -1455,7 +1468,7 @@ async function addComment(issueNo: number, body: string): Promise<void> {
 
 async function editComment(commentId: number, body: string): Promise<void> {
   // PATCH /repos/:owner/:repo/issues/comments/:id
-  await api.patch(
+  await githubApi.patchJson(
     `repos/${
       config.parentRepo || config.repository
     }/issues/comments/${commentId}`,
@@ -1467,7 +1480,7 @@ async function editComment(commentId: number, body: string): Promise<void> {
 
 async function deleteComment(commentId: number): Promise<void> {
   // DELETE /repos/:owner/:repo/issues/comments/:id
-  await api.delete(
+  await githubApi.deleteJson(
     `repos/${
       config.parentRepo || config.repository
     }/issues/comments/${commentId}`
@@ -1487,7 +1500,7 @@ async function getComments(issueNo: number): Promise<Comment[]> {
   }/issues/${issueNo}/comments?per_page=100`;
   try {
     const comments = (
-      await api.get<Comment[]>(url, {
+      await githubApi.getJson<Comment[]>(url, {
         paginate: true,
       })
     ).body;
@@ -1622,7 +1635,7 @@ export async function createPr({
   }
   logger.debug({ title, head, base }, 'Creating PR');
   const pr = (
-    await api.post<GhPr>(
+    await githubApi.postJson<GhPr>(
       `repos/${config.parentRepo || config.repository}/pulls`,
       options
     )
@@ -1656,11 +1669,11 @@ export async function getPrFiles(prNo: number): Promise<string[]> {
     return [];
   }
   const files = (
-    await api.get(
+    await githubApi.getJson<{ filename: string }[]>(
       `repos/${config.parentRepo || config.repository}/pulls/${prNo}/files`
     )
   ).body;
-  return files.map((f: { filename: string }) => f.filename);
+  return files.map((f) => f.filename);
 }
 
 export async function updatePr(
@@ -1682,7 +1695,7 @@ export async function updatePr(
     options.token = config.forkToken;
   }
   try {
-    await api.patch(
+    await githubApi.patchJson(
       `repos/${config.parentRepo || config.repository}/pulls/${prNo}`,
       options
     );
@@ -1715,9 +1728,11 @@ export async function mergePr(
       'Branch protection: Attempting to merge PR when PR reviews are enabled'
     );
     const repository = config.parentRepo || config.repository;
-    const reviews = await api.get(`repos/${repository}/pulls/${prNo}/reviews`);
+    const reviews = await githubApi.getJson<{ state: string }[]>(
+      `repos/${repository}/pulls/${prNo}/reviews`
+    );
     const isApproved = reviews.body.some(
-      (review: { state: string }) => review.state === 'APPROVED'
+      (review) => review.state === 'APPROVED'
     );
     if (!isApproved) {
       logger.debug(
@@ -1740,7 +1755,7 @@ export async function mergePr(
     options.body.merge_method = config.mergeMethod;
     try {
       logger.debug({ options, url }, `mergePr`);
-      await api.put(url, options);
+      await githubApi.putJson(url, options);
       automerged = true;
     } catch (err) {
       if (err.statusCode === 404 || err.statusCode === 405) {
@@ -1760,7 +1775,7 @@ export async function mergePr(
     options.body.merge_method = 'rebase';
     try {
       logger.debug({ options, url }, `mergePr`);
-      await api.put(url, options);
+      await githubApi.putJson(url, options);
     } catch (err1) {
       logger.debug(
         { err: err1 },
@@ -1769,7 +1784,7 @@ export async function mergePr(
       try {
         options.body.merge_method = 'squash';
         logger.debug({ options, url }, `mergePr`);
-        await api.put(url, options);
+        await githubApi.putJson(url, options);
       } catch (err2) {
         logger.debug(
           { err: err2 },
@@ -1778,7 +1793,7 @@ export async function mergePr(
         try {
           options.body.merge_method = 'merge';
           logger.debug({ options, url }, `mergePr`);
-          await api.put(url, options);
+          await githubApi.putJson(url, options);
         } catch (err3) {
           logger.debug(
             { err: err3 },
@@ -1811,10 +1826,6 @@ export function getPrBody(input: string): string {
 }
 
 export async function getVulnerabilityAlerts(): Promise<VulnerabilityAlert[]> {
-  const headers = {
-    accept: 'application/vnd.github.vixen-preview+json',
-  };
-  const url = 'graphql';
   // prettier-ignore
   const query = `
   query {
@@ -1842,18 +1853,18 @@ export async function getVulnerabilityAlerts(): Promise<VulnerabilityAlert[]> {
       }
     }
   }`;
-  const options = {
-    headers,
-    body: JSON.stringify({ query }),
-    json: false,
-  };
   let alerts = [];
   try {
-    const res = JSON.parse((await api.post(url, options)).body);
-    if (res?.data?.repository?.vulnerabilityAlerts) {
-      alerts = res.data.repository.vulnerabilityAlerts.edges.map(
-        (edge: { node: any }) => edge.node
-      );
+    const vulnerabilityAlerts = await githubApi.getGraphqlNodes<{ node: any }>(
+      query,
+      'vulnerabilityAlerts',
+      {
+        paginate: false,
+        acceptHeader: 'application/vnd.github.vixen-preview+json',
+      }
+    );
+    if (vulnerabilityAlerts?.length) {
+      alerts = vulnerabilityAlerts.map((edge) => edge.node);
       if (alerts.length) {
         logger.debug({ alerts }, 'Found GitHub vulnerability alerts');
       }
diff --git a/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap b/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap
index 426d781813..568032b650 100644
--- a/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap
+++ b/lib/workers/pr/changelog/__snapshots__/index.spec.ts.snap
@@ -51,6 +51,110 @@ Object {
 }
 `;
 
+exports[`workers/pr/changelog getChangeLogJSON filters unnecessary warns 2`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/chalk/chalk/tags?per_page=100",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/chalk/chalk/contents/",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/chalk/chalk/releases?per_page=100",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/chalk/chalk/contents/",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/chalk/chalk/releases?per_page=100",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/chalk/chalk/contents/",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/chalk/chalk/releases?per_page=100",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/chalk/chalk/contents/",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/chalk/chalk/releases?per_page=100",
+  },
+]
+`;
+
 exports[`workers/pr/changelog getChangeLogJSON supports github enterprise and github enterprise changelog 1`] = `
 Object {
   "hasReleaseNotes": true,
@@ -102,6 +206,44 @@ Object {
 }
 `;
 
+exports[`workers/pr/changelog getChangeLogJSON supports github enterprise and github enterprise changelog 2`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "github-enterprise.example.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://github-enterprise.example.com/repos/chalk/chalk/tags?per_page=100",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "github-enterprise.example.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://github-enterprise.example.com/repos/chalk/chalk/contents/",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "github-enterprise.example.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://github-enterprise.example.com/repos/chalk/chalk/releases?per_page=100",
+  },
+]
+`;
+
 exports[`workers/pr/changelog getChangeLogJSON supports github enterprise and github.com changelog 1`] = `
 Object {
   "hasReleaseNotes": true,
@@ -153,6 +295,44 @@ Object {
 }
 `;
 
+exports[`workers/pr/changelog getChangeLogJSON supports github enterprise and github.com changelog 2`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/chalk/chalk/tags?per_page=100",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/chalk/chalk/contents/",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/chalk/chalk/releases?per_page=100",
+  },
+]
+`;
+
 exports[`workers/pr/changelog getChangeLogJSON supports github.com and github enterprise changelog 1`] = `
 Object {
   "hasReleaseNotes": true,
@@ -204,6 +384,44 @@ Object {
 }
 `;
 
+exports[`workers/pr/changelog getChangeLogJSON supports github.com and github enterprise changelog 2`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "github-enterprise.example.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://github-enterprise.example.com/repos/chalk/chalk/tags?per_page=100",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "github-enterprise.example.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://github-enterprise.example.com/repos/chalk/chalk/contents/",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "github-enterprise.example.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://github-enterprise.example.com/repos/chalk/chalk/releases?per_page=100",
+  },
+]
+`;
+
 exports[`workers/pr/changelog getChangeLogJSON supports node engines 1`] = `
 Object {
   "hasReleaseNotes": true,
@@ -310,6 +528,44 @@ Object {
 }
 `;
 
+exports[`workers/pr/changelog getChangeLogJSON uses GitHub tags 2`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/chalk/chalk/tags?per_page=100",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/chalk/chalk/contents/",
+  },
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate",
+      "authorization": "token abc",
+      "host": "api.github.com",
+      "user-agent": "https://github.com/renovatebot/renovate",
+    },
+    "method": "GET",
+    "url": "https://api.github.com/repos/chalk/chalk/releases?per_page=100",
+  },
+]
+`;
+
 exports[`workers/pr/changelog getChangeLogJSON works without Github 1`] = `
 Object {
   "hasReleaseNotes": true,
diff --git a/lib/workers/pr/changelog/index.spec.ts b/lib/workers/pr/changelog/index.spec.ts
index 7ad98cb885..6a06a73c2e 100644
--- a/lib/workers/pr/changelog/index.spec.ts
+++ b/lib/workers/pr/changelog/index.spec.ts
@@ -1,17 +1,17 @@
-import { mocked, partial } from '../../../../test/util';
+import * as httpMock from '../../../../test/httpMock';
+import { partial } from '../../../../test/util';
 import { PLATFORM_TYPE_GITHUB } from '../../../constants/platforms';
-import { api } from '../../../platform/github/gh-got-wrapper';
 import * as globalCache from '../../../util/cache/global';
+import { clear } from '../../../util/cache/run';
 import * as runCache from '../../../util/cache/run';
 import * as hostRules from '../../../util/host-rules';
 import * as semverVersioning from '../../../versioning/semver';
 import { BranchConfig } from '../../common';
 import { ChangeLogError, getChangeLogJSON } from '.';
 
-jest.mock('../../../platform/github/gh-got-wrapper');
 jest.mock('../../../datasource/npm');
 
-const ghGot = mocked(api).get;
+const githubApiHost = 'https://api.github.com';
 
 const upgrade: BranchConfig = partial<BranchConfig>({
   endpoint: 'https://api.github.com/',
@@ -37,7 +37,7 @@ const upgrade: BranchConfig = partial<BranchConfig>({
 describe('workers/pr/changelog', () => {
   describe('getChangeLogJSON', () => {
     beforeEach(async () => {
-      ghGot.mockClear();
+      httpMock.setup();
       hostRules.clear();
       hostRules.add({
         hostType: PLATFORM_TYPE_GITHUB,
@@ -47,25 +47,34 @@ describe('workers/pr/changelog', () => {
       await globalCache.rmAll();
       runCache.clear();
     });
+
+    afterEach(() => {
+      clear();
+      httpMock.reset();
+    });
+
     it('returns null if @types', async () => {
+      httpMock.scope(githubApiHost);
       expect(
         await getChangeLogJSON({
           ...upgrade,
           fromVersion: null,
         })
       ).toBeNull();
-      expect(ghGot).toHaveBeenCalledTimes(0);
+      expect(httpMock.getTrace()).toHaveLength(0);
     });
     it('returns null if no fromVersion', async () => {
+      httpMock.scope(githubApiHost);
       expect(
         await getChangeLogJSON({
           ...upgrade,
           sourceUrl: 'https://github.com/DefinitelyTyped/DefinitelyTyped',
         })
       ).toBeNull();
-      expect(ghGot).toHaveBeenCalledTimes(0);
+      expect(httpMock.getTrace()).toHaveLength(0);
     });
     it('returns null if fromVersion equals toVersion', async () => {
+      httpMock.scope(githubApiHost);
       expect(
         await getChangeLogJSON({
           ...upgrade,
@@ -73,50 +82,61 @@ describe('workers/pr/changelog', () => {
           toVersion: '1.0.0',
         })
       ).toBeNull();
-      expect(ghGot).toHaveBeenCalledTimes(0);
+      expect(httpMock.getTrace()).toHaveLength(0);
     });
     it('skips invalid repos', async () => {
+      httpMock.scope(githubApiHost);
       expect(
         await getChangeLogJSON({
           ...upgrade,
           sourceUrl: 'https://github.com/about',
         })
       ).toBeNull();
+      expect(httpMock.getTrace()).toHaveLength(0);
     });
     it('works without Github', async () => {
+      httpMock.scope(githubApiHost);
       expect(
         await getChangeLogJSON({
           ...upgrade,
         })
       ).toMatchSnapshot();
+      expect(httpMock.getTrace()).toHaveLength(0);
     });
     it('uses GitHub tags', async () => {
-      ghGot.mockResolvedValueOnce({
-        body: [
+      httpMock
+        .scope(githubApiHost)
+        .get('/repos/chalk/chalk/tags?per_page=100')
+        .reply(200, [
           { name: '0.9.0' },
           { name: '1.0.0' },
           { name: '1.4.0' },
           { name: 'v2.3.0' },
           { name: '2.2.2' },
           { name: 'v2.4.2' },
-        ],
-      } as never);
+        ])
+        .persist()
+        .get(/.*/)
+        .reply(200, []);
       expect(
         await getChangeLogJSON({
           ...upgrade,
         })
       ).toMatchSnapshot();
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('filters unnecessary warns', async () => {
-      ghGot.mockImplementation(() => {
-        throw new Error('Unknown Github Repo');
+      httpMock
+        .scope(githubApiHost)
+        .persist()
+        .get(/.*/)
+        .replyWithError('Unknown Github Repo');
+      const res = await getChangeLogJSON({
+        ...upgrade,
+        depName: '@renovate/no',
       });
-      expect(
-        await getChangeLogJSON({
-          ...upgrade,
-          depName: '@renovate/no',
-        })
-      ).toMatchSnapshot();
+      expect(res).toMatchSnapshot();
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('supports node engines', async () => {
       expect(
@@ -167,6 +187,7 @@ describe('workers/pr/changelog', () => {
       ).toBeNull();
     });
     it('supports github enterprise and github.com changelog', async () => {
+      httpMock.scope(githubApiHost).persist().get(/.*/).reply(200, []);
       hostRules.add({
         hostType: PLATFORM_TYPE_GITHUB,
         token: 'super_secret',
@@ -178,21 +199,14 @@ describe('workers/pr/changelog', () => {
           endpoint: 'https://github-enterprise.example.com/',
         })
       ).toMatchSnapshot();
-      expect(ghGot).toHaveBeenNthCalledWith(
-        1,
-        'https://api.github.com/repos/chalk/chalk/tags?per_page=100',
-        { paginate: true }
-      );
-      expect(ghGot).toHaveBeenNthCalledWith(
-        2,
-        'https://api.github.com/repos/chalk/chalk/contents/'
-      );
-      expect(ghGot).toHaveBeenNthCalledWith(
-        3,
-        'https://api.github.com/repos/chalk/chalk/releases?per_page=100'
-      );
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
     it('supports github enterprise and github enterprise changelog', async () => {
+      httpMock
+        .scope('https://github-enterprise.example.com')
+        .persist()
+        .get(/.*/)
+        .reply(200, []);
       hostRules.add({
         hostType: PLATFORM_TYPE_GITHUB,
         baseUrl: 'https://github-enterprise.example.com/',
@@ -206,22 +220,15 @@ describe('workers/pr/changelog', () => {
           endpoint: 'https://github-enterprise.example.com/',
         })
       ).toMatchSnapshot();
-      expect(ghGot).toHaveBeenNthCalledWith(
-        1,
-        'https://github-enterprise.example.com/repos/chalk/chalk/tags?per_page=100',
-        { paginate: true }
-      );
-      expect(ghGot).toHaveBeenNthCalledWith(
-        2,
-        'https://github-enterprise.example.com/repos/chalk/chalk/contents/'
-      );
-      expect(ghGot).toHaveBeenNthCalledWith(
-        3,
-        'https://github-enterprise.example.com/repos/chalk/chalk/releases?per_page=100'
-      );
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
 
     it('supports github.com and github enterprise changelog', async () => {
+      httpMock
+        .scope('https://github-enterprise.example.com')
+        .persist()
+        .get(/.*/)
+        .reply(200, []);
       hostRules.add({
         hostType: PLATFORM_TYPE_GITHUB,
         baseUrl: 'https://github-enterprise.example.com/',
@@ -233,19 +240,7 @@ describe('workers/pr/changelog', () => {
           sourceUrl: 'https://github-enterprise.example.com/chalk/chalk',
         })
       ).toMatchSnapshot();
-      expect(ghGot).toHaveBeenNthCalledWith(
-        1,
-        'https://github-enterprise.example.com/repos/chalk/chalk/tags?per_page=100',
-        { paginate: true }
-      );
-      expect(ghGot).toHaveBeenNthCalledWith(
-        2,
-        'https://github-enterprise.example.com/repos/chalk/chalk/contents/'
-      );
-      expect(ghGot).toHaveBeenNthCalledWith(
-        3,
-        'https://github-enterprise.example.com/repos/chalk/chalk/releases?per_page=100'
-      );
+      expect(httpMock.getTrace()).toMatchSnapshot();
     });
   });
 });
diff --git a/lib/workers/pr/changelog/release-notes.ts b/lib/workers/pr/changelog/release-notes.ts
index dd2da999f1..c42a15063d 100644
--- a/lib/workers/pr/changelog/release-notes.ts
+++ b/lib/workers/pr/changelog/release-notes.ts
@@ -4,15 +4,15 @@ import { linkify } from 'linkify-markdown';
 import MarkdownIt from 'markdown-it';
 
 import { logger } from '../../../logger';
-import { api } from '../../../platform/github/gh-got-wrapper';
 import * as globalCache from '../../../util/cache/global';
+import { GithubHttp } from '../../../util/http/github';
 import { ChangeLogNotes, ChangeLogResult } from './common';
 
-const { get: ghGot } = api;
-
 const markdown = new MarkdownIt('zero');
 markdown.enable(['heading', 'lheading']);
 
+const http = new GithubHttp();
+
 export async function getReleaseList(
   apiBaseUrl: string,
   repository: string
@@ -25,7 +25,7 @@ export async function getReleaseList(
   try {
     let url = apiBaseUrl.replace(/\/?$/, '/');
     url += `repos/${repository}/releases?per_page=100`;
-    const res = await ghGot<
+    const res = await http.getJson<
       {
         html_url: string;
         id: number;
@@ -161,7 +161,7 @@ export async function getReleaseNotesMd(
     let apiPrefix = apiBaseUrl.replace(/\/?$/, '/');
 
     apiPrefix += `repos/${repository}/contents/`;
-    const filesRes = await ghGot<{ name: string }[]>(apiPrefix);
+    const filesRes = await http.getJson<{ name: string }[]>(apiPrefix);
     const files = filesRes.body
       .map((f) => f.name)
       .filter((f) => changelogFilenameRegex.test(f));
@@ -176,7 +176,7 @@ export async function getReleaseNotesMd(
         `Multiple candidates for changelog file, using ${changelogFile}`
       );
     }
-    const fileRes = await ghGot<{ content: string }>(
+    const fileRes = await http.getJson<{ content: string }>(
       `${apiPrefix}/${changelogFile}`
     );
     changelogMd =
diff --git a/lib/workers/pr/changelog/source-github.ts b/lib/workers/pr/changelog/source-github.ts
index 70d8c0efa4..629a55cb17 100644
--- a/lib/workers/pr/changelog/source-github.ts
+++ b/lib/workers/pr/changelog/source-github.ts
@@ -2,15 +2,15 @@ import URL from 'url';
 import { PLATFORM_TYPE_GITHUB } from '../../../constants/platforms';
 import { Release } from '../../../datasource';
 import { logger } from '../../../logger';
-import { api } from '../../../platform/github/gh-got-wrapper';
 import * as globalCache from '../../../util/cache/global';
 import * as hostRules from '../../../util/host-rules';
+import { GithubHttp } from '../../../util/http/github';
 import * as allVersioning from '../../../versioning';
 import { BranchUpgradeConfig } from '../../common';
 import { ChangeLogError, ChangeLogRelease, ChangeLogResult } from './common';
 import { addReleaseNotes } from './release-notes';
 
-const { get: ghGot } = api;
+const http = new GithubHttp();
 
 async function getTags(
   endpoint: string,
@@ -18,7 +18,7 @@ async function getTags(
 ): Promise<string[]> {
   const url = `${endpoint}repos/${repository}/tags?per_page=100`;
   try {
-    const res = await ghGot<{ name: string }[]>(url, {
+    const res = await http.getJson<{ name: string }[]>(url, {
       paginate: true,
     });
 
-- 
GitLab