@@ -63,9 +63,9 @@ def normalize_source_path(path: str) -> str:
6363 return "/" .join (parts )
6464
6565
66- def parse_github_directory_url (url : str ) -> tuple [str , str , str | None ]:
66+ def parse_github_directory_url (url : str ) -> tuple [str , str | None , str | None ]:
6767 """
68- Parse a GitHub directory URL into (repo_url, source_path, ref).
68+ Parse a GitHub URL into (repo_url, source_path, ref).
6969
7070 Supported examples:
7171 - https://github.com/org/repo/lang/ruby
@@ -80,10 +80,8 @@ def parse_github_directory_url(url: str) -> tuple[str, str, str | None]:
8080 raise GitExportError (f"Not a supported GitHub URL: { url } " )
8181
8282 parts = [p for p in parsed .path .split ("/" ) if p ]
83- if len (parts ) < 3 :
84- raise GitExportError (
85- f"GitHub URL must include a directory path after owner/repo (got: { url } )"
86- )
83+ if len (parts ) < 2 :
84+ raise GitExportError (f"GitHub URL must include owner/repo (got: { url } )" )
8785
8886 owner = parts [0 ]
8987 repo = parts [1 ]
@@ -92,9 +90,11 @@ def parse_github_directory_url(url: str) -> tuple[str, str, str | None]:
9290
9391 rest = parts [2 :]
9492 ref : str | None = None
95- source : str
93+ source : str | None = None
9694
97- if rest [0 ] in ("tree" , "blob" ):
95+ if not rest :
96+ source = None
97+ elif rest [0 ] in ("tree" , "blob" ):
9898 if len (rest ) < 3 :
9999 raise GitExportError (
100100 f"tree/blob URLs must include ref and directory path, got: { url } "
@@ -105,7 +105,8 @@ def parse_github_directory_url(url: str) -> tuple[str, str, str | None]:
105105 source = "/" .join (rest )
106106
107107 repo_url = f"https://github.com/{ owner } /{ repo } .git"
108- return repo_url , normalize_source_path (source ), ref
108+ normalized_source = normalize_source_path (source ) if source is not None else None
109+ return repo_url , normalized_source , ref
109110
110111
111112def prepare_output_dir (output_dir : Path , force : bool ) -> None :
@@ -148,11 +149,11 @@ def export_directory(
148149 verbose : bool ,
149150) -> None :
150151 start_total = time .perf_counter ()
151- source_path = normalize_source_path ( source_path )
152+ source_path = source_path . strip ( "/" )
152153 output_dir = output_dir .resolve ()
153154
154155 info (f"Repository: { repo_url } " )
155- info (f"Source path: { source_path } " )
156+ info (f"Source path: { source_path or '(repo root)' } " )
156157 info (f"Ref: { ref or 'default branch' } " )
157158 info (f"Output: { output_dir } " )
158159
@@ -186,12 +187,20 @@ def export_directory(
186187 cwd = clone_dir ,
187188 verbose = verbose ,
188189 )
189- run_git (
190- git_bin ,
191- ["sparse-checkout" , "set" , "--" , source_path ],
192- cwd = clone_dir ,
193- verbose = verbose ,
194- )
190+ if source_path :
191+ run_git (
192+ git_bin ,
193+ ["sparse-checkout" , "set" , "--" , source_path ],
194+ cwd = clone_dir ,
195+ verbose = verbose ,
196+ )
197+ else :
198+ run_git (
199+ git_bin ,
200+ ["sparse-checkout" , "disable" ],
201+ cwd = clone_dir ,
202+ verbose = verbose ,
203+ )
195204 info (f"Step 2/6 complete in { time .perf_counter () - step_start :.1f} s" )
196205
197206 info ("Step 3/6: checking out requested ref/path" )
@@ -214,10 +223,10 @@ def export_directory(
214223 info (f"Step 3/6 complete in { time .perf_counter () - step_start :.1f} s" )
215224
216225 info ("Step 4/6: validating source directory" )
217- source_dir = clone_dir / source_path
226+ source_dir = clone_dir if not source_path else clone_dir / source_path
218227 if not source_dir .exists () or not source_dir .is_dir ():
219228 raise GitExportError (
220- f"Source directory not found after checkout: { source_path } \n "
229+ f"Source directory not found after checkout: { source_path or '.' } \n "
221230 f"Repository: { repo_url } \n "
222231 f"Ref: { ref or 'default branch' } "
223232 )
@@ -235,6 +244,9 @@ def export_directory(
235244 if total_children == 0 :
236245 info ("Source directory is empty" )
237246 for idx , child in enumerate (children , start = 1 ):
247+ if child .name == ".git" :
248+ info (f" - [{ idx } /{ total_children } ] { child .name } (skipped)" )
249+ continue
238250 info (f" - [{ idx } /{ total_children } ] { child .name } " )
239251 copy_entry (child , output_dir / child .name )
240252 info (f"Step 6/6 complete in { time .perf_counter () - step_start :.1f} s" )
@@ -276,6 +288,8 @@ def main(argv: list[str]) -> int:
276288 repo_url , source_path , inferred_ref = parse_github_directory_url (
277289 args .source
278290 )
291+ if source_path is None :
292+ source_path = normalize_source_path (args .path ) if args .path else ""
279293 ref = args .ref if args .ref is not None else inferred_ref
280294 else :
281295 if not args .path :
0 commit comments