From 062045168a1c3d7ff18f2c61e4ae95b13afa8e56 Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Mon, 17 Aug 2020 13:31:53 +0200
Subject: [PATCH] refactor(git): lazy sync git (#6984)

Co-authored-by: Jamie Magee <JamieMagee@users.noreply.github.com>
---
 lib/platform/azure/index.ts            |  2 +-
 lib/platform/bitbucket-server/index.ts |  2 +-
 lib/platform/bitbucket/index.ts        |  2 +-
 lib/platform/gitea/index.ts            |  2 +-
 lib/platform/github/index.ts           |  2 +-
 lib/platform/gitlab/index.ts           |  2 +-
 lib/util/git/index.spec.ts             | 15 ++++---
 lib/util/git/index.ts                  | 55 ++++++++++++++++++--------
 8 files changed, 53 insertions(+), 29 deletions(-)

diff --git a/lib/platform/azure/index.ts b/lib/platform/azure/index.ts
index 51040110fb..7f9b5c42d4 100644
--- a/lib/platform/azure/index.ts
+++ b/lib/platform/azure/index.ts
@@ -150,7 +150,7 @@ export async function initRepo({
   const url =
     defaults.endpoint +
     `${encodeURIComponent(projectName)}/_git/${encodeURIComponent(repoName)}`;
-  await git.initRepo({
+  git.initRepo({
     ...config,
     localDir,
     url,
diff --git a/lib/platform/bitbucket-server/index.ts b/lib/platform/bitbucket-server/index.ts
index f2a7ab70d6..f3ff7b8669 100644
--- a/lib/platform/bitbucket-server/index.ts
+++ b/lib/platform/bitbucket-server/index.ts
@@ -179,7 +179,7 @@ export async function initRepo({
     repository,
   });
 
-  await git.initRepo({
+  git.initRepo({
     ...config,
     localDir,
     url: gitUrl,
diff --git a/lib/platform/bitbucket/index.ts b/lib/platform/bitbucket/index.ts
index 53b0cd12a2..30ad14f102 100644
--- a/lib/platform/bitbucket/index.ts
+++ b/lib/platform/bitbucket/index.ts
@@ -153,7 +153,7 @@ export async function initRepo({
     repository,
   });
 
-  await git.initRepo({
+  git.initRepo({
     ...config,
     localDir,
     url,
diff --git a/lib/platform/gitea/index.ts b/lib/platform/gitea/index.ts
index 081d1f2005..fa4ab36f41 100644
--- a/lib/platform/gitea/index.ts
+++ b/lib/platform/gitea/index.ts
@@ -306,7 +306,7 @@ const platform: Platform = {
     gitEndpoint.auth = opts.token;
 
     // Initialize Git storage
-    await git.initRepo({
+    git.initRepo({
       ...config,
       url: URL.format(gitEndpoint),
       gitAuthorName: global.gitAuthor?.name,
diff --git a/lib/platform/github/index.ts b/lib/platform/github/index.ts
index 09bb5a4af5..7d1ce6d5fe 100644
--- a/lib/platform/github/index.ts
+++ b/lib/platform/github/index.ts
@@ -413,7 +413,7 @@ export async function initRepo({
   );
   parsedEndpoint.pathname = config.repository + '.git';
   const url = URL.format(parsedEndpoint);
-  await git.initRepo({
+  git.initRepo({
     ...config,
     url,
     gitAuthorName: global.gitAuthor?.name,
diff --git a/lib/platform/gitlab/index.ts b/lib/platform/gitlab/index.ts
index 518cf5b3c0..19e511d6de 100644
--- a/lib/platform/gitlab/index.ts
+++ b/lib/platform/gitlab/index.ts
@@ -224,7 +224,7 @@ export async function initRepo({
       repoUrl.auth = 'oauth2:' + opts.token;
       url = URL.format(repoUrl);
     }
-    await git.initRepo({
+    git.initRepo({
       ...config,
       url,
       gitAuthorName: global.gitAuthor?.name,
diff --git a/lib/util/git/index.spec.ts b/lib/util/git/index.spec.ts
index 0a344739a7..4e31c3a315 100644
--- a/lib/util/git/index.spec.ts
+++ b/lib/util/git/index.spec.ts
@@ -60,7 +60,7 @@ describe('platform/git', () => {
     const repo = Git(origin.path);
     await repo.clone(base.path, '.', ['--bare']);
     tmpDir = await tmp.dir({ unsafeCleanup: true });
-    await git.initRepo({
+    git.initRepo({
       localDir: tmpDir.path,
       url: origin.path,
       extraCloneOpts: {
@@ -100,10 +100,11 @@ describe('platform/git', () => {
       const repo = Git(base.path).silent(true);
       await repo.submoduleAdd(base.path, 'submodule');
       await repo.commit('Add submodule');
-      await git.initRepo({
+      git.initRepo({
         localDir: tmpDir.path,
         url: base.path,
       });
+      await git.syncGit();
       expect(await fs.exists(tmpDir.path + '/.gitmodules')).toBeTruthy();
       expect(await git.getFileList()).toMatchSnapshot();
       await repo.reset(['--hard', 'HEAD^']);
@@ -129,6 +130,7 @@ describe('platform/git', () => {
   });
   describe('isBranchStale()', () => {
     beforeEach(async () => {
+      await git.syncGit();
       await git.setBranch('master');
     });
     it('should return false if same SHA as master', async () => {
@@ -369,7 +371,7 @@ describe('platform/git', () => {
 
       await git.setBranch('develop');
 
-      await git.initRepo({
+      git.initRepo({
         localDir: tmpDir.path,
         url: base.path,
       });
@@ -391,7 +393,7 @@ describe('platform/git', () => {
       await repo.commit('past message2');
       await repo.checkout('master');
 
-      await git.initRepo({
+      git.initRepo({
         localDir: tmpDir.path,
         url: base.path,
       });
@@ -400,7 +402,7 @@ describe('platform/git', () => {
       expect(await git.branchExists('renovate/test')).toBe(true);
       const cid = await git.getBranchCommit('renovate/test');
 
-      await git.initRepo({
+      git.initRepo({
         localDir: tmpDir.path,
         url: base.path,
       });
@@ -429,10 +431,11 @@ describe('platform/git', () => {
         'test',
       ]);
       await repo.commit('Add submodule');
-      await git.initRepo({
+      git.initRepo({
         localDir: tmpDir.path,
         url: base.path,
       });
+      await git.syncGit();
       expect(await fs.exists(tmpDir.path + '/.gitmodules')).toBeTruthy();
       await repo.reset(['--hard', 'HEAD^']);
     });
diff --git a/lib/util/git/index.ts b/lib/util/git/index.ts
index 3dafd524c7..a2eb0bc6b1 100644
--- a/lib/util/git/index.ts
+++ b/lib/util/git/index.ts
@@ -114,6 +114,13 @@ let git: SimpleGit | undefined;
 
 let privateKeySet = false;
 
+export function initRepo(args: StorageConfig): void {
+  config = { ...args } as any;
+  config.branchExists = {};
+  config.branchIsModified = {};
+  git = undefined;
+}
+
 async function resetToBranch(branchName: string): Promise<void> {
   logger.debug(`resetToBranch(${branchName})`);
   await git.raw(['reset', '--hard']);
@@ -143,14 +150,17 @@ async function cleanLocalBranches(): Promise<void> {
  * By calling this function once the repo's branchPrefix is known, we can fetch all of Renovate's branches in one command.
  */
 export async function setBranchPrefix(branchPrefix: string): Promise<void> {
-  logger.debug('Setting branchPrefix: ' + branchPrefix);
   config.branchPrefix = branchPrefix;
-  const ref = `refs/heads/${branchPrefix}*:refs/remotes/origin/${branchPrefix}*`;
-  try {
-    await git.fetch(['origin', ref, '--depth=2', '--force']);
-  } catch (err) /* istanbul ignore next */ {
-    checkForPlatformFailure(err);
-    throw err;
+  // If the repo is already cloned then set branchPrefix now, otherwise it will be called again during syncGit()
+  if (git) {
+    logger.debug('Setting branchPrefix: ' + branchPrefix);
+    const ref = `refs/heads/${branchPrefix}*:refs/remotes/origin/${branchPrefix}*`;
+    try {
+      await git.fetch(['origin', ref, '--depth=2', '--force']);
+    } catch (err) /* istanbul ignore next */ {
+      checkForPlatformFailure(err);
+      throw err;
+    }
   }
 }
 
@@ -258,20 +268,15 @@ export async function syncGit(): Promise<void> {
     logger.debug({ err }, 'Error setting git author config');
     throw new Error(REPOSITORY_TEMPORARY_ERROR);
   }
-
   config.currentBranch = config.currentBranch || (await getDefaultBranch(git));
-}
-
-export async function initRepo(args: StorageConfig): Promise<void> {
-  config = { ...args } as any;
-  config.branchExists = {};
-  config.branchIsModified = {};
-  git = undefined;
-  await syncGit();
+  if (config.branchPrefix) {
+    await setBranchPrefix(config.branchPrefix);
+  }
 }
 
 // istanbul ignore next
-export function getRepoStatus(): Promise<StatusResult> {
+export async function getRepoStatus(): Promise<StatusResult> {
+  await syncGit();
   return git.status();
 }
 
@@ -279,6 +284,7 @@ export async function createBranch(
   branchName: string,
   sha: string
 ): Promise<void> {
+  await syncGit();
   logger.debug(`createBranch(${branchName})`);
   await git.reset(ResetMode.HARD);
   await git.raw(['clean', '-fd']);
@@ -289,6 +295,7 @@ export async function createBranch(
 }
 
 export async function branchExists(branchName: string): Promise<boolean> {
+  await syncGit();
   // First check cache
   if (config.branchExists[branchName] !== undefined) {
     return config.branchExists[branchName];
@@ -315,6 +322,7 @@ export async function branchExists(branchName: string): Promise<boolean> {
 
 // Return the commit SHA for a branch
 export async function getBranchCommit(branchName: string): Promise<string> {
+  await syncGit();
   if (!(await branchExists(branchName))) {
     throw Error(
       'Cannot fetch commit for branch that does not exist: ' + branchName
@@ -325,6 +333,7 @@ export async function getBranchCommit(branchName: string): Promise<string> {
 }
 
 export async function getCommitMessages(): Promise<string[]> {
+  await syncGit();
   logger.debug('getCommitMessages');
   const res = await git.log({
     n: 10,
@@ -334,6 +343,7 @@ export async function getCommitMessages(): Promise<string[]> {
 }
 
 export async function setBranch(branchName: string): Promise<string> {
+  await syncGit();
   if (!(await branchExists(branchName))) {
     throwBranchValidationError(branchName);
   }
@@ -365,6 +375,7 @@ export async function setBranch(branchName: string): Promise<string> {
 }
 
 export async function getFileList(): Promise<string[]> {
+  await syncGit();
   const branch = config.currentBranch;
   const submodules = await getSubmodules();
   const files: string = await git.raw(['ls-tree', '-r', branch]);
@@ -385,6 +396,7 @@ export async function getFileList(): Promise<string[]> {
 export async function getAllRenovateBranches(
   branchPrefix: string
 ): Promise<string[]> {
+  await syncGit();
   const branches = await git.branch(['--remotes', '--verbose']);
   return branches.all
     .map(localName)
@@ -392,6 +404,7 @@ export async function getAllRenovateBranches(
 }
 
 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
@@ -407,6 +420,7 @@ export async function isBranchStale(branchName: string): Promise<boolean> {
 }
 
 export async function isBranchModified(branchName: string): Promise<boolean> {
+  await syncGit();
   // First check cache
   if (config.branchIsModified[branchName] !== undefined) {
     return config.branchIsModified[branchName];
@@ -438,6 +452,7 @@ export async function isBranchModified(branchName: string): Promise<boolean> {
 }
 
 export async function deleteBranch(branchName: string): Promise<void> {
+  await syncGit();
   try {
     await git.raw(['push', '--delete', 'origin', branchName]);
     logger.debug({ branchName }, 'Deleted remote branch');
@@ -457,6 +472,7 @@ export async function deleteBranch(branchName: string): Promise<void> {
 }
 
 export async function mergeBranch(branchName: string): Promise<void> {
+  await syncGit();
   await git.reset(ResetMode.HARD);
   await git.checkout(['-B', branchName, 'origin/' + branchName]);
   await git.checkout(config.currentBranch);
@@ -468,6 +484,7 @@ export async function mergeBranch(branchName: string): Promise<void> {
 export async function getBranchLastCommitTime(
   branchName: string
 ): Promise<Date> {
+  await syncGit();
   try {
     const time = await git.show(['-s', '--format=%ai', 'origin/' + branchName]);
     return new Date(Date.parse(time));
@@ -478,6 +495,7 @@ export async function getBranchLastCommitTime(
 }
 
 export async function getBranchFiles(branchName: string): Promise<string[]> {
+  await syncGit();
   try {
     const diff = await git.diffSummary([branchName, config.currentBranch]);
     return diff.files.map((file) => file.file);
@@ -491,6 +509,7 @@ export async function getFile(
   filePath: string,
   branchName?: string
 ): Promise<string | null> {
+  await syncGit();
   if (branchName) {
     const exists = await branchExists(branchName);
     if (!exists) {
@@ -510,6 +529,7 @@ export async function getFile(
 }
 
 export async function hasDiff(branchName: string): Promise<boolean> {
+  await syncGit();
   try {
     return (await git.diff(['HEAD', branchName])) !== '';
   } catch (err) {
@@ -545,6 +565,7 @@ export async function commitFiles({
   message,
   force = false,
 }: CommitFilesConfig): Promise<string | null> {
+  await syncGit();
   logger.debug(`Committing files to branch ${branchName}`);
   if (!privateKeySet) {
     await writePrivateKey(config.localDir);
-- 
GitLab