@@ -29,16 +29,19 @@ class CloneConfig:
29
29
The specific commit hash to check out after cloning (default is None).
30
30
branch : str, optional
31
31
The branch to clone (default is None).
32
+ subpath : str
33
+ The subpath to clone from the repository (default is "/").
32
34
"""
33
35
34
36
url : str
35
37
local_path : str
36
38
commit : Optional [str ] = None
37
39
branch : Optional [str ] = None
40
+ subpath : str = "/"
38
41
39
42
40
43
@async_timeout (TIMEOUT )
41
- async def clone_repo (config : CloneConfig ) -> Tuple [ bytes , bytes ] :
44
+ async def clone_repo (config : CloneConfig ) -> None :
42
45
"""
43
46
Clone a repository to a local path based on the provided configuration.
44
47
@@ -49,35 +52,21 @@ async def clone_repo(config: CloneConfig) -> Tuple[bytes, bytes]:
49
52
Parameters
50
53
----------
51
54
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.
62
56
63
57
Raises
64
58
------
65
59
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 .
67
61
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 .
69
63
"""
70
64
# Extract and validate query parameters
71
65
url : str = config .url
72
66
local_path : str = config .local_path
73
67
commit : Optional [str ] = config .commit
74
68
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 != "/"
81
70
82
71
# Create parent directory if it doesn't exist
83
72
parent_dir = Path (local_path ).parent
@@ -90,34 +79,32 @@ async def clone_repo(config: CloneConfig) -> Tuple[bytes, bytes]:
90
79
if not await _check_repo_exists (url ):
91
80
raise ValueError ("Repository not found, make sure it is public" )
92
81
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" ]
98
83
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" ]
102
86
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 )
121
108
122
109
123
110
async def _check_repo_exists (url : str ) -> bool :
@@ -176,7 +163,7 @@ async def fetch_remote_branch_list(url: str) -> List[str]:
176
163
A list of branch names available in the remote repository.
177
164
"""
178
165
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 )
180
167
stdout_decoded = stdout .decode ()
181
168
182
169
return [
@@ -186,41 +173,28 @@ async def fetch_remote_branch_list(url: str) -> List[str]:
186
173
]
187
174
188
175
189
- async def _run_git_command (* args : str ) -> Tuple [bytes , bytes ]:
176
+ async def _run_command (* args : str ) -> Tuple [bytes , bytes ]:
190
177
"""
191
- Execute a Git command asynchronously and captures its output.
178
+ Execute a command asynchronously and captures its output.
192
179
193
180
Parameters
194
181
----------
195
182
*args : str
196
- The Git command and its arguments to execute.
183
+ The command and its arguments to execute.
197
184
198
185
Returns
199
186
-------
200
187
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.
202
189
203
190
Raises
204
191
------
205
192
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.
207
194
"""
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 ()
222
196
223
- # Execute the requested Git command
197
+ # Execute the requested command
224
198
proc = await asyncio .create_subprocess_exec (
225
199
* args ,
226
200
stdout = asyncio .subprocess .PIPE ,
@@ -229,11 +203,36 @@ async def _run_git_command(*args: str) -> Tuple[bytes, bytes]:
229
203
stdout , stderr = await proc .communicate ()
230
204
if proc .returncode != 0 :
231
205
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 } " )
233
207
234
208
return stdout , stderr
235
209
236
210
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
+
237
236
def _get_status_code (response : str ) -> int :
238
237
"""
239
238
Extract the status code from an HTTP response.
0 commit comments