From d6d4124b11ee66f1db019d0359cc47606f8d946d Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Thu, 9 Sep 2021 09:25:12 +0200
Subject: [PATCH] feat(git): blobless git (#11401)

---
 docs/development/local-development.md |  2 +-
 docs/usage/getting-started/running.md |  2 +
 lib/util/git/index.spec.ts            |  8 +--
 lib/util/git/index.ts                 | 71 ++++++---------------------
 lib/workers/repository/init/index.ts  |  2 +-
 5 files changed, 22 insertions(+), 63 deletions(-)

diff --git a/docs/development/local-development.md b/docs/development/local-development.md
index faba5746bc..4d848f8316 100644
--- a/docs/development/local-development.md
+++ b/docs/development/local-development.md
@@ -10,7 +10,7 @@ For example, if you think anything is unclear, or you think something needs to b
 
 You need the following dependencies for local development:
 
-- Git `>=2.22.0`
+- Git `>=2.33.0`
 - Node.js `>=14.15.4`
 - Yarn `^1.22.5`
 - C++ compiler
diff --git a/docs/usage/getting-started/running.md b/docs/usage/getting-started/running.md
index 9831beb5f6..9b33176075 100644
--- a/docs/usage/getting-started/running.md
+++ b/docs/usage/getting-started/running.md
@@ -27,6 +27,8 @@ Therefore if you need Renovate to support any non-npm lock files like Bundler th
 
 The `renovate` npm package is compatible with all of Renovate's supported platforms.
 
+Renovate requires Node.js >=14.15.0 as well as Git >=2.33.0.
+
 #### Docker image
 
 The `renovate` npm package is also distributed via pre-built Node.js images on Docker Hub (`renovate/renovate`).
diff --git a/lib/util/git/index.spec.ts b/lib/util/git/index.spec.ts
index 36a3136e74..d7412b3720 100644
--- a/lib/util/git/index.spec.ts
+++ b/lib/util/git/index.spec.ts
@@ -75,7 +75,7 @@ describe('util/git/index', () => {
     await git.initRepo({
       url: origin.path,
     });
-    await git.setUserRepoConfig({ branchPrefix: 'renovate/' });
+    git.setUserRepoConfig({ branchPrefix: 'renovate/' });
     git.setGitAuthor('Jest <Jest@example.com>');
     setNoVerify([]);
     await git.syncGit();
@@ -159,7 +159,7 @@ describe('util/git/index', () => {
       expect(await git.isBranchModified('renovate/future_branch')).toBe(false);
     });
     it('should return false when author is ignored', async () => {
-      await git.setUserRepoConfig({
+      git.setUserRepoConfig({
         gitIgnoredAuthors: ['custom@example.com'],
       });
       expect(await git.isBranchModified('renovate/custom_author')).toBe(false);
@@ -494,7 +494,7 @@ describe('util/git/index', () => {
         url: base.path,
       });
 
-      await git.setUserRepoConfig({ branchPrefix: 'renovate/' });
+      git.setUserRepoConfig({ branchPrefix: 'renovate/' });
       expect(git.branchExists('renovate/test')).toBe(true);
 
       await git.initRepo({
@@ -504,7 +504,7 @@ describe('util/git/index', () => {
       await repo.checkout('renovate/test');
       await repo.commit('past message3', ['--amend']);
 
-      await git.setUserRepoConfig({ branchPrefix: 'renovate/' });
+      git.setUserRepoConfig({ branchPrefix: 'renovate/' });
       expect(git.branchExists('renovate/test')).toBe(true);
     });
 
diff --git a/lib/util/git/index.ts b/lib/util/git/index.ts
index 7a674036c6..a0faf92734 100644
--- a/lib/util/git/index.ts
+++ b/lib/util/git/index.ts
@@ -53,7 +53,6 @@ interface LocalConfig extends StorageConfig {
   currentBranchSha: string;
   branchCommits: Record<string, CommitSha>;
   branchIsModified: Record<string, boolean>;
-  branchPrefix: string;
   ignoredAuthors: string[];
   gitAuthorName?: string;
   gitAuthorEmail?: string;
@@ -215,25 +214,6 @@ async function cleanLocalBranches(): Promise<void> {
   }
 }
 
-/*
- * When we initially clone, we clone only the default branch so how no knowledge of other branches existing.
- * By calling this function once the repo's branchPrefix is known, we can fetch all of Renovate's branches in one command.
- */
-async function setBranchPrefix(branchPrefix: string): Promise<void> {
-  config.branchPrefix = branchPrefix;
-  // If the repo is already cloned then set branchPrefix now, otherwise it will be called again during syncGit()
-  if (gitInitialized) {
-    logger.debug('Setting branchPrefix: ' + branchPrefix);
-    const ref = `refs/heads/${branchPrefix}*:refs/remotes/origin/${branchPrefix}*`;
-    try {
-      await git.fetch(['origin', ref, '--depth=5', '--force']);
-    } catch (err) /* istanbul ignore next */ {
-      checkForPlatformFailure(err);
-      throw err;
-    }
-  }
-}
-
 export function setGitAuthor(gitAuthor: string): void {
   const gitAuthorParsed = parseGitAuthor(
     gitAuthor || 'Renovate Bot <renovate@whitesourcesoftware.com>'
@@ -270,12 +250,10 @@ export async function writeGitAuthor(): Promise<void> {
   }
 }
 
-export async function setUserRepoConfig({
-  branchPrefix,
+export function setUserRepoConfig({
   gitIgnoredAuthors,
   gitAuthor,
-}: RenovateConfig): Promise<void> {
-  await setBranchPrefix(branchPrefix);
+}: RenovateConfig): void {
   config.ignoredAuthors = gitIgnoredAuthors ?? [];
   setGitAuthor(gitAuthor);
 }
@@ -316,7 +294,7 @@ export async function syncGit(): Promise<void> {
       await resetToBranch(await getDefaultBranch(git));
       const fetchStart = Date.now();
       await git.pull();
-      await git.fetch(['--depth=10']);
+      await git.fetch();
       config.currentBranch =
         config.currentBranch || (await getDefaultBranch(git));
       await resetToBranch(config.currentBranch);
@@ -336,8 +314,8 @@ export async function syncGit(): Promise<void> {
     await fs.emptyDir(localDir);
     const cloneStart = Date.now();
     try {
-      // clone only the default branch
-      const opts = ['--depth=10'];
+      // blobless clone
+      const opts = ['--filter=blob:none'];
       if (config.extraCloneOpts) {
         Object.entries(config.extraCloneOpts).forEach((e) =>
           opts.push(e[0], `${e[1]}`)
@@ -383,9 +361,6 @@ export async function syncGit(): Promise<void> {
     logger.warn({ err }, 'Cannot retrieve latest commit');
   }
   config.currentBranch = config.currentBranch || (await getDefaultBranch(git));
-  if (config.branchPrefix) {
-    await setBranchPrefix(config.branchPrefix);
-  }
 }
 
 // istanbul ignore next
@@ -394,24 +369,6 @@ export async function getRepoStatus(): Promise<StatusResult> {
   return git.status();
 }
 
-async function syncBranch(branchName: string): Promise<void> {
-  await syncGit();
-  if (branchName.startsWith(config.branchPrefix)) {
-    return;
-  }
-  if (config.additionalBranches.includes(branchName)) {
-    return;
-  }
-  config.additionalBranches.push(branchName);
-  // fetch the branch only if it's not part of the existing branchPrefix
-  try {
-    await git.raw(['remote', 'set-branches', '--add', 'origin', branchName]);
-    await git.fetch(['origin', branchName, '--depth=5']);
-  } catch (err) /* istanbul ignore next */ {
-    checkForPlatformFailure(err);
-  }
-}
-
 export function branchExists(branchName: string): boolean {
   return !!config.branchCommits[branchName];
 }
@@ -447,7 +404,7 @@ export async function getCommitMessages(): Promise<string[]> {
 
 export async function checkoutBranch(branchName: string): Promise<CommitSha> {
   logger.debug(`Setting current branch to ${branchName}`);
-  await syncBranch(branchName);
+  await syncGit();
   try {
     config.currentBranch = branchName;
     config.currentBranchSha = (
@@ -506,7 +463,7 @@ export function getBranchList(): string[] {
 }
 
 export async function isBranchStale(branchName: string): Promise<boolean> {
-  await syncBranch(branchName);
+  await syncGit();
   try {
     const { currentBranchSha, currentBranch } = config;
     const branches = await git.branch([
@@ -528,7 +485,7 @@ export async function isBranchStale(branchName: string): Promise<boolean> {
 }
 
 export async function isBranchModified(branchName: string): Promise<boolean> {
-  await syncBranch(branchName);
+  await syncGit();
   // First check cache
   if (config.branchIsModified[branchName] !== undefined) {
     return config.branchIsModified[branchName];
@@ -580,7 +537,7 @@ export async function isBranchModified(branchName: string): Promise<boolean> {
 }
 
 export async function deleteBranch(branchName: string): Promise<void> {
-  await syncBranch(branchName);
+  await syncGit();
   try {
     await git.raw(['push', '--delete', 'origin', branchName]);
     logger.debug({ branchName }, 'Deleted remote branch');
@@ -602,7 +559,7 @@ export async function deleteBranch(branchName: string): Promise<void> {
 export async function mergeBranch(branchName: string): Promise<void> {
   let status;
   try {
-    await syncBranch(branchName);
+    await syncGit();
     await git.reset(ResetMode.HARD);
     await git.checkout(['-B', branchName, 'origin/' + branchName]);
     await git.checkout([
@@ -633,7 +590,7 @@ export async function mergeBranch(branchName: string): Promise<void> {
 export async function getBranchLastCommitTime(
   branchName: string
 ): Promise<Date> {
-  await syncBranch(branchName);
+  await syncGit();
   try {
     const time = await git.show(['-s', '--format=%ai', 'origin/' + branchName]);
     return new Date(Date.parse(time));
@@ -644,7 +601,7 @@ export async function getBranchLastCommitTime(
 }
 
 export async function getBranchFiles(branchName: string): Promise<string[]> {
-  await syncBranch(branchName);
+  await syncGit();
   try {
     const diff = await git.diffSummary([
       `origin/${branchName}`,
@@ -675,7 +632,7 @@ export async function getFile(
 }
 
 export async function hasDiff(branchName: string): Promise<boolean> {
-  await syncBranch(branchName);
+  await syncGit();
   try {
     return (await git.diff(['HEAD', branchName])) !== '';
   } catch (err) {
@@ -816,7 +773,7 @@ export async function commitFiles({
     logger.debug({ result: pushRes }, 'git push');
     // Fetch it after create
     const ref = `refs/heads/${branchName}:refs/remotes/origin/${branchName}`;
-    await git.fetch(['origin', ref, '--depth=5', '--force']);
+    await git.fetch(['origin', ref, '--force']);
     config.branchCommits[branchName] = (
       await git.revparse([branchName])
     ).trim();
diff --git a/lib/workers/repository/init/index.ts b/lib/workers/repository/init/index.ts
index b3584ee83b..62c292ad1f 100644
--- a/lib/workers/repository/init/index.ts
+++ b/lib/workers/repository/init/index.ts
@@ -32,7 +32,7 @@ export async function initRepo(
   checkIfConfigured(config);
   warnOnUnsupportedOptions(config);
   config = applySecretsToConfig(config);
-  await setUserRepoConfig(config);
+  setUserRepoConfig(config);
   config = await detectVulnerabilityAlerts(config);
   // istanbul ignore if
   if (config.printConfig) {
-- 
GitLab