From 012561f69afe0baebc689b045df7a1c65b31a567 Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Sun, 30 Aug 2020 22:03:58 +0200
Subject: [PATCH] feat(git): get branch commit without cloning (#7130)

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
---
 lib/manager/npm/post-update/index.ts          |   2 +-
 lib/platform/azure/index.spec.ts              |   2 +-
 lib/platform/azure/index.ts                   |   2 +-
 lib/platform/bitbucket-server/index.spec.ts   |   6 +-
 lib/platform/bitbucket-server/index.ts        |  12 +-
 lib/platform/bitbucket/index.spec.ts          |   2 +-
 lib/platform/bitbucket/index.ts               |   2 +-
 lib/platform/gitea/index.spec.ts              |   2 +-
 lib/platform/gitea/index.ts                   |   4 +-
 lib/platform/github/index.spec.ts             |   4 +-
 lib/platform/github/index.ts                  |   6 +-
 lib/platform/gitlab/index.spec.ts             |   6 +-
 lib/platform/gitlab/index.ts                  |  10 +-
 lib/util/git/__snapshots__/index.spec.ts.snap |  10 --
 lib/util/git/index.spec.ts                    |  63 ++++-------
 lib/util/git/index.ts                         | 104 ++++--------------
 lib/workers/branch/index.spec.ts              |  66 +++++------
 lib/workers/branch/index.ts                   |   2 +-
 lib/workers/branch/lock-files/index.spec.ts   |   2 +-
 lib/workers/branch/reuse.spec.ts              |  24 ++--
 lib/workers/branch/reuse.ts                   |   2 +-
 lib/workers/repository/finalise/prune.spec.ts |  28 ++---
 lib/workers/repository/finalise/prune.ts      |   6 +-
 lib/workers/repository/process/index.spec.ts  |   8 +-
 lib/workers/repository/process/index.ts       |   4 +-
 lib/workers/repository/process/limits.spec.ts |  10 +-
 lib/workers/repository/process/limits.ts      |   8 +-
 27 files changed, 159 insertions(+), 238 deletions(-)

diff --git a/lib/manager/npm/post-update/index.ts b/lib/manager/npm/post-update/index.ts
index 33e5302d76..0a8b3e1de0 100644
--- a/lib/manager/npm/post-update/index.ts
+++ b/lib/manager/npm/post-update/index.ts
@@ -359,7 +359,7 @@ export async function getAdditionalFiles(
   if (
     config.updateType === 'lockFileMaintenance' &&
     config.reuseExistingBranch &&
-    (await branchExists(config.branchName))
+    branchExists(config.branchName)
   ) {
     logger.debug('Skipping lockFileMaintenance update');
     return { artifactErrors, updatedArtifacts };
diff --git a/lib/platform/azure/index.spec.ts b/lib/platform/azure/index.spec.ts
index 134c13e94f..0ad83f30b1 100644
--- a/lib/platform/azure/index.spec.ts
+++ b/lib/platform/azure/index.spec.ts
@@ -24,7 +24,7 @@ describe('platform/azure', () => {
     azureApi = require('./azure-got-wrapper');
     azureHelper = require('./azure-helper');
     git = require('../../util/git');
-    git.branchExists.mockResolvedValue(true);
+    git.branchExists.mockReturnValue(true);
     git.isBranchStale.mockResolvedValue(false);
     hostRules.find.mockReturnValue({
       token: 'token',
diff --git a/lib/platform/azure/index.ts b/lib/platform/azure/index.ts
index 33e8458b53..c527dd7d5f 100644
--- a/lib/platform/azure/index.ts
+++ b/lib/platform/azure/index.ts
@@ -149,7 +149,7 @@ export async function initRepo({
   const url =
     defaults.endpoint +
     `${encodeURIComponent(projectName)}/_git/${encodeURIComponent(repoName)}`;
-  git.initRepo({
+  await git.initRepo({
     ...config,
     localDir,
     url,
diff --git a/lib/platform/bitbucket-server/index.spec.ts b/lib/platform/bitbucket-server/index.spec.ts
index b7354b5894..0b063b1e36 100644
--- a/lib/platform/bitbucket-server/index.spec.ts
+++ b/lib/platform/bitbucket-server/index.spec.ts
@@ -184,9 +184,9 @@ describe(getName(__filename), () => {
         hostRules = require('../../util/host-rules');
         bitbucket = await import('.');
         git = require('../../util/git');
-        git.branchExists.mockResolvedValue(true);
+        git.branchExists.mockReturnValue(true);
         git.isBranchStale.mockResolvedValue(false);
-        git.getBranchCommit.mockResolvedValue(
+        git.getBranchCommit.mockReturnValue(
           '0d9c7726c3d628b7e28af234595cfd20febdbf8e'
         );
         const endpoint =
@@ -1646,7 +1646,7 @@ Followed by some information.
         });
 
         it('throws repository-changed', async () => {
-          git.branchExists.mockResolvedValue(false);
+          git.branchExists.mockReturnValue(false);
           await initRepo();
           await expect(
             bitbucket.getBranchStatus('somebranch', [])
diff --git a/lib/platform/bitbucket-server/index.ts b/lib/platform/bitbucket-server/index.ts
index 487c56c50f..f26061594f 100644
--- a/lib/platform/bitbucket-server/index.ts
+++ b/lib/platform/bitbucket-server/index.ts
@@ -177,7 +177,7 @@ export async function initRepo({
     repository,
   });
 
-  git.initRepo({
+  await git.initRepo({
     ...config,
     localDir,
     url: gitUrl,
@@ -365,7 +365,7 @@ async function getStatus(
   branchName: string,
   useCache = true
 ): Promise<utils.BitbucketCommitStatus> {
-  const branchCommit = await git.getBranchCommit(branchName);
+  const branchCommit = git.getBranchCommit(branchName);
 
   return (
     await bitbucketServerHttp.getJson<utils.BitbucketCommitStatus>(
@@ -394,7 +394,7 @@ export async function getBranchStatus(
     return BranchStatus.green;
   }
 
-  if (!(await git.branchExists(branchName))) {
+  if (!git.branchExists(branchName)) {
     throw new Error(REPOSITORY_CHANGED);
   }
 
@@ -418,11 +418,11 @@ export async function getBranchStatus(
   }
 }
 
-async function getStatusCheck(
+function getStatusCheck(
   branchName: string,
   useCache = true
 ): Promise<utils.BitbucketStatus[]> {
-  const branchCommit = await git.getBranchCommit(branchName);
+  const branchCommit = git.getBranchCommit(branchName);
 
   return utils.accumulateValues(
     `./rest/build-status/1.0/commits/${branchCommit}`,
@@ -475,7 +475,7 @@ export async function setBranchStatus({
   }
   logger.debug({ branch: branchName, context, state }, 'Setting branch status');
 
-  const branchCommit = await git.getBranchCommit(branchName);
+  const branchCommit = git.getBranchCommit(branchName);
 
   try {
     const body: any = {
diff --git a/lib/platform/bitbucket/index.spec.ts b/lib/platform/bitbucket/index.spec.ts
index b5400c74b7..44c291b43b 100644
--- a/lib/platform/bitbucket/index.spec.ts
+++ b/lib/platform/bitbucket/index.spec.ts
@@ -52,7 +52,7 @@ describe('platform/bitbucket', () => {
     bitbucket = await import('.');
     logger = (await import('../../logger')).logger as any;
     git = require('../../util/git');
-    git.branchExists.mockResolvedValue(true);
+    git.branchExists.mockReturnValue(true);
     git.isBranchStale.mockResolvedValue(false);
     // clean up hostRules
     hostRules.clear();
diff --git a/lib/platform/bitbucket/index.ts b/lib/platform/bitbucket/index.ts
index 86e1134f18..9c169c6b24 100644
--- a/lib/platform/bitbucket/index.ts
+++ b/lib/platform/bitbucket/index.ts
@@ -157,7 +157,7 @@ export async function initRepo({
     repository,
   });
 
-  git.initRepo({
+  await git.initRepo({
     ...config,
     localDir,
     url,
diff --git a/lib/platform/gitea/index.spec.ts b/lib/platform/gitea/index.spec.ts
index 8e786a34f3..7ef78ebc29 100644
--- a/lib/platform/gitea/index.spec.ts
+++ b/lib/platform/gitea/index.spec.ts
@@ -158,7 +158,7 @@ describe('platform/gitea', () => {
     logger = (await import('../../logger')).logger as any;
     gitvcs = require('../../util/git');
     gitvcs.isBranchStale.mockResolvedValue(false);
-    gitvcs.getBranchCommit.mockResolvedValue(mockCommitHash);
+    gitvcs.getBranchCommit.mockReturnValue(mockCommitHash);
 
     global.gitAuthor = { name: 'Renovate', email: 'renovate@example.com' };
 
diff --git a/lib/platform/gitea/index.ts b/lib/platform/gitea/index.ts
index 56e8eb350d..b76cdd85d1 100644
--- a/lib/platform/gitea/index.ts
+++ b/lib/platform/gitea/index.ts
@@ -301,7 +301,7 @@ const platform: Platform = {
     gitEndpoint.auth = opts.token;
 
     // Initialize Git storage
-    git.initRepo({
+    await git.initRepo({
       ...config,
       url: URL.format(gitEndpoint),
       gitAuthorName: global.gitAuthor?.name,
@@ -339,7 +339,7 @@ const platform: Platform = {
   }: BranchStatusConfig): Promise<void> {
     try {
       // Create new status for branch commit
-      const branchCommit = await git.getBranchCommit(branchName);
+      const branchCommit = git.getBranchCommit(branchName);
       await helper.createCommitStatus(config.repository, branchCommit, {
         state: helper.renovateToGiteaStatusMapping[state] || 'pending',
         context,
diff --git a/lib/platform/github/index.spec.ts b/lib/platform/github/index.spec.ts
index 6f23e1b9f8..ea9c07c292 100644
--- a/lib/platform/github/index.spec.ts
+++ b/lib/platform/github/index.spec.ts
@@ -26,9 +26,9 @@ describe('platform/github', () => {
     hostRules = mocked(await import('../../util/host-rules'));
     jest.mock('../../util/git');
     git = mocked(await import('../../util/git'));
-    git.branchExists.mockResolvedValue(true);
+    git.branchExists.mockReturnValue(true);
     git.isBranchStale.mockResolvedValue(true);
-    git.getBranchCommit.mockResolvedValue(
+    git.getBranchCommit.mockReturnValue(
       '0d9c7726c3d628b7e28af234595cfd20febdbf8e'
     );
     delete global.gitAuthor;
diff --git a/lib/platform/github/index.ts b/lib/platform/github/index.ts
index fc4e7e8af4..464965f1af 100644
--- a/lib/platform/github/index.ts
+++ b/lib/platform/github/index.ts
@@ -401,7 +401,7 @@ export async function initRepo({
   );
   parsedEndpoint.pathname = config.repository + '.git';
   const url = URL.format(parsedEndpoint);
-  git.initRepo({
+  await git.initRepo({
     ...config,
     url,
     gitAuthorName: global.gitAuthor?.name,
@@ -898,7 +898,7 @@ async function getStatusCheck(
   branchName: string,
   useCache = true
 ): Promise<GhBranchStatus[]> {
-  const branchCommit = await git.getBranchCommit(branchName);
+  const branchCommit = git.getBranchCommit(branchName);
 
   const url = `repos/${config.repository}/commits/${branchCommit}/statuses`;
 
@@ -953,7 +953,7 @@ export async function setBranchStatus({
   }
   logger.debug({ branch: branchName, context, state }, 'Setting branch status');
   try {
-    const branchCommit = await git.getBranchCommit(branchName);
+    const branchCommit = git.getBranchCommit(branchName);
     const url = `repos/${config.repository}/statuses/${branchCommit}`;
     const renovateToGitHubStateMapping = {
       green: 'success',
diff --git a/lib/platform/gitlab/index.spec.ts b/lib/platform/gitlab/index.spec.ts
index 804934013a..b5bc54e26c 100644
--- a/lib/platform/gitlab/index.spec.ts
+++ b/lib/platform/gitlab/index.spec.ts
@@ -29,9 +29,9 @@ describe('platform/gitlab', () => {
     hostRules = require('../../util/host-rules');
     jest.mock('../../util/git');
     git = require('../../util/git');
-    git.branchExists.mockResolvedValue(true);
+    git.branchExists.mockReturnValue(true);
     git.isBranchStale.mockResolvedValue(true);
-    git.getBranchCommit.mockResolvedValue(
+    git.getBranchCommit.mockReturnValue(
       '0d9c7726c3d628b7e28af234595cfd20febdbf8e'
     );
     hostRules.find.mockReturnValue({
@@ -464,7 +464,7 @@ describe('platform/gitlab', () => {
     });
     it('throws repository-changed', async () => {
       expect.assertions(2);
-      git.branchExists.mockResolvedValue(false);
+      git.branchExists.mockReturnValue(false);
       await initRepo();
       await expect(gitlab.getBranchStatus('somebranch', [])).rejects.toThrow(
         REPOSITORY_CHANGED
diff --git a/lib/platform/gitlab/index.ts b/lib/platform/gitlab/index.ts
index 16fac4a0a3..ec70b09e69 100644
--- a/lib/platform/gitlab/index.ts
+++ b/lib/platform/gitlab/index.ts
@@ -211,7 +211,7 @@ export async function initRepo({
       repoUrl.auth = 'oauth2:' + opts.token;
       url = URL.format(repoUrl);
     }
-    git.initRepo({
+    await git.initRepo({
       ...config,
       url,
       gitAuthorName: global.gitAuthor?.name,
@@ -260,7 +260,7 @@ async function getStatus(
   branchName: string,
   useCache = true
 ): Promise<GitlabBranchStatus[]> {
-  const branchSha = await git.getBranchCommit(branchName);
+  const branchSha = git.getBranchCommit(branchName);
   const url = `projects/${config.repository}/repository/commits/${branchSha}/statuses`;
 
   return (
@@ -298,7 +298,7 @@ export async function getBranchStatus(
     return BranchStatus.red;
   }
 
-  if (!(await git.branchExists(branchName))) {
+  if (!git.branchExists(branchName)) {
     throw new Error(REPOSITORY_CHANGED);
   }
 
@@ -505,7 +505,7 @@ export function getPrBody(input: string): string {
 export async function getBranchPr(branchName: string): Promise<Pr> {
   logger.debug(`getBranchPr(${branchName})`);
   // istanbul ignore if
-  if (!(await git.branchExists(branchName))) {
+  if (!git.branchExists(branchName)) {
     return null;
   }
   const query = new URLSearchParams({
@@ -553,7 +553,7 @@ export async function setBranchStatus({
   url: targetUrl,
 }: BranchStatusConfig): Promise<void> {
   // First, get the branch commit SHA
-  const branchSha = await git.getBranchCommit(branchName);
+  const branchSha = git.getBranchCommit(branchName);
   // Now, check the statuses for that commit
   const url = `projects/${config.repository}/statuses/${branchSha}`;
   let state = 'success';
diff --git a/lib/util/git/__snapshots__/index.spec.ts.snap b/lib/util/git/__snapshots__/index.spec.ts.snap
index fe56ac8e3e..4f03a4baee 100644
--- a/lib/util/git/__snapshots__/index.spec.ts.snap
+++ b/lib/util/git/__snapshots__/index.spec.ts.snap
@@ -1,9 +1,5 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`platform/git checkoutBranch(branchName) should throw if branch does not exist 1`] = `[Error: config-validation]`;
-
-exports[`platform/git getBranchCommit(branchName) should throw if branch does not exist 1`] = `[Error: Cannot fetch commit for branch that does not exist: not_found]`;
-
 exports[`platform/git getBranchFiles(branchName) detects changed files compared to current base branch 1`] = `
 Array [
   "some-new-file",
@@ -17,8 +13,6 @@ Array [
 ]
 `;
 
-exports[`platform/git getFile(filePath, branchName) returns null for 404 1`] = `[Error: repository-changed]`;
-
 exports[`platform/git getFileList() should exclude submodules 1`] = `
 Array [
   ".gitmodules",
@@ -49,7 +43,3 @@ Array [
   "master message",
 ]
 `;
-
-exports[`platform/git isBranchModified() should throw if branch does not exist 1`] = `[Error: Cannot check modification for branch that does not exist: not_found]`;
-
-exports[`platform/git isBranchStale() should throw if branch does not exist 1`] = `[Error: Cannot check staleness for branch that does not exist: not_found]`;
diff --git a/lib/util/git/index.spec.ts b/lib/util/git/index.spec.ts
index 971ec39d8f..04e1b95c94 100644
--- a/lib/util/git/index.spec.ts
+++ b/lib/util/git/index.spec.ts
@@ -62,7 +62,7 @@ describe('platform/git', () => {
     const repo = Git(origin.path);
     await repo.clone(base.path, '.', ['--bare']);
     tmpDir = await tmp.dir({ unsafeCleanup: true });
-    git.initRepo({
+    await git.initRepo({
       localDir: tmpDir.path,
       url: origin.path,
       extraCloneOpts: {
@@ -91,11 +91,7 @@ describe('platform/git', () => {
     it('sets non-master base branch', async () => {
       await expect(git.checkoutBranch('develop')).resolves.not.toThrow();
     });
-    it('should throw if branch does not exist', async () => {
-      await expect(git.checkoutBranch('not_found')).rejects.toMatchSnapshot();
-    });
   });
-
   describe('getFileList()', () => {
     it('should return the correct files', async () => {
       expect(await git.getFileList()).toMatchSnapshot();
@@ -104,7 +100,7 @@ describe('platform/git', () => {
       const repo = Git(base.path).silent(true);
       await repo.submoduleAdd(base.path, 'submodule');
       await repo.commit('Add submodule');
-      git.initRepo({
+      await git.initRepo({
         localDir: tmpDir.path,
         url: base.path,
       });
@@ -115,20 +111,19 @@ describe('platform/git', () => {
     });
   });
   describe('branchExists(branchName)', () => {
-    it('should return true if found', async () => {
-      expect(await git.branchExists('renovate/future_branch')).toBe(true);
-      expect(await git.branchExists('renovate/future_branch')).toBe(true); // should come from cache
+    it('should return true if found', () => {
+      expect(git.branchExists('renovate/future_branch')).toBe(true);
     });
-    it('should return false if not found', async () => {
-      expect(await git.branchExists('not_found')).toBe(false);
+    it('should return false if not found', () => {
+      expect(git.branchExists('not_found')).toBe(false);
     });
   });
-  describe('getAllRenovateBranches()', () => {
-    it('should return all renovate branches', async () => {
-      const res = await git.getAllRenovateBranches('renovate/');
+  describe('getBranchList()', () => {
+    it('should return all branches', () => {
+      const res = git.getBranchList();
       expect(res).toContain('renovate/past_branch');
       expect(res).toContain('renovate/future_branch');
-      expect(res).not.toContain('master');
+      expect(res).toContain('master');
     });
   });
   describe('isBranchStale()', () => {
@@ -138,14 +133,8 @@ describe('platform/git', () => {
     it('should return true if SHA different from master', async () => {
       expect(await git.isBranchStale('renovate/past_branch')).toBe(true);
     });
-    it('should throw if branch does not exist', async () => {
-      await expect(git.isBranchStale('not_found')).rejects.toMatchSnapshot();
-    });
   });
   describe('isBranchModified()', () => {
-    it('should throw if branch does not exist', async () => {
-      await expect(git.isBranchModified('not_found')).rejects.toMatchSnapshot();
-    });
     it('should return true when author matches', async () => {
       expect(await git.isBranchModified('renovate/future_branch')).toBe(false);
       expect(await git.isBranchModified('renovate/future_branch')).toBe(false);
@@ -156,13 +145,13 @@ describe('platform/git', () => {
   });
 
   describe('getBranchCommit(branchName)', () => {
-    it('should return same value for equal refs', async () => {
-      const hex = await git.getBranchCommit('renovate/equal_branch');
-      expect(hex).toBe(await git.getBranchCommit('master'));
+    it('should return same value for equal refs', () => {
+      const hex = git.getBranchCommit('renovate/equal_branch');
+      expect(hex).toBe(git.getBranchCommit('master'));
       expect(hex).toHaveLength(40);
     });
-    it('should throw if branch does not exist', async () => {
-      await expect(git.getBranchCommit('not_found')).rejects.toMatchSnapshot();
+    it('should return null', () => {
+      expect(git.getBranchCommit('not_found')).toBeNull();
     });
   });
 
@@ -225,9 +214,7 @@ describe('platform/git', () => {
       expect(res).toBeNull();
     });
     it('returns null for 404', async () => {
-      await expect(
-        git.getFile('some-path', 'some-branch')
-      ).rejects.toMatchSnapshot();
+      expect(await git.getFile('some-path', 'some-branch')).toBeNull();
     });
   });
   describe('commitFiles({branchName, files, message})', () => {
@@ -352,18 +339,18 @@ describe('platform/git', () => {
       await repo.commit('past message2');
       await repo.checkout('master');
 
-      expect(await git.branchExists('test')).toBeFalsy();
+      expect(git.branchExists('test')).toBeFalsy();
 
       expect(await git.getCommitMessages()).toMatchSnapshot();
 
       await git.checkoutBranch('develop');
 
-      git.initRepo({
+      await git.initRepo({
         localDir: tmpDir.path,
         url: base.path,
       });
 
-      expect(await git.branchExists('test')).toBeTruthy();
+      expect(git.branchExists('test')).toBeTruthy();
 
       await git.checkoutBranch('test');
 
@@ -380,16 +367,15 @@ describe('platform/git', () => {
       await repo.commit('past message2');
       await repo.checkout('master');
 
-      git.initRepo({
+      await git.initRepo({
         localDir: tmpDir.path,
         url: base.path,
       });
 
       await git.setBranchPrefix('renovate/');
-      expect(await git.branchExists('renovate/test')).toBe(true);
-      const cid = await git.getBranchCommit('renovate/test');
+      expect(git.branchExists('renovate/test')).toBe(true);
 
-      git.initRepo({
+      await git.initRepo({
         localDir: tmpDir.path,
         url: base.path,
       });
@@ -398,8 +384,7 @@ describe('platform/git', () => {
       await repo.commit('past message3', ['--amend']);
 
       await git.setBranchPrefix('renovate/');
-      expect(await git.branchExists('renovate/test')).toBe(true);
-      expect(await git.getBranchCommit('renovate/test')).not.toEqual(cid);
+      expect(git.branchExists('renovate/test')).toBe(true);
     });
 
     it('should fail clone ssh submodule', async () => {
@@ -418,7 +403,7 @@ describe('platform/git', () => {
         'test',
       ]);
       await repo.commit('Add submodule');
-      git.initRepo({
+      await git.initRepo({
         localDir: tmpDir.path,
         url: base.path,
       });
diff --git a/lib/util/git/index.ts b/lib/util/git/index.ts
index a442df648f..cea22cc2bc 100644
--- a/lib/util/git/index.ts
+++ b/lib/util/git/index.ts
@@ -8,7 +8,6 @@ import Git, {
   StatusResult as StatusResult_,
 } from 'simple-git';
 import {
-  CONFIG_VALIDATION,
   REPOSITORY_CHANGED,
   REPOSITORY_EMPTY,
   REPOSITORY_TEMPORARY_ERROR,
@@ -44,7 +43,7 @@ interface StorageConfig {
 interface LocalConfig extends StorageConfig {
   currentBranch: string;
   currentBranchSha: string;
-  branchExists: Record<string, boolean>;
+  branchCommits: Record<string, CommitSha>;
   branchIsModified: Record<string, boolean>;
   branchPrefix: string;
 }
@@ -76,14 +75,6 @@ function localName(branchName: string): string {
   return branchName.replace(/^origin\//, '');
 }
 
-function throwBranchValidationError(branchName: string): never {
-  const error = new Error(CONFIG_VALIDATION);
-  error.validationError = 'branch not found';
-  error.validationMessage =
-    'The following branch could not be found: ' + branchName;
-  throw error;
-}
-
 async function isDirectory(dir: string): Promise<boolean> {
   try {
     return (await fs.stat(dir)).isDirectory();
@@ -117,12 +108,23 @@ let gitInitialized: boolean;
 
 let privateKeySet = false;
 
-export function initRepo(args: StorageConfig): void {
+async function fetchBranchCommits(): Promise<void> {
+  config.branchCommits = {};
+  (await git.listRemote(['--heads', config.url]))
+    .split('\n')
+    .filter(Boolean)
+    .map((line) => line.trim().split(/\s+/))
+    .forEach(([sha, ref]) => {
+      config.branchCommits[ref.replace('refs/heads/', '')] = sha;
+    });
+}
+
+export async function initRepo(args: StorageConfig): Promise<void> {
   config = { ...args } as any;
-  config.branchExists = {};
   config.branchIsModified = {};
   git = Git(config.localDir).silent(true);
   gitInitialized = false;
+  await fetchBranchCommits();
 }
 
 async function resetToBranch(branchName: string): Promise<void> {
@@ -290,40 +292,19 @@ async function syncBranch(branchName: string): Promise<void> {
     try {
       await git.raw(['remote', 'set-branches', '--add', 'origin', branchName]);
       await git.fetch(['origin', branchName, '--depth=2']);
-    } catch (err) {
+    } catch (err) /* istanbul ignore next */ {
       checkForPlatformFailure(err);
     }
   }
 }
 
-export async function branchExists(branchName: string): Promise<boolean> {
-  await syncGit();
-  // First check cache
-  if (config.branchExists[branchName] !== undefined) {
-    return config.branchExists[branchName];
-  }
-  await syncBranch(branchName);
-  try {
-    await git.raw(['show-branch', 'origin/' + branchName]);
-    config.branchExists[branchName] = true;
-    return true;
-  } catch (err) {
-    checkForPlatformFailure(err);
-    config.branchExists[branchName] = false;
-    return false;
-  }
+export function branchExists(branchName: string): boolean {
+  return !!config.branchCommits[branchName];
 }
 
 // Return the commit SHA for a branch
-export async function getBranchCommit(branchName: string): Promise<CommitSha> {
-  await syncGit();
-  if (!(await branchExists(branchName))) {
-    throw Error(
-      'Cannot fetch commit for branch that does not exist: ' + branchName
-    );
-  }
-  const res = await git.revparse(['origin/' + branchName]);
-  return res.trim();
+export function getBranchCommit(branchName: string): CommitSha | null {
+  return config.branchCommits[branchName] || null;
 }
 
 export async function getCommitMessages(): Promise<string[]> {
@@ -338,10 +319,8 @@ export async function getCommitMessages(): Promise<string[]> {
 
 export async function checkoutBranch(branchName: string): Promise<CommitSha> {
   await syncGit();
-  if (!(await branchExists(branchName))) {
-    throwBranchValidationError(branchName);
-  }
   logger.debug(`Setting current branch to ${branchName}`);
+  await syncBranch(branchName);
   try {
     config.currentBranch = branchName;
     config.currentBranchSha = (
@@ -356,14 +335,6 @@ export async function checkoutBranch(branchName: string): Promise<CommitSha> {
     return config.currentBranchSha;
   } catch (err) /* istanbul ignore next */ {
     checkForPlatformFailure(err);
-    if (
-      err.message.includes(
-        'unknown revision or path not in the working tree'
-      ) ||
-      err.message.includes('did not match any file(s) known to git')
-    ) {
-      throwBranchValidationError(branchName);
-    }
     throw err;
   }
 }
@@ -387,27 +358,12 @@ export async function getFileList(): Promise<string[]> {
     );
 }
 
-export async function getAllRenovateBranches(
-  branchPrefix: string
-): Promise<string[]> {
-  // istanbul ignore if
-  if (!gitInitialized) {
-    logger.debug('git is uninitialized so returning empty branch set');
-    return [];
-  }
-  const branches = await git.branch(['--remotes', '--verbose']);
-  return branches.all
-    .map(localName)
-    .filter((branchName) => branchName.startsWith(branchPrefix));
+export function getBranchList(): string[] {
+  return Object.keys(config.branchCommits);
 }
 
 export async function isBranchStale(branchName: string): Promise<boolean> {
   await syncGit();
-  if (!(await branchExists(branchName))) {
-    throw Error(
-      'Cannot check staleness for branch that does not exist: ' + branchName
-    );
-  }
   const branches = await git.branch([
     '--remotes',
     '--verbose',
@@ -423,11 +379,6 @@ export async function isBranchModified(branchName: string): Promise<boolean> {
   if (config.branchIsModified[branchName] !== undefined) {
     return config.branchIsModified[branchName];
   }
-  if (!(await branchExists(branchName))) {
-    throw Error(
-      'Cannot check modification for branch that does not exist: ' + branchName
-    );
-  }
   // Retrieve the author of the most recent commit
   const lastAuthor = (
     await git.raw(['log', '-1', '--pretty=format:%ae', `origin/${branchName}`])
@@ -466,7 +417,7 @@ export async function deleteBranch(branchName: string): Promise<void> {
     checkForPlatformFailure(err);
     logger.debug({ branchName }, 'No local branch to delete');
   }
-  config.branchExists[branchName] = false;
+  delete config.branchCommits[branchName];
 }
 
 export async function mergeBranch(branchName: string): Promise<void> {
@@ -508,13 +459,6 @@ export async function getFile(
   branchName?: string
 ): Promise<string | null> {
   await syncGit();
-  if (branchName) {
-    const exists = await branchExists(branchName);
-    if (!exists) {
-      logger.debug({ branchName }, 'branch no longer exists - aborting');
-      throw new Error(REPOSITORY_CHANGED);
-    }
-  }
   try {
     const content = await git.show([
       'origin/' + (branchName || config.currentBranch) + ':' + filePath,
@@ -630,7 +574,7 @@ export async function commitFiles({
     // Fetch it after create
     const ref = `refs/heads/${branchName}:refs/remotes/origin/${branchName}`;
     await git.fetch(['origin', ref, '--depth=2', '--force']);
-    config.branchExists[branchName] = true;
+    config.branchCommits[branchName] = commit;
     config.branchIsModified[branchName] = false;
     limits.incrementLimit('prCommitsPerRunLimit');
     return commit;
diff --git a/lib/workers/branch/index.spec.ts b/lib/workers/branch/index.spec.ts
index 43329a15d8..8288816800 100644
--- a/lib/workers/branch/index.spec.ts
+++ b/lib/workers/branch/index.spec.ts
@@ -79,7 +79,7 @@ describe('workers/branch', () => {
     it('skips branch if not scheduled and not updating out of schedule', async () => {
       schedule.isScheduledNow.mockReturnValueOnce(false);
       config.updateNotScheduled = false;
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       const res = await branchWorker.processBranch(config);
       expect(res).toEqual('not-scheduled');
     });
@@ -88,7 +88,7 @@ describe('workers/branch', () => {
       config.unpublishSafe = true;
       config.canBeUnpublished = true;
       config.prCreation = 'not-pending';
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       const res = await branchWorker.processBranch(config);
       expect(res).toEqual('pending');
     });
@@ -107,7 +107,7 @@ describe('workers/branch', () => {
     it('processes branch if not scheduled but updating out of schedule', async () => {
       schedule.isScheduledNow.mockReturnValueOnce(false);
       config.updateNotScheduled = true;
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         state: PrState.Open,
       } as never);
@@ -117,7 +117,7 @@ describe('workers/branch', () => {
     });
     it('skips branch if closed major PR found', async () => {
       schedule.isScheduledNow.mockReturnValueOnce(false);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       config.updateType = 'major';
       checkExisting.prAlreadyExisted.mockResolvedValueOnce({
         number: 13,
@@ -128,7 +128,7 @@ describe('workers/branch', () => {
     });
     it('skips branch if closed digest PR found', async () => {
       schedule.isScheduledNow.mockReturnValueOnce(false);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       config.updateType = 'digest';
       checkExisting.prAlreadyExisted.mockResolvedValueOnce({
         number: 13,
@@ -139,7 +139,7 @@ describe('workers/branch', () => {
     });
     it('skips branch if closed minor PR found', async () => {
       schedule.isScheduledNow.mockReturnValueOnce(false);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       checkExisting.prAlreadyExisted.mockResolvedValueOnce({
         number: 13,
         state: PrState.Closed,
@@ -149,7 +149,7 @@ describe('workers/branch', () => {
     });
     it('skips branch if merged PR found', async () => {
       schedule.isScheduledNow.mockReturnValueOnce(false);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       checkExisting.prAlreadyExisted.mockResolvedValueOnce({
         number: 13,
         state: PrState.Merged,
@@ -159,7 +159,7 @@ describe('workers/branch', () => {
     });
     it('throws error if closed PR found', async () => {
       schedule.isScheduledNow.mockReturnValueOnce(false);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         state: PrState.Merged,
       } as never);
@@ -170,7 +170,7 @@ describe('workers/branch', () => {
     });
     it('does not skip branch if edited PR found with rebaseLabel', async () => {
       schedule.isScheduledNow.mockReturnValueOnce(false);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         state: PrState.Open,
         labels: ['rebase'],
@@ -181,7 +181,7 @@ describe('workers/branch', () => {
     });
     it('skips branch if edited PR found', async () => {
       schedule.isScheduledNow.mockReturnValueOnce(false);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         state: PrState.Open,
         body: '**Rebasing**: something',
@@ -192,7 +192,7 @@ describe('workers/branch', () => {
     });
     it('skips branch if target branch changed', async () => {
       schedule.isScheduledNow.mockReturnValueOnce(false);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         state: PrState.Open,
         targetBranch: 'v6',
@@ -210,7 +210,7 @@ describe('workers/branch', () => {
         artifactErrors: [],
         updatedArtifacts: [],
       });
-      git.branchExists.mockResolvedValue(false);
+      git.branchExists.mockReturnValue(false);
       expect(await branchWorker.processBranch(config, true)).toEqual(
         'pr-limit-reached'
       );
@@ -223,7 +223,7 @@ describe('workers/branch', () => {
         artifactErrors: [],
         updatedArtifacts: [],
       });
-      git.branchExists.mockResolvedValue(true);
+      git.branchExists.mockReturnValue(true);
       prWorker.ensurePr.mockResolvedValueOnce({
         prResult: PrResult.LimitReached,
       });
@@ -239,7 +239,7 @@ describe('workers/branch', () => {
         artifactErrors: [],
         updatedArtifacts: [],
       });
-      git.branchExists.mockResolvedValue(false);
+      git.branchExists.mockReturnValue(false);
       expect(await branchWorker.processBranch(config, false, true)).toEqual(
         'commit-limit-reached'
       );
@@ -252,7 +252,7 @@ describe('workers/branch', () => {
         artifactErrors: [],
         updatedArtifacts: [],
       });
-      git.branchExists.mockResolvedValueOnce(false);
+      git.branchExists.mockReturnValueOnce(false);
       commit.commitFilesToBranch.mockResolvedValueOnce(null);
       expect(await branchWorker.processBranch(config)).toEqual('no-work');
     });
@@ -264,7 +264,7 @@ describe('workers/branch', () => {
         artifactErrors: [],
         updatedArtifacts: [{}],
       } as never);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       commit.commitFilesToBranch.mockResolvedValueOnce(null);
       automerge.tryBranchAutomerge.mockResolvedValueOnce('automerged');
       await branchWorker.processBranch(config);
@@ -281,7 +281,7 @@ describe('workers/branch', () => {
         artifactErrors: [],
         updatedArtifacts: [{}],
       } as never);
-      git.branchExists.mockResolvedValueOnce(false);
+      git.branchExists.mockReturnValueOnce(false);
       automerge.tryBranchAutomerge.mockResolvedValueOnce('automerged');
       await branchWorker.processBranch({
         ...config,
@@ -300,7 +300,7 @@ describe('workers/branch', () => {
         artifactErrors: [],
         updatedArtifacts: [{}],
       } as never);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       commit.commitFilesToBranch.mockResolvedValueOnce(null);
       automerge.tryBranchAutomerge.mockResolvedValueOnce('automerged');
       await branchWorker.processBranch({ ...config, dryRun: true });
@@ -316,7 +316,7 @@ describe('workers/branch', () => {
         artifactErrors: [],
         updatedArtifacts: [{}],
       } as never);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       commit.commitFilesToBranch.mockResolvedValueOnce(null);
       automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
       prWorker.ensurePr.mockResolvedValueOnce({
@@ -335,7 +335,7 @@ describe('workers/branch', () => {
         artifactErrors: [],
         updatedArtifacts: [{}],
       } as never);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       commit.commitFilesToBranch.mockResolvedValueOnce(null);
       automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
       prWorker.ensurePr.mockResolvedValueOnce({
@@ -371,7 +371,7 @@ describe('workers/branch', () => {
         artifactErrors: [],
         updatedArtifacts: [{}],
       } as never);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
       prWorker.ensurePr.mockResolvedValueOnce({
         result: PrResult.Created,
@@ -392,7 +392,7 @@ describe('workers/branch', () => {
         artifactErrors: [{}],
         updatedArtifacts: [{}],
       } as never);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
       prWorker.ensurePr.mockResolvedValueOnce({
         result: PrResult.Created,
@@ -413,7 +413,7 @@ describe('workers/branch', () => {
         artifactErrors: [{}],
         updatedArtifacts: [{}],
       } as never);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
       prWorker.ensurePr.mockResolvedValueOnce({
         result: PrResult.Created,
@@ -435,7 +435,7 @@ describe('workers/branch', () => {
         artifactErrors: [{}],
         updatedArtifacts: [{}],
       } as never);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
       prWorker.ensurePr.mockResolvedValueOnce({
         result: PrResult.Created,
@@ -457,7 +457,7 @@ describe('workers/branch', () => {
         artifactErrors: [{}],
         updatedArtifacts: [{}],
       } as never);
-      git.branchExists.mockResolvedValueOnce(false);
+      git.branchExists.mockReturnValueOnce(false);
       automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
       prWorker.ensurePr.mockResolvedValueOnce({
         result: PrResult.Created,
@@ -478,7 +478,7 @@ describe('workers/branch', () => {
         updatedArtifacts: [{}],
       } as never);
       config.recreateClosed = true;
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       automerge.tryBranchAutomerge.mockResolvedValueOnce('failed');
       prWorker.ensurePr.mockResolvedValueOnce({
         result: PrResult.Created,
@@ -517,7 +517,7 @@ describe('workers/branch', () => {
         artifactErrors: [],
         updatedArtifacts: [{}],
       } as never);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       automerge.tryBranchAutomerge.mockResolvedValueOnce(false as never);
       prWorker.ensurePr.mockImplementationOnce(() => {
         throw new Error('some error');
@@ -527,7 +527,7 @@ describe('workers/branch', () => {
     });
 
     it('closed pr (dry run)', async () => {
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       checkExisting.prAlreadyExisted.mockResolvedValueOnce({
         state: PrState.Closed,
       });
@@ -537,7 +537,7 @@ describe('workers/branch', () => {
     });
 
     it('branch pr no rebase (dry run)', async () => {
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         state: PrState.Open,
       } as never);
@@ -556,7 +556,7 @@ describe('workers/branch', () => {
         artifactErrors: [],
         updatedArtifacts: [{}],
       } as never);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         title: 'rebase!',
         state: PrState.Open,
@@ -586,7 +586,7 @@ describe('workers/branch', () => {
         artifactErrors: [],
         updatedArtifacts: [{}],
       } as never);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         title: 'rebase!',
         state: PrState.Open,
@@ -617,7 +617,7 @@ describe('workers/branch', () => {
         artifactErrors: [],
         updatedArtifacts: [{}],
       } as never);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         title: 'rebase!',
         state: PrState.Open,
@@ -654,7 +654,7 @@ describe('workers/branch', () => {
           },
         ],
       } as never);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         title: 'rebase!',
         state: PrState.Open,
diff --git a/lib/workers/branch/index.ts b/lib/workers/branch/index.ts
index 10e7f3bed8..cf95ac21bb 100644
--- a/lib/workers/branch/index.ts
+++ b/lib/workers/branch/index.ts
@@ -67,7 +67,7 @@ export async function processBranch(
   );
   logger.trace({ config }, 'branch config');
   await checkoutBranch(config.baseBranch);
-  const branchExists = await gitBranchExists(config.branchName);
+  const branchExists = gitBranchExists(config.branchName);
   const branchPr = await platform.getBranchPr(config.branchName);
   logger.debug(`branchExists=${branchExists}`);
   const dependencyDashboardCheck = (config.dependencyDashboardChecks || {})[
diff --git a/lib/workers/branch/lock-files/index.spec.ts b/lib/workers/branch/lock-files/index.spec.ts
index 9963de3e3f..dfbfffdfff 100644
--- a/lib/workers/branch/lock-files/index.spec.ts
+++ b/lib/workers/branch/lock-files/index.spec.ts
@@ -105,7 +105,7 @@ describe('manager/npm/post-update', () => {
     it('returns no error and empty lockfiles if lock file maintenance exists', async () => {
       config.updateType = 'lockFileMaintenance';
       config.reuseExistingBranch = true;
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       const res = await getAdditionalFiles(config, { npm: [{}] });
       expect(res).toMatchSnapshot();
       expect(res.artifactErrors).toHaveLength(0);
diff --git a/lib/workers/branch/reuse.spec.ts b/lib/workers/branch/reuse.spec.ts
index d8fd9a5da5..f9b77d45c5 100644
--- a/lib/workers/branch/reuse.spec.ts
+++ b/lib/workers/branch/reuse.spec.ts
@@ -22,18 +22,18 @@ describe('workers/branch/parent', () => {
       };
     });
     it('returns undefined if branch does not exist', async () => {
-      git.branchExists.mockResolvedValueOnce(false);
+      git.branchExists.mockReturnValueOnce(false);
       const res = await shouldReuseExistingBranch(config);
       expect(res.reuseExistingBranch).toBe(false);
     });
     it('returns branchName if no PR', async () => {
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockReturnValue(null);
       const res = await shouldReuseExistingBranch(config);
       expect(res.reuseExistingBranch).toBe(true);
     });
     it('returns branchName if does not need rebaseing', async () => {
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         ...pr,
         isConflicted: false,
@@ -42,7 +42,7 @@ describe('workers/branch/parent', () => {
       expect(res.reuseExistingBranch).toBe(true);
     });
     it('returns branchName if unmergeable and cannot rebase', async () => {
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         ...pr,
         isConflicted: true,
@@ -53,7 +53,7 @@ describe('workers/branch/parent', () => {
     });
     it('returns branchName if unmergeable and can rebase, but rebaseWhen is never', async () => {
       config.rebaseWhen = 'never';
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         ...pr,
         isConflicted: true,
@@ -63,7 +63,7 @@ describe('workers/branch/parent', () => {
       expect(res.reuseExistingBranch).toBe(true);
     });
     it('returns undefined if PR title rebase!', async () => {
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         ...pr,
         title: 'rebase!Update foo to v4',
@@ -72,7 +72,7 @@ describe('workers/branch/parent', () => {
       expect(res.reuseExistingBranch).toBe(false);
     });
     it('returns undefined if PR body check rebase', async () => {
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         ...pr,
         title: 'Update foo to v4',
@@ -82,7 +82,7 @@ describe('workers/branch/parent', () => {
       expect(res.reuseExistingBranch).toBe(false);
     });
     it('aaa2 returns undefined if manual rebase by label', async () => {
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         ...pr,
         labels: ['rebase'],
@@ -91,7 +91,7 @@ describe('workers/branch/parent', () => {
       expect(res.reuseExistingBranch).toBe(false);
     });
     it('aaa1 returns undefined if unmergeable and can rebase', async () => {
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         ...pr,
         isConflicted: true,
@@ -103,21 +103,21 @@ describe('workers/branch/parent', () => {
     it('returns branchName if automerge branch and not stale', async () => {
       config.automerge = true;
       config.automergeType = 'branch';
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       const res = await shouldReuseExistingBranch(config);
       expect(res.reuseExistingBranch).toBe(true);
     });
     it('returns undefined if automerge branch and stale', async () => {
       config.automerge = true;
       config.automergeType = 'branch';
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       git.isBranchStale.mockResolvedValueOnce(true);
       const res = await shouldReuseExistingBranch(config);
       expect(res.reuseExistingBranch).toBe(false);
     });
     it('returns branch if rebaseWhen=behind-base-branch but cannot rebase', async () => {
       config.rebaseWhen = 'behind-base-branch';
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       git.isBranchStale.mockResolvedValueOnce(true);
       platform.getBranchPr.mockResolvedValueOnce({
         ...pr,
diff --git a/lib/workers/branch/reuse.ts b/lib/workers/branch/reuse.ts
index 8a86a235df..0d2c0e4e70 100644
--- a/lib/workers/branch/reuse.ts
+++ b/lib/workers/branch/reuse.ts
@@ -13,7 +13,7 @@ export async function shouldReuseExistingBranch(
 ): Promise<ParentBranch> {
   const { branchName } = config;
   // Check if branch exists
-  if (!(await branchExists(branchName))) {
+  if (!branchExists(branchName)) {
     logger.debug(`Branch needs creating`);
     return { reuseExistingBranch: false };
   }
diff --git a/lib/workers/repository/finalise/prune.spec.ts b/lib/workers/repository/finalise/prune.spec.ts
index acb64146c5..a58de04933 100644
--- a/lib/workers/repository/finalise/prune.spec.ts
+++ b/lib/workers/repository/finalise/prune.spec.ts
@@ -23,42 +23,42 @@ describe('workers/repository/finalise/prune', () => {
     it('returns if no branchList', async () => {
       delete config.branchList;
       await cleanup.pruneStaleBranches(config, config.branchList);
-      expect(git.getAllRenovateBranches).toHaveBeenCalledTimes(0);
+      expect(git.getBranchList).toHaveBeenCalledTimes(0);
     });
     it('returns if no renovate branches', async () => {
       config.branchList = [];
-      git.getAllRenovateBranches.mockResolvedValueOnce([]);
+      git.getBranchList.mockReturnValueOnce([]);
       await expect(
         cleanup.pruneStaleBranches(config, config.branchList)
       ).resolves.not.toThrow();
     });
     it('returns if no remaining branches', async () => {
       config.branchList = ['renovate/a', 'renovate/b'];
-      git.getAllRenovateBranches.mockResolvedValueOnce(config.branchList);
+      git.getBranchList.mockReturnValueOnce(config.branchList);
       await cleanup.pruneStaleBranches(config, config.branchList);
-      expect(git.getAllRenovateBranches).toHaveBeenCalledTimes(1);
+      expect(git.getBranchList).toHaveBeenCalledTimes(1);
       expect(git.deleteBranch).toHaveBeenCalledTimes(0);
     });
     it('renames deletes remaining branch', async () => {
       config.branchList = ['renovate/a', 'renovate/b'];
-      git.getAllRenovateBranches.mockResolvedValueOnce(
+      git.getBranchList.mockReturnValueOnce(
         config.branchList.concat(['renovate/c'])
       );
       platform.findPr.mockResolvedValueOnce({ title: 'foo' } as never);
       await cleanup.pruneStaleBranches(config, config.branchList);
-      expect(git.getAllRenovateBranches).toHaveBeenCalledTimes(1);
+      expect(git.getBranchList).toHaveBeenCalledTimes(1);
       expect(git.deleteBranch).toHaveBeenCalledTimes(1);
       expect(platform.updatePr).toHaveBeenCalledTimes(1);
     });
     it('does nothing on dryRun', async () => {
       config.branchList = ['renovate/a', 'renovate/b'];
       config.dryRun = true;
-      git.getAllRenovateBranches.mockResolvedValueOnce(
+      git.getBranchList.mockReturnValueOnce(
         config.branchList.concat(['renovate/c'])
       );
       platform.findPr.mockResolvedValueOnce({ title: 'foo' } as never);
       await cleanup.pruneStaleBranches(config, config.branchList);
-      expect(git.getAllRenovateBranches).toHaveBeenCalledTimes(1);
+      expect(git.getBranchList).toHaveBeenCalledTimes(1);
       expect(git.deleteBranch).toHaveBeenCalledTimes(0);
       expect(platform.updatePr).toHaveBeenCalledTimes(0);
     });
@@ -66,26 +66,26 @@ describe('workers/repository/finalise/prune', () => {
       config.branchList = ['renovate/a', 'renovate/b'];
       config.dryRun = false;
       config.pruneStaleBranches = false;
-      git.getAllRenovateBranches.mockResolvedValueOnce(
+      git.getBranchList.mockReturnValueOnce(
         config.branchList.concat(['renovate/c'])
       );
       platform.findPr.mockResolvedValueOnce({ title: 'foo' } as never);
       await cleanup.pruneStaleBranches(config, config.branchList);
-      expect(git.getAllRenovateBranches).toHaveBeenCalledTimes(1);
+      expect(git.getBranchList).toHaveBeenCalledTimes(1);
       expect(git.deleteBranch).toHaveBeenCalledTimes(0);
       expect(platform.updatePr).toHaveBeenCalledTimes(0);
     });
     it('posts comment if someone pushed to PR', async () => {
       config.branchList = ['renovate/a', 'renovate/b'];
       config.dryRun = false;
-      git.getAllRenovateBranches.mockResolvedValueOnce(
+      git.getBranchList.mockReturnValueOnce(
         config.branchList.concat(['renovate/c'])
       );
       platform.getBranchPr.mockResolvedValueOnce({} as never);
       git.isBranchModified.mockResolvedValueOnce(true);
       platform.findPr.mockResolvedValueOnce({ title: 'foo' } as never);
       await cleanup.pruneStaleBranches(config, config.branchList);
-      expect(git.getAllRenovateBranches).toHaveBeenCalledTimes(1);
+      expect(git.getBranchList).toHaveBeenCalledTimes(1);
       expect(git.deleteBranch).toHaveBeenCalledTimes(0);
       expect(platform.updatePr).toHaveBeenCalledTimes(0);
       expect(platform.ensureComment).toHaveBeenCalledTimes(1);
@@ -93,14 +93,14 @@ describe('workers/repository/finalise/prune', () => {
     it('skips comment if dry run', async () => {
       config.branchList = ['renovate/a', 'renovate/b'];
       config.dryRun = true;
-      git.getAllRenovateBranches.mockResolvedValueOnce(
+      git.getBranchList.mockReturnValueOnce(
         config.branchList.concat(['renovate/c'])
       );
       platform.getBranchPr.mockResolvedValueOnce({} as never);
       git.isBranchModified.mockResolvedValueOnce(true);
       platform.findPr.mockResolvedValueOnce({ title: 'foo' } as never);
       await cleanup.pruneStaleBranches(config, config.branchList);
-      expect(git.getAllRenovateBranches).toHaveBeenCalledTimes(1);
+      expect(git.getBranchList).toHaveBeenCalledTimes(1);
       expect(git.deleteBranch).toHaveBeenCalledTimes(0);
       expect(platform.updatePr).toHaveBeenCalledTimes(0);
       expect(platform.ensureComment).toHaveBeenCalledTimes(0);
diff --git a/lib/workers/repository/finalise/prune.ts b/lib/workers/repository/finalise/prune.ts
index 616266170c..207b99000a 100644
--- a/lib/workers/repository/finalise/prune.ts
+++ b/lib/workers/repository/finalise/prune.ts
@@ -5,7 +5,7 @@ import { platform } from '../../../platform';
 import { PrState } from '../../../types';
 import {
   deleteBranch,
-  getAllRenovateBranches,
+  getBranchList,
   isBranchModified,
 } from '../../../util/git';
 
@@ -89,7 +89,9 @@ export async function pruneStaleBranches(
     logger.debug('No branchList');
     return;
   }
-  let renovateBranches = await getAllRenovateBranches(config.branchPrefix);
+  let renovateBranches = getBranchList().filter((branchName) =>
+    branchName.startsWith(config.branchPrefix)
+  );
   if (!renovateBranches?.length) {
     logger.debug('No renovate branches found');
     return;
diff --git a/lib/workers/repository/process/index.spec.ts b/lib/workers/repository/process/index.spec.ts
index 40e09c939e..bad4c22815 100644
--- a/lib/workers/repository/process/index.spec.ts
+++ b/lib/workers/repository/process/index.spec.ts
@@ -22,10 +22,10 @@ describe('workers/repository/process/index', () => {
     it('processes baseBranches', async () => {
       extract.mockResolvedValue({} as never);
       config.baseBranches = ['branch1', 'branch2'];
-      git.branchExists.mockResolvedValueOnce(false);
-      git.branchExists.mockResolvedValueOnce(true);
-      git.branchExists.mockResolvedValueOnce(false);
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(false);
+      git.branchExists.mockReturnValueOnce(true);
+      git.branchExists.mockReturnValueOnce(false);
+      git.branchExists.mockReturnValueOnce(true);
       const res = await extractDependencies(config);
       await updateRepo(config, res.branches, res.branchList);
       expect(res).toMatchSnapshot();
diff --git a/lib/workers/repository/process/index.ts b/lib/workers/repository/process/index.ts
index 02e8307c5e..cd7fcdf731 100644
--- a/lib/workers/repository/process/index.ts
+++ b/lib/workers/repository/process/index.ts
@@ -66,7 +66,7 @@ export async function extractDependencies(
     logger.debug({ baseBranches: config.baseBranches }, 'baseBranches');
     const extracted: Record<string, Record<string, PackageFile[]>> = {};
     for (const baseBranch of config.baseBranches) {
-      if (await branchExists(baseBranch)) {
+      if (branchExists(baseBranch)) {
         const baseBranchConfig = await getBaseBranchConfig(baseBranch, config);
         extracted[baseBranch] = await extract(baseBranchConfig);
       } else {
@@ -75,7 +75,7 @@ export async function extractDependencies(
     }
     addSplit('extract');
     for (const baseBranch of config.baseBranches) {
-      if (await branchExists(baseBranch)) {
+      if (branchExists(baseBranch)) {
         const baseBranchConfig = await getBaseBranchConfig(baseBranch, config);
         const packageFiles = extracted[baseBranch];
         const baseBranchRes = await lookup(baseBranchConfig, packageFiles);
diff --git a/lib/workers/repository/process/limits.spec.ts b/lib/workers/repository/process/limits.spec.ts
index 056aa0a5d6..5dfbb4eae2 100644
--- a/lib/workers/repository/process/limits.spec.ts
+++ b/lib/workers/repository/process/limits.spec.ts
@@ -39,18 +39,18 @@ describe('workers/repository/process/limits', () => {
     });
   });
   describe('getConcurrentPrsRemaining()', () => {
-    it('calculates concurrent limit remaining', async () => {
+    it('calculates concurrent limit remaining', () => {
       config.prConcurrentLimit = 20;
-      git.branchExists.mockResolvedValueOnce(true);
+      git.branchExists.mockReturnValueOnce(true);
       const branches: BranchConfig[] = [
         { branchName: 'test', upgrades: [] },
         { branchName: undefined, upgrades: [] },
       ];
-      const res = await limits.getConcurrentPrsRemaining(config, branches);
+      const res = limits.getConcurrentPrsRemaining(config, branches);
       expect(res).toEqual(19);
     });
-    it('returns 99 if no concurrent limit', async () => {
-      const res = await limits.getConcurrentPrsRemaining(config, []);
+    it('returns 99 if no concurrent limit', () => {
+      const res = limits.getConcurrentPrsRemaining(config, []);
       expect(res).toEqual(99);
     });
   });
diff --git a/lib/workers/repository/process/limits.ts b/lib/workers/repository/process/limits.ts
index 9d279476fd..0911eaaede 100644
--- a/lib/workers/repository/process/limits.ts
+++ b/lib/workers/repository/process/limits.ts
@@ -40,15 +40,15 @@ export async function getPrHourlyRemaining(
   return 99;
 }
 
-export async function getConcurrentPrsRemaining(
+export function getConcurrentPrsRemaining(
   config: RenovateConfig,
   branches: BranchConfig[]
-): Promise<number> {
+): number {
   if (config.prConcurrentLimit) {
     logger.debug(`Enforcing prConcurrentLimit (${config.prConcurrentLimit})`);
     let currentlyOpen = 0;
     for (const branch of branches) {
-      if (await branchExists(branch.branchName)) {
+      if (branchExists(branch.branchName)) {
         currentlyOpen += 1;
       }
     }
@@ -65,7 +65,7 @@ export async function getPrsRemaining(
   branches: BranchConfig[]
 ): Promise<number> {
   const hourlyRemaining = await getPrHourlyRemaining(config);
-  const concurrentRemaining = await getConcurrentPrsRemaining(config, branches);
+  const concurrentRemaining = getConcurrentPrsRemaining(config, branches);
   return hourlyRemaining < concurrentRemaining
     ? hourlyRemaining
     : concurrentRemaining;
-- 
GitLab