|
27 | 27 | # Initialize logger for this module |
28 | 28 | logger = get_logger(__name__) |
29 | 29 |
|
| 30 | +_ASKPASS_SCRIPT_NAME = "gitingest-askpass.sh" |
| 31 | + |
| 32 | + |
| 33 | +def _write_git_askpass_script(repo_path: Path) -> Path: |
| 34 | + """Write a small askpass helper into ``.git`` that reads the token from env. |
| 35 | +
|
| 36 | + The script never embeds secrets; it prints the username and reads the token from |
| 37 | + ``GITINGEST_GIT_PASSWORD``. |
| 38 | + """ |
| 39 | + git_dir = repo_path / ".git" |
| 40 | + git_dir.mkdir(parents=True, exist_ok=True) |
| 41 | + |
| 42 | + askpass_path = git_dir / _ASKPASS_SCRIPT_NAME |
| 43 | + askpass_path.write_text( |
| 44 | + "#!/bin/sh\n" |
| 45 | + 'case "$1" in\n' |
| 46 | + ' Username*) echo "x-access-token" ;;\n' |
| 47 | + ' Password*) echo "${GITINGEST_GIT_PASSWORD:-}" ;;\n' |
| 48 | + ' *) echo "" ;;\n' |
| 49 | + "esac\n", |
| 50 | + encoding="utf-8", |
| 51 | + ) |
| 52 | + try: |
| 53 | + askpass_path.chmod(0o700) |
| 54 | + except OSError: |
| 55 | + # Best-effort on platforms where chmod may be unsupported. |
| 56 | + pass |
| 57 | + return askpass_path |
| 58 | + |
| 59 | + |
| 60 | +def _configure_submodule_auth(repo: git.Repo, *, token: str | None, url: str, local_path: str) -> None: |
| 61 | + """Disable interactive prompts and provide an askpass hook for private submodules.""" |
| 62 | + try: |
| 63 | + repo.git.update_environment(GIT_TERMINAL_PROMPT="0") |
| 64 | + except Exception: |
| 65 | + # Best-effort: if the GitPython object doesn't support env updates, continue. |
| 66 | + return |
| 67 | + |
| 68 | + if not (token and is_github_host(url)): |
| 69 | + return |
| 70 | + |
| 71 | + try: |
| 72 | + askpass_path = _write_git_askpass_script(Path(local_path)) |
| 73 | + except OSError: |
| 74 | + logger.exception("Failed to write GIT_ASKPASS helper", extra={"local_path": local_path}) |
| 75 | + return |
| 76 | + |
| 77 | + repo.git.update_environment( |
| 78 | + GIT_ASKPASS=str(askpass_path), |
| 79 | + GIT_TERMINAL_PROMPT="0", |
| 80 | + GITINGEST_GIT_PASSWORD=token, |
| 81 | + ) |
| 82 | + |
30 | 83 |
|
31 | 84 | @async_timeout(DEFAULT_TIMEOUT) |
32 | 85 | async def clone_repo(config: CloneConfig, *, token: str | None = None) -> None: |
@@ -169,6 +222,7 @@ async def _perform_post_clone_operations( |
169 | 222 | # Update submodules |
170 | 223 | if config.include_submodules: |
171 | 224 | logger.info("Updating submodules") |
| 225 | + _configure_submodule_auth(repo, token=token, url=url, local_path=local_path) |
172 | 226 | repo.git.submodule("update", "--init", "--recursive", "--depth=1") |
173 | 227 | logger.debug("Submodules updated successfully") |
174 | 228 | except git.GitCommandError as exc: |
|
0 commit comments