3333)
3434
3535DEFAULT_STORAGE_OPTIMIZATION_THRESHOLD_MB = 10000
36+ INITIAL_BATCH_DEEPEN = 50
37+ MAX_BATCH_DEEPEN = 10000
38+ BATCH_DEEPEN_MULTIPLIER = 2
3639
3740RETRYABLE_CLONE_ERRORS = (RateLimitError , NetworkError , RemoteServerError )
3841
@@ -57,6 +60,16 @@ def __init__(self):
5760 def _is_gerrit_remote (remote : str ) -> bool :
5861 return any (pattern in remote for pattern in GERRIT_PATTERNS )
5962
63+ async def _configure_global_git_client (self , path : str ) -> None :
64+ # Increase post buffer to reduce RPC failures on large repos
65+ await run_shell_command (
66+ ["git" , "config" , "--global" , "http.postBuffer" , "524288000" ], cwd = path
67+ )
68+ # Disable recursive submodule fetches
69+ await run_shell_command (
70+ ["git" , "config" , "--global" , "fetch.recurseSubmodules" , "false" ], cwd = path
71+ )
72+
6073 async def _check_if_final_batch (self , path : str , target_commit_hash : str | None ) -> bool :
6174 """
6275 Final batch is determined if:
@@ -74,20 +87,62 @@ async def _check_if_final_batch(self, path: str, target_commit_hash: str | None)
7487 await run_shell_command (
7588 ["git" , "rev-parse" , "--verify" , f"{ target_commit_hash } ^{{commit}}" ], cwd = path
7689 )
90+ has_boundary_in_required_range = await self ._has_boundary_in_required_range (
91+ path , target_commit_hash
92+ )
93+ if has_boundary_in_required_range :
94+ self .logger .info (
95+ f"Target commit { target_commit_hash } reached, but shallow boundary still intersects required range. Continuing deepen."
96+ )
97+ return False
7798 self .logger .info (f"Target commit { target_commit_hash } reached" )
7899 return True
79100 except CommandExecutionError :
80101 return False
81102
103+ async def _read_shallow_file (self , repo_path : str ) -> list [str ]:
104+ """
105+ Read commit hashes from .git/shallow.
106+ Returns an empty list when the file does not exist (full clone).
107+ """
108+ shallow_file = os .path .join (repo_path , ".git" , "shallow" )
109+ try :
110+ async with aiofiles .open (shallow_file , "r" , encoding = "utf-8" ) as f :
111+ return [line .strip () for line in await f .readlines () if line .strip ()]
112+ except FileNotFoundError :
113+ return []
114+
115+ async def _get_shallow_boundary_commits (self , repo_path : str ) -> set [str ]:
116+ """
117+ Return all shallow boundary commits from .git/shallow.
118+ """
119+ return set (await self ._read_shallow_file (repo_path ))
120+
121+ async def _has_boundary_in_required_range (
122+ self , repo_path : str , target_commit_hash : str
123+ ) -> bool :
124+ """
125+ Check if any shallow boundary commit is inside the current required range.
126+
127+ If true, the required range is still truncated and the clone should be deepened.
128+ """
129+ shallow_boundaries = await self ._get_shallow_boundary_commits (repo_path )
130+ if not shallow_boundaries :
131+ return False
132+
133+ required_commits_output = await run_shell_command (
134+ ["git" , "rev-list" , f"{ target_commit_hash } ..HEAD" ],
135+ cwd = repo_path ,
136+ )
137+ required_commits = {line .strip () for line in required_commits_output .splitlines () if line }
138+ problematic_boundaries = (required_commits & shallow_boundaries ) - {target_commit_hash }
139+ return bool (problematic_boundaries )
140+
82141 @retry_on_clone_error
83142 async def _perform_minimal_clone (self , path : str , remote : str ) -> None :
84143 """
85144 Perform minimal clone of depth=1
86145 """
87- # increasing post buffer to avoid RPC failed error
88- await run_shell_command (
89- ["git" , "config" , "--global" , "http.postBuffer" , "524288000" ], cwd = path
90- )
91146 self .logger .info ("Initializing minimal clone" )
92147 await run_shell_command (
93148 ["git" , "clone" , "--depth=1" , "--no-tags" , "--single-branch" , remote , "." ], cwd = path
@@ -196,21 +251,19 @@ async def _update_batch_info(
196251 batch_info .is_final_batch = await self ._check_if_final_batch (repo_path , target_commit_hash )
197252 batch_info .edge_commit = await self ._get_edge_commit (repo_path )
198253
199- async def _get_edge_commit (self , repo_path : str ):
254+ async def _get_edge_commit (self , repo_path : str ) -> str | None :
200255 """
201256 Returns the edge commit of a shallow clone by reading the .git/shallow file,
202257 which contains the boundary commit(s) when history is truncated.
203258
204259 If the full history has been cloned, the .git/shallow file does not exist.
205260 """
206- shallow_file = os .path .join (repo_path , ".git" , "shallow" )
207- try :
208- async with aiofiles .open (shallow_file , "r" , encoding = "utf-8" ) as f :
209- oldest_commit = (await f .readline ()).strip ()
210- self .logger .info (f"Edge commit: { oldest_commit } " )
211- return oldest_commit
212- except FileNotFoundError :
261+ boundaries = await self ._read_shallow_file (repo_path )
262+ if not boundaries :
213263 return None
264+ oldest_commit = boundaries [0 ]
265+ self .logger .info (f"Edge commit: { oldest_commit } " )
266+ return oldest_commit
214267
215268 async def _cleanup_temp_directory (self , temp_repo_path : str , repo_id : str ) -> None :
216269 """
@@ -288,26 +341,6 @@ async def _cleanup_working_directory(self, repo_path: str) -> None:
288341
289342 self .logger .info ("Working directory cleanup completed" )
290343
291- async def _calculate_batch_depth (self , repo_path : str , remote : str ) -> int :
292- calculated_depth = None
293- total_branches_tags = await run_shell_command (
294- ["git" , "ls-remote" , "--heads" , "--tags" , remote ], cwd = repo_path
295- )
296- total_branches_tags = len (total_branches_tags .splitlines ())
297- if total_branches_tags <= 200 :
298- # Small repo, get a decent amount of history
299- calculated_depth = 100
300- elif total_branches_tags <= 1000 :
301- # Medium repo, get a moderate amount of history
302- calculated_depth = 50
303- else :
304- # Large repo, get less history
305- calculated_depth = 5
306- self .logger .info (
307- f"total_branches_tags={ total_branches_tags } , calculated_depth={ calculated_depth } "
308- )
309- return calculated_depth
310-
311344 @retry_on_clone_error
312345 async def _perform_full_clone (self , repo_path : str , remote : str ):
313346 """Perform full repository clone"""
@@ -372,6 +405,7 @@ async def determine_clone_strategy(
372405 self .logger .info (
373406 f"Starting clone decision for { remote } (branch: { branch } , last_commit: { last_processed_commit } )"
374407 )
408+ await self ._configure_global_git_client (repo_path )
375409
376410 default_branch_changed = await self .has_default_branch_changed (remote , branch )
377411
@@ -405,6 +439,7 @@ async def clone_batches_generator(
405439 error_code = None
406440 error_message = None
407441 total_execution_time = 0.0
442+ batch_depth = INITIAL_BATCH_DEEPEN
408443 remote = repository .url .removesuffix (".git" )
409444
410445 batch_info = CloneBatchInfo (
@@ -420,8 +455,6 @@ async def clone_batches_generator(
420455 clone_with_batches = await self .determine_clone_strategy (
421456 temp_repo_path , remote , repository .branch , repository .last_processed_commit
422457 )
423- if clone_with_batches :
424- batch_depth = await self ._calculate_batch_depth (temp_repo_path , remote )
425458 await self ._update_batch_info (
426459 batch_info , temp_repo_path , repository .last_processed_commit , clone_with_batches
427460 )
@@ -447,6 +480,12 @@ async def clone_batches_generator(
447480 total_execution_time += round (batch_end_time - batch_start_time , 2 )
448481
449482 yield batch_info
483+ if not batch_info .is_final_batch :
484+ # exponential deepen increment to speed up fetching
485+ batch_depth = min (
486+ batch_depth * BATCH_DEEPEN_MULTIPLIER ,
487+ MAX_BATCH_DEEPEN ,
488+ )
450489
451490 except Exception as e :
452491 # Handle both CrowdGitError and generic Exception
0 commit comments