|
5 | 5 | from pathlib import Path
|
6 | 6 | from typing import TYPE_CHECKING
|
7 | 7 |
|
| 8 | +import git |
| 9 | + |
8 | 10 | from gitingest.config import DEFAULT_TIMEOUT
|
9 | 11 | from gitingest.utils.git_utils import (
|
10 | 12 | check_repo_exists,
|
11 | 13 | checkout_partial_clone,
|
12 |
| - create_git_auth_header, |
13 |
| - create_git_command, |
| 14 | + create_git_repo, |
14 | 15 | ensure_git_installed,
|
| 16 | + git_auth_context, |
15 | 17 | is_github_host,
|
16 | 18 | resolve_commit,
|
17 |
| - run_command, |
18 | 19 | )
|
19 | 20 | from gitingest.utils.logging_config import get_logger
|
20 | 21 | from gitingest.utils.os_utils import ensure_directory_exists_or_create
|
@@ -46,6 +47,8 @@ async def clone_repo(config: CloneConfig, *, token: str | None = None) -> None:
|
46 | 47 | ------
|
47 | 48 | ValueError
|
48 | 49 | If the repository is not found, if the provided URL is invalid, or if the token format is invalid.
|
| 50 | + RuntimeError |
| 51 | + If Git operations fail during the cloning process. |
49 | 52 |
|
50 | 53 | """
|
51 | 54 | # Extract and validate query parameters
|
@@ -83,41 +86,91 @@ async def clone_repo(config: CloneConfig, *, token: str | None = None) -> None:
|
83 | 86 | commit = await resolve_commit(config, token=token)
|
84 | 87 | logger.debug("Resolved commit", extra={"commit": commit})
|
85 | 88 |
|
86 |
| - clone_cmd = ["git"] |
87 |
| - if token and is_github_host(url): |
88 |
| - clone_cmd += ["-c", create_git_auth_header(token, url=url)] |
89 |
| - |
90 |
| - clone_cmd += ["clone", "--single-branch", "--no-checkout", "--depth=1"] |
91 |
| - if partial_clone: |
92 |
| - clone_cmd += ["--filter=blob:none", "--sparse"] |
93 |
| - |
94 |
| - clone_cmd += [url, local_path] |
95 |
| - |
96 |
| - # Clone the repository |
97 |
| - logger.info("Executing git clone command", extra={"command": " ".join([*clone_cmd[:-1], "<url>", local_path])}) |
98 |
| - await run_command(*clone_cmd) |
99 |
| - logger.info("Git clone completed successfully") |
| 89 | + # Clone the repository using GitPython with proper authentication |
| 90 | + logger.info("Executing git clone operation", extra={"url": "<redacted>", "local_path": local_path}) |
| 91 | + try: |
| 92 | + clone_kwargs = { |
| 93 | + "single_branch": True, |
| 94 | + "no_checkout": True, |
| 95 | + "depth": 1, |
| 96 | + } |
| 97 | + |
| 98 | + with git_auth_context(url, token) as (git_cmd, auth_url): |
| 99 | + if partial_clone: |
| 100 | + # For partial clones, use git.Git() with filter and sparse options |
| 101 | + cmd_args = ["--single-branch", "--no-checkout", "--depth=1"] |
| 102 | + cmd_args.extend(["--filter=blob:none", "--sparse"]) |
| 103 | + cmd_args.extend([auth_url, local_path]) |
| 104 | + git_cmd.clone(*cmd_args) |
| 105 | + elif token and is_github_host(url): |
| 106 | + # For authenticated GitHub repos, use git_cmd with auth URL |
| 107 | + cmd_args = ["--single-branch", "--no-checkout", "--depth=1", auth_url, local_path] |
| 108 | + git_cmd.clone(*cmd_args) |
| 109 | + else: |
| 110 | + # For non-authenticated repos, use the standard GitPython method |
| 111 | + git.Repo.clone_from(url, local_path, **clone_kwargs) |
| 112 | + |
| 113 | + logger.info("Git clone completed successfully") |
| 114 | + except git.GitCommandError as exc: |
| 115 | + msg = f"Git clone failed: {exc}" |
| 116 | + raise RuntimeError(msg) from exc |
100 | 117 |
|
101 | 118 | # Checkout the subpath if it is a partial clone
|
102 | 119 | if partial_clone:
|
103 | 120 | logger.info("Setting up partial clone for subpath", extra={"subpath": config.subpath})
|
104 | 121 | await checkout_partial_clone(config, token=token)
|
105 | 122 | logger.debug("Partial clone setup completed")
|
106 | 123 |
|
107 |
| - git = create_git_command(["git"], local_path, url, token) |
| 124 | + # Perform post-clone operations |
| 125 | + await _perform_post_clone_operations(config, local_path, url, token, commit) |
108 | 126 |
|
109 |
| - # Ensure the commit is locally available |
110 |
| - logger.debug("Fetching specific commit", extra={"commit": commit}) |
111 |
| - await run_command(*git, "fetch", "--depth=1", "origin", commit) |
| 127 | + logger.info("Git clone operation completed successfully", extra={"local_path": local_path}) |
112 | 128 |
|
113 |
| - # Write the work-tree at that commit |
114 |
| - logger.info("Checking out commit", extra={"commit": commit}) |
115 |
| - await run_command(*git, "checkout", commit) |
116 | 129 |
|
117 |
| - # Update submodules |
118 |
| - if config.include_submodules: |
119 |
| - logger.info("Updating submodules") |
120 |
| - await run_command(*git, "submodule", "update", "--init", "--recursive", "--depth=1") |
121 |
| - logger.debug("Submodules updated successfully") |
| 130 | +async def _perform_post_clone_operations( |
| 131 | + config: CloneConfig, |
| 132 | + local_path: str, |
| 133 | + url: str, |
| 134 | + token: str | None, |
| 135 | + commit: str, |
| 136 | +) -> None: |
| 137 | + """Perform post-clone operations like fetching, checkout, and submodule updates. |
122 | 138 |
|
123 |
| - logger.info("Git clone operation completed successfully", extra={"local_path": local_path}) |
| 139 | + Parameters |
| 140 | + ---------- |
| 141 | + config : CloneConfig |
| 142 | + The configuration for cloning the repository. |
| 143 | + local_path : str |
| 144 | + The local path where the repository was cloned. |
| 145 | + url : str |
| 146 | + The repository URL. |
| 147 | + token : str | None |
| 148 | + GitHub personal access token (PAT) for accessing private repositories. |
| 149 | + commit : str |
| 150 | + The commit SHA to checkout. |
| 151 | +
|
| 152 | + Raises |
| 153 | + ------ |
| 154 | + RuntimeError |
| 155 | + If any Git operation fails. |
| 156 | +
|
| 157 | + """ |
| 158 | + try: |
| 159 | + repo = create_git_repo(local_path, url, token) |
| 160 | + |
| 161 | + # Ensure the commit is locally available |
| 162 | + logger.debug("Fetching specific commit", extra={"commit": commit}) |
| 163 | + repo.git.fetch("--depth=1", "origin", commit) |
| 164 | + |
| 165 | + # Write the work-tree at that commit |
| 166 | + logger.info("Checking out commit", extra={"commit": commit}) |
| 167 | + repo.git.checkout(commit) |
| 168 | + |
| 169 | + # Update submodules |
| 170 | + if config.include_submodules: |
| 171 | + logger.info("Updating submodules") |
| 172 | + repo.git.submodule("update", "--init", "--recursive", "--depth=1") |
| 173 | + logger.debug("Submodules updated successfully") |
| 174 | + except git.GitCommandError as exc: |
| 175 | + msg = f"Git operation failed: {exc}" |
| 176 | + raise RuntimeError(msg) from exc |
0 commit comments