@@ -13,7 +13,7 @@ index 1b776e8..b486fa1 100644
1313@@ -87,7 +86,12 @@ async def clone_repo(config: CloneConfig, *, token: str | None = None) -> None:
1414 commit = await resolve_commit(config, token=token)
1515 logger.debug("Resolved commit", extra={"commit": commit})
16-
16+
1717- # Clone the repository using GitPython with proper authentication
1818+ # Prepare URL with authentication if needed
1919+ clone_url = url
@@ -27,7 +27,7 @@ index 1b776e8..b486fa1 100644
2727@@ -96,20 +100,18 @@ async def clone_repo(config: CloneConfig, *, token: str | None = None) -> None:
2828 "depth": 1,
2929 }
30-
30+
3131- with git_auth_context(url, token) as (git_cmd, auth_url):
3232+ if partial_clone:
3333+ # GitPython doesn't directly support --filter and --sparse in clone
@@ -68,17 +68,17 @@ index 1c1a986..b7f293a 100644
6868- from typing import TYPE_CHECKING, Final, Generator, Iterable
6969+ from typing import TYPE_CHECKING, Final, Iterable
7070 from urllib.parse import urlparse, urlunparse
71-
71+
7272 import git
7373+ import httpx
7474+ from starlette.status import HTTP_200_OK, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND
75-
75+
7676 from gitingest.utils.compat_func import removesuffix
7777 from gitingest.utils.exceptions import InvalidGitHubTokenError
7878@@ -135,15 +136,35 @@ async def check_repo_exists(url: str, token: str | None = None) -> bool:
7979 bool
8080 ``True`` if the repository exists, ``False`` otherwise.
81-
81+
8282+ Raises
8383+ ------
8484+ RuntimeError
@@ -99,7 +99,7 @@ index 1c1a986..b7f293a 100644
9999+ base_api = "https://api.github.com" if host == "github.com" else f"https://{host}/api/v3"
100100+ url = f"{base_api}/repos/{owner}/{repo}"
101101+ headers["Authorization"] = f"Bearer {token}"
102-
102+
103103- return True
104104+ async with httpx.AsyncClient(follow_redirects=True) as client:
105105+ try:
@@ -115,11 +115,11 @@ index 1c1a986..b7f293a 100644
115115+ return False
116116+ msg = f"Unexpected HTTP status {status_code} for {url}"
117117+ raise RuntimeError(msg)
118-
119-
118+
119+
120120 def _parse_github_url(url: str) -> tuple[str, str, str]:
121121@@ -217,6 +238,13 @@ async def fetch_remote_branches_or_tags(url: str, *, ref_type: str, token: str |
122-
122+
123123 # Use GitPython to get remote references
124124 try:
125125+ git_cmd = git.Git()
@@ -131,25 +131,25 @@ index 1c1a986..b7f293a 100644
131131+
132132 fetch_tags = ref_type == "tags"
133133 to_fetch = "tags" if fetch_tags else "heads"
134-
134+
135135@@ -226,11 +254,8 @@ async def fetch_remote_branches_or_tags(url: str, *, ref_type: str, token: str |
136136 cmd_args.append("--refs") # Filter out peeled tag objects
137137 cmd_args.append(url)
138-
138+
139139- # Run the command with proper authentication
140140- with git_auth_context(url, token) as (git_cmd, auth_url):
141141- # Replace the URL in cmd_args with the authenticated URL
142142- cmd_args[-1] = auth_url # URL is the last argument
143143- output = git_cmd.ls_remote(*cmd_args)
144144+ # Run the command using git_cmd.ls_remote() method
145145+ output = git_cmd.ls_remote(*cmd_args)
146-
146+
147147 # Parse output
148148 return [
149149@@ -314,70 +339,6 @@ def create_git_auth_header(token: str, url: str = "https://github.com") -> str:
150150 return f"http.https://{hostname}/.extraheader=Authorization: Basic {basic}"
151-
152-
151+
152+
153153- def create_authenticated_url(url: str, token: str | None = None) -> str:
154154- """Create an authenticated URL for Git operations.
155155-
@@ -216,9 +216,9 @@ index 1c1a986..b7f293a 100644
216216-
217217 def validate_github_token(token: str) -> None:
218218 """Validate the format of a GitHub Personal Access Token.
219-
219+
220220@@ -479,9 +440,15 @@ async def _resolve_ref_to_sha(url: str, pattern: str, token: str | None = None)
221-
221+
222222 """
223223 try:
224224- # Execute ls-remote command with proper authentication
@@ -234,20 +234,20 @@ index 1c1a986..b7f293a 100644
234234+ # Execute ls-remote command
235235+ output = git_cmd.ls_remote(auth_url, pattern)
236236 lines = output.splitlines()
237-
237+
238238 sha = _pick_commit_sha(lines)
239239@@ -490,7 +457,7 @@ async def _resolve_ref_to_sha(url: str, pattern: str, token: str | None = None)
240240 raise ValueError(msg)
241-
241+
242242 except git.GitCommandError as exc:
243243- msg = f"Failed to resolve {pattern} in {url}:\n{exc}"
244244+ msg = f"Failed to resolve {pattern} in {url}: {exc}"
245245 raise ValueError(msg) from exc
246-
246+
247247 return sha
248248@@ -547,8 +514,6 @@ def _add_token_to_url(url: str, token: str) -> str:
249249 The URL with embedded authentication.
250-
250+
251251 """
252252- from urllib.parse import urlparse, urlunparse
253253-
@@ -264,35 +264,35 @@ index f2f2ae9..03f52f1 100644
264264 _cleanup_repository(clone_config)
265265- return IngestErrorResponse(error=f"{exc!s}")
266266+ return IngestErrorResponse(error=str(exc))
267-
267+
268268 if len(content) > MAX_DISPLAY_SIZE:
269269 content = (
270270diff --git a/tests/test_clone.py b/tests/test_clone.py
271271index 6abbd87..8c44523 100644
272272--- a/tests/test_clone.py
273273+++ b/tests/test_clone.py
274274@@ -8,8 +8,11 @@ from __future__ import annotations
275-
275+
276276 import sys
277277 from typing import TYPE_CHECKING
278278+ from unittest.mock import AsyncMock
279-
279+
280280+ import httpx
281281 import pytest
282282+ from starlette.status import HTTP_200_OK, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND
283-
283+
284284 from gitingest.clone import clone_repo
285285 from gitingest.schemas import CloneConfig
286286@@ -18,7 +21,6 @@ from tests.conftest import DEMO_URL, LOCAL_REPO_PATH
287-
287+
288288 if TYPE_CHECKING:
289289 from pathlib import Path
290290- from unittest.mock import AsyncMock
291-
291+
292292 from pytest_mock import MockerFixture
293-
293+
294294@@ -91,30 +93,24 @@ async def test_clone_nonexistent_repository(repo_exists_true: AsyncMock) -> None
295-
295+
296296 @pytest.mark.asyncio
297297 @pytest.mark.parametrize(
298298- ("git_command_succeeds", "expected"),
@@ -325,23 +325,23 @@ index 6abbd87..8c44523 100644
325325+ mock_client.__aenter__.return_value = mock_client # context-manager protocol
326326+ mock_client.head.return_value = httpx.Response(status_code=status_code)
327327+ mocker.patch("httpx.AsyncClient", return_value=mock_client)
328-
328+
329329 result = await check_repo_exists(DEMO_URL)
330-
330+
331331 assert result is expected
332332- mock_resolve.assert_called_once_with(DEMO_URL, "HEAD", token=None)
333-
334-
333+
334+
335335 @pytest.mark.asyncio
336336@@ -206,18 +202,19 @@ async def test_clone_with_include_submodules(gitpython_mocks: dict) -> None:
337-
338-
337+
338+
339339 @pytest.mark.asyncio
340340- async def test_check_repo_exists_with_auth_token(mocker: MockerFixture) -> None:
341341- """Test ``check_repo_exists`` with authentication token.
342342+ async def test_check_repo_exists_with_redirect(mocker: MockerFixture) -> None:
343343+ """Test ``check_repo_exists`` when a redirect (302) is returned.
344-
344+
345345- Given a GitHub URL and a token:
346346+ Given a URL that responds with "302 Found":
347347 When ``check_repo_exists`` is called,
@@ -355,11 +355,11 @@ index 6abbd87..8c44523 100644
355355+ mock_process.communicate.return_value = (b"302\n", b"")
356356+ mock_process.returncode = 0 # Simulate successful request
357357+ mock_exec.return_value = mock_process
358-
358+
359359- test_token = "token123" # noqa: S105
360360- result = await check_repo_exists("https://github.com/test/repo", token=test_token)
361361+ repo_exists = await check_repo_exists(DEMO_URL)
362-
362+
363363- assert result is True
364364- mock_resolve.assert_called_once_with("https://github.com/test/repo", "HEAD", token=test_token)
365365+ assert repo_exists is False
0 commit comments