Skip to content

Commit 193e3f0

Browse files
committed
fix(scripts): support exporting from repo root URLs
Allow GitHub owner/repo URLs to be parsed without requiring a directory path and treat missing paths as repository root during export. Skip copying the .git directory when exporting from root and keep path normalization compatible with optional source paths.
1 parent 2551afe commit 193e3f0

1 file changed

Lines changed: 33 additions & 19 deletions

File tree

scripts/git-export.py

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -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

111112
def 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

Comments
 (0)