Skip to content

Commit 82b1452

Browse files
committed
idk
1 parent 4561fc1 commit 82b1452

File tree

2 files changed

+80
-71
lines changed

2 files changed

+80
-71
lines changed

src/gitingest/clone.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99

1010
from gitingest.config import DEFAULT_TIMEOUT
1111
from gitingest.utils.git_utils import (
12-
_add_token_to_url,
1312
check_repo_exists,
1413
checkout_partial_clone,
1514
create_git_repo,
1615
ensure_git_installed,
16+
git_auth_context,
1717
is_github_host,
1818
resolve_commit,
1919
)
@@ -86,12 +86,7 @@ async def clone_repo(config: CloneConfig, *, token: str | None = None) -> None:
8686
commit = await resolve_commit(config, token=token)
8787
logger.debug("Resolved commit", extra={"commit": commit})
8888

89-
# Prepare URL with authentication if needed
90-
clone_url = url
91-
if token and is_github_host(url):
92-
clone_url = _add_token_to_url(url, token)
93-
94-
# Clone the repository using GitPython
89+
# Clone the repository using GitPython with proper authentication
9590
logger.info("Executing git clone operation", extra={"url": "<redacted>", "local_path": local_path})
9691
try:
9792
clone_kwargs = {
@@ -100,17 +95,22 @@ async def clone_repo(config: CloneConfig, *, token: str | None = None) -> None:
10095
"depth": 1,
10196
}
10297

103-
if partial_clone:
104-
# GitPython doesn't directly support --filter and --sparse in clone
105-
# We'll need to use git.Git() for the initial clone with these options
106-
git_cmd = git.Git()
107-
cmd_args = ["--single-branch", "--no-checkout", "--depth=1"]
98+
with git_auth_context(url, token) as git_cmd:
10899
if partial_clone:
109-
cmd_args.extend(["--filter=blob:none", "--sparse"])
110-
cmd_args.extend([clone_url, local_path])
111-
git_cmd.clone(*cmd_args)
112-
else:
113-
git.Repo.clone_from(clone_url, local_path, **clone_kwargs)
100+
# GitPython doesn't directly support --filter and --sparse in clone
101+
# We'll need to use git.Git() for the initial clone with these options
102+
cmd_args = ["--single-branch", "--no-checkout", "--depth=1"]
103+
if partial_clone:
104+
cmd_args.extend(["--filter=blob:none", "--sparse"])
105+
cmd_args.extend([url, local_path])
106+
git_cmd.clone(*cmd_args)
107+
# For regular clones without auth, we can use git.Repo.clone_from
108+
# But with auth, we need to use the configured git_cmd
109+
elif token and is_github_host(url):
110+
cmd_args = ["--single-branch", "--no-checkout", "--depth=1", url, local_path]
111+
git_cmd.clone(*cmd_args)
112+
else:
113+
git.Repo.clone_from(url, local_path, **clone_kwargs)
114114

115115
logger.info("Git clone completed successfully")
116116
except git.GitCommandError as exc:

src/gitingest/utils/git_utils.py

Lines changed: 63 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
import os
88
import re
99
import sys
10+
from contextlib import contextmanager, suppress
1011
from pathlib import Path
11-
from typing import TYPE_CHECKING, Final, Iterable
12-
from urllib.parse import urlparse, urlunparse
12+
from typing import TYPE_CHECKING, Final, Generator, Iterable
13+
from urllib.parse import urlparse
1314

1415
import git
1516

@@ -217,13 +218,6 @@ async def fetch_remote_branches_or_tags(url: str, *, ref_type: str, token: str |
217218

218219
# Use GitPython to get remote references
219220
try:
220-
git_cmd = git.Git()
221-
222-
# Prepare authentication if needed
223-
if token and is_github_host(url):
224-
auth_url = _add_token_to_url(url, token)
225-
url = auth_url
226-
227221
fetch_tags = ref_type == "tags"
228222
to_fetch = "tags" if fetch_tags else "heads"
229223

@@ -233,8 +227,9 @@ async def fetch_remote_branches_or_tags(url: str, *, ref_type: str, token: str |
233227
cmd_args.append("--refs") # Filter out peeled tag objects
234228
cmd_args.append(url)
235229

236-
# Run the command using git_cmd.ls_remote() method
237-
output = git_cmd.ls_remote(*cmd_args)
230+
# Run the command with proper authentication
231+
with git_auth_context(url, token) as git_cmd:
232+
output = git_cmd.ls_remote(*cmd_args)
238233

239234
# Parse output
240235
return [
@@ -318,6 +313,60 @@ def create_git_auth_header(token: str, url: str = "https://github.com") -> str:
318313
return f"http.https://{hostname}/.extraheader=Authorization: Basic {basic}"
319314

320315

316+
@contextmanager
317+
def git_auth_context(url: str, token: str | None = None) -> Generator[git.Git]:
318+
"""Context manager for GitPython authentication.
319+
320+
Creates a Git command object configured with authentication for GitHub repositories.
321+
Uses git's credential system instead of URL manipulation.
322+
323+
Parameters
324+
----------
325+
url : str
326+
The repository URL to check if authentication is needed.
327+
token : str | None
328+
GitHub personal access token (PAT) for accessing private repositories.
329+
330+
Yields
331+
------
332+
Generator[git.Git]
333+
Git command object configured with authentication.
334+
335+
"""
336+
git_cmd = git.Git()
337+
338+
if token and is_github_host(url):
339+
# Configure git to use the token for this hostname
340+
# This is equivalent to: git config credential.https://hostname.username x-oauth-basic
341+
# and: git config credential.https://hostname.helper store
342+
auth_header = create_git_auth_header(token, url)
343+
key, value = auth_header.split("=", 1)
344+
345+
# Set the auth configuration for this git command
346+
original_config = {}
347+
try:
348+
# Store original config if it exists
349+
with suppress(git.GitCommandError):
350+
original_config[key] = git_cmd.config("--get", key)
351+
352+
# Set the authentication
353+
git_cmd.config(key, value)
354+
355+
yield git_cmd
356+
357+
finally:
358+
# Restore original config
359+
try:
360+
if key in original_config:
361+
git_cmd.config(key, original_config[key])
362+
else:
363+
git_cmd.config("--unset", key)
364+
except git.GitCommandError:
365+
pass # Config cleanup failed, not critical
366+
else:
367+
yield git_cmd
368+
369+
321370
def validate_github_token(token: str) -> None:
322371
"""Validate the format of a GitHub Personal Access Token.
323372
@@ -419,15 +468,9 @@ async def _resolve_ref_to_sha(url: str, pattern: str, token: str | None = None)
419468
420469
"""
421470
try:
422-
git_cmd = git.Git()
423-
424-
# Prepare authentication if needed
425-
auth_url = url
426-
if token and is_github_host(url):
427-
auth_url = _add_token_to_url(url, token)
428-
429-
# Execute ls-remote command
430-
output = git_cmd.ls_remote(auth_url, pattern)
471+
# Execute ls-remote command with proper authentication
472+
with git_auth_context(url, token) as git_cmd:
473+
output = git_cmd.ls_remote(url, pattern)
431474
lines = output.splitlines()
432475

433476
sha = _pick_commit_sha(lines)
@@ -475,37 +518,3 @@ def _pick_commit_sha(lines: Iterable[str]) -> str | None:
475518
first_non_peeled = sha
476519

477520
return first_non_peeled # branch or lightweight tag (or None)
478-
479-
480-
def _add_token_to_url(url: str, token: str) -> str:
481-
"""Add authentication token to GitHub URL.
482-
483-
Parameters
484-
----------
485-
url : str
486-
The original GitHub URL.
487-
token : str
488-
The GitHub token to add.
489-
490-
Returns
491-
-------
492-
str
493-
The URL with embedded authentication.
494-
495-
"""
496-
parsed = urlparse(url)
497-
# Add token as username in URL (GitHub supports this)
498-
netloc = f"x-oauth-basic:{token}@{parsed.hostname}"
499-
if parsed.port:
500-
netloc += f":{parsed.port}"
501-
502-
return urlunparse(
503-
(
504-
parsed.scheme,
505-
netloc,
506-
parsed.path,
507-
parsed.params,
508-
parsed.query,
509-
parsed.fragment,
510-
),
511-
)

0 commit comments

Comments
 (0)