11from __future__ import annotations
22
3+ import os
34import shutil
45import subprocess
56from dataclasses import dataclass
@@ -27,6 +28,11 @@ class CloneResult:
2728class CloneManager :
2829 """
2930 Handles local repository cloning for deeper file-level analysis.
31+
32+ Important behavior:
33+ - public repositories can be cloned anonymously over HTTPS
34+ - private repositories require an authenticated clone URL
35+ - SSH remains supported if enabled in settings
3036 """
3137
3238 def __init__ (self , settings : Settings ) -> None :
@@ -114,6 +120,50 @@ def _resolve_clone_url(self, repo: RepoMetadata) -> str:
114120 return repo .links .ssh_url
115121
116122 if repo .links .clone_url :
117- return str (repo .links .clone_url )
123+ https_clone_url = str (repo .links .clone_url )
124+ return self ._build_authenticated_https_clone_url (https_clone_url )
125+
126+ raise CloneError (f"HTTPS clone URL is missing for repository { repo .full_name } ." )
127+
128+ def _build_authenticated_https_clone_url (self , clone_url : str ) -> str :
129+ """
130+ Build an authenticated HTTPS clone URL when a GitHub token is available.
131+
132+ Example:
133+ https://github.com/owner/repo.git
134+ becomes:
135+ https://x-access-token:<TOKEN>@github.com/owner/repo.git
136+
137+ Notes:
138+ - This is only applied to GitHub HTTPS URLs.
139+ - If no token is available, the original URL is returned unchanged.
140+ - The authenticated URL must never be logged.
141+ """
142+ token = self ._resolve_github_token ()
143+ if not token :
144+ return clone_url
145+
146+ github_prefix = "https://github.com/"
147+ if not clone_url .startswith (github_prefix ):
148+ return clone_url
149+
150+ return clone_url .replace (
151+ github_prefix ,
152+ f"https://x-access-token:{ token } @github.com/" ,
153+ 1 ,
154+ )
155+
156+ def _resolve_github_token (self ) -> str | None :
157+ """
158+ Resolve the GitHub token from environment variables.
159+
160+ This works both:
161+ - locally when .env is loaded into the environment
162+ - on Streamlit Community Cloud after secrets are copied into os.environ
163+ """
164+ token = os .getenv ("GITHUB_TOKEN" )
165+ if not token :
166+ return None
118167
119- raise CloneError (f"HTTPS clone URL is missing for repository { repo .full_name } ." )
168+ token = token .strip ()
169+ return token or None
0 commit comments