@@ -29,16 +29,19 @@ class CloneConfig:
2929 The specific commit hash to check out after cloning (default is None).
3030 branch : str, optional
3131 The branch to clone (default is None).
32+ subpath : str
33+ The subpath to clone from the repository (default is "/").
3234 """
3335
3436 url : str
3537 local_path : str
3638 commit : Optional [str ] = None
3739 branch : Optional [str ] = None
40+ subpath : str = "/"
3841
3942
4043@async_timeout (TIMEOUT )
41- async def clone_repo (config : CloneConfig ) -> Tuple [ bytes , bytes ] :
44+ async def clone_repo (config : CloneConfig ) -> None :
4245 """
4346 Clone a repository to a local path based on the provided configuration.
4447
@@ -49,35 +52,21 @@ async def clone_repo(config: CloneConfig) -> Tuple[bytes, bytes]:
4952 Parameters
5053 ----------
5154 config : CloneConfig
52- A dictionary containing the following keys:
53- - url (str): The URL of the repository.
54- - local_path (str): The local path to clone the repository to.
55- - commit (str, optional): The specific commit hash to checkout.
56- - branch (str, optional): The branch to clone. Defaults to 'main' or 'master' if not provided.
57-
58- Returns
59- -------
60- Tuple[bytes, bytes]
61- A tuple containing the stdout and stderr of the Git commands executed.
55+ The configuration for cloning the repository.
6256
6357 Raises
6458 ------
6559 ValueError
66- If the 'url' or 'local_path' parameters are missing, or if the repository is not found .
60+ If the repository is not found or if the provided URL is invalid .
6761 OSError
68- If there is an error creating the parent directory structure .
62+ If an error occurs while creating the parent directory for the repository .
6963 """
7064 # Extract and validate query parameters
7165 url : str = config .url
7266 local_path : str = config .local_path
7367 commit : Optional [str ] = config .commit
7468 branch : Optional [str ] = config .branch
75-
76- if not url :
77- raise ValueError ("The 'url' parameter is required." )
78-
79- if not local_path :
80- raise ValueError ("The 'local_path' parameter is required." )
69+ partial_clone : bool = config .subpath != "/"
8170
8271 # Create parent directory if it doesn't exist
8372 parent_dir = Path (local_path ).parent
@@ -90,34 +79,32 @@ async def clone_repo(config: CloneConfig) -> Tuple[bytes, bytes]:
9079 if not await _check_repo_exists (url ):
9180 raise ValueError ("Repository not found, make sure it is public" )
9281
93- if commit :
94- # Scenario 1: Clone and checkout a specific commit
95- # Clone the repository without depth to ensure full history for checkout
96- clone_cmd = ["git" , "clone" , "--recurse-submodules" , "--single-branch" , url , local_path ]
97- await _run_git_command (* clone_cmd )
82+ clone_cmd = ["git" , "clone" , "--recurse-submodules" , "--single-branch" ]
9883
99- # Checkout the specific commit
100- checkout_cmd = ["git" , "-C" , local_path , "checkout" , commit ]
101- return await _run_git_command (* checkout_cmd )
84+ if partial_clone :
85+ clone_cmd += ["--filter=blob:none" , "--sparse" ]
10286
103- if branch and branch .lower () not in ("main" , "master" ):
104- # Scenario 2: Clone a specific branch with shallow depth
105- clone_cmd = [
106- "git" ,
107- "clone" ,
108- "--recurse-submodules" ,
109- "--depth=1" ,
110- "--single-branch" ,
111- "--branch" ,
112- branch ,
113- url ,
114- local_path ,
115- ]
116- return await _run_git_command (* clone_cmd )
117-
118- # Scenario 3: Clone the default branch with shallow depth
119- clone_cmd = ["git" , "clone" , "--recurse-submodules" , "--depth=1" , "--single-branch" , url , local_path ]
120- return await _run_git_command (* clone_cmd )
87+ if not commit :
88+ clone_cmd += ["--depth=1" ]
89+ if branch and branch .lower () not in ("main" , "master" ):
90+ clone_cmd += ["--branch" , branch ]
91+
92+ clone_cmd += [url , local_path ]
93+
94+ # Clone the repository
95+ await _run_command (* clone_cmd )
96+
97+ if commit or partial_clone :
98+ checkout_cmd = ["git" , "-C" , local_path ]
99+
100+ if partial_clone :
101+ checkout_cmd += ["sparse-checkout" , "set" , config .subpath .lstrip ("/" )]
102+
103+ if commit :
104+ checkout_cmd += ["checkout" , commit ]
105+
106+ # Check out the specific commit and/or subpath
107+ await _run_command (* checkout_cmd )
121108
122109
123110async def _check_repo_exists (url : str ) -> bool :
@@ -176,7 +163,7 @@ async def fetch_remote_branch_list(url: str) -> List[str]:
176163 A list of branch names available in the remote repository.
177164 """
178165 fetch_branches_command = ["git" , "ls-remote" , "--heads" , url ]
179- stdout , _ = await _run_git_command (* fetch_branches_command )
166+ stdout , _ = await _run_command (* fetch_branches_command )
180167 stdout_decoded = stdout .decode ()
181168
182169 return [
@@ -186,41 +173,28 @@ async def fetch_remote_branch_list(url: str) -> List[str]:
186173 ]
187174
188175
189- async def _run_git_command (* args : str ) -> Tuple [bytes , bytes ]:
176+ async def _run_command (* args : str ) -> Tuple [bytes , bytes ]:
190177 """
191- Execute a Git command asynchronously and captures its output.
178+ Execute a command asynchronously and captures its output.
192179
193180 Parameters
194181 ----------
195182 *args : str
196- The Git command and its arguments to execute.
183+ The command and its arguments to execute.
197184
198185 Returns
199186 -------
200187 Tuple[bytes, bytes]
201- A tuple containing the stdout and stderr of the Git command.
188+ A tuple containing the stdout and stderr of the command.
202189
203190 Raises
204191 ------
205192 RuntimeError
206- If Git is not installed or if the Git command exits with a non-zero status.
193+ If command exits with a non-zero status.
207194 """
208- # Check if Git is installed
209- try :
210- version_proc = await asyncio .create_subprocess_exec (
211- "git" ,
212- "--version" ,
213- stdout = asyncio .subprocess .PIPE ,
214- stderr = asyncio .subprocess .PIPE ,
215- )
216- _ , stderr = await version_proc .communicate ()
217- if version_proc .returncode != 0 :
218- error_message = stderr .decode ().strip () if stderr else "Git command not found"
219- raise RuntimeError (f"Git is not installed or not accessible: { error_message } " )
220- except FileNotFoundError as exc :
221- raise RuntimeError ("Git is not installed. Please install Git before proceeding." ) from exc
195+ await check_git_installed ()
222196
223- # Execute the requested Git command
197+ # Execute the requested command
224198 proc = await asyncio .create_subprocess_exec (
225199 * args ,
226200 stdout = asyncio .subprocess .PIPE ,
@@ -229,11 +203,36 @@ async def _run_git_command(*args: str) -> Tuple[bytes, bytes]:
229203 stdout , stderr = await proc .communicate ()
230204 if proc .returncode != 0 :
231205 error_message = stderr .decode ().strip ()
232- raise RuntimeError (f"Git command failed: { ' ' .join (args )} \n Error: { error_message } " )
206+ raise RuntimeError (f"Command failed: { ' ' .join (args )} \n Error: { error_message } " )
233207
234208 return stdout , stderr
235209
236210
211+ async def check_git_installed () -> None :
212+ """
213+ Check if Git is installed and accessible on the system.
214+
215+ Raises
216+ ------
217+ RuntimeError
218+ If Git is not installed or if the Git command exits with a non-zero status.
219+ """
220+ try :
221+ proc = await asyncio .create_subprocess_exec (
222+ "git" ,
223+ "--version" ,
224+ stdout = asyncio .subprocess .PIPE ,
225+ stderr = asyncio .subprocess .PIPE ,
226+ )
227+ _ , stderr = await proc .communicate ()
228+ if proc .returncode != 0 :
229+ error_message = stderr .decode ().strip () if stderr else "Git command not found"
230+ raise RuntimeError (f"Git is not installed or not accessible: { error_message } " )
231+
232+ except FileNotFoundError as exc :
233+ raise RuntimeError ("Git is not installed. Please install Git before proceeding." ) from exc
234+
235+
237236def _get_status_code (response : str ) -> int :
238237 """
239238 Extract the status code from an HTTP response.
0 commit comments