@@ -13,7 +13,7 @@ index 1b776e8..b486fa1 100644
13
13
@@ -87,7 +86,12 @@ async def clone_repo(config: CloneConfig, *, token: str | None = None) -> None:
14
14
commit = await resolve_commit(config, token=token)
15
15
logger.debug("Resolved commit", extra={"commit": commit})
16
-
16
+
17
17
- # Clone the repository using GitPython with proper authentication
18
18
+ # Prepare URL with authentication if needed
19
19
+ clone_url = url
@@ -27,7 +27,7 @@ index 1b776e8..b486fa1 100644
27
27
@@ -96,20 +100,18 @@ async def clone_repo(config: CloneConfig, *, token: str | None = None) -> None:
28
28
"depth": 1,
29
29
}
30
-
30
+
31
31
- with git_auth_context(url, token) as (git_cmd, auth_url):
32
32
+ if partial_clone:
33
33
+ # GitPython doesn't directly support --filter and --sparse in clone
@@ -68,17 +68,17 @@ index 1c1a986..b7f293a 100644
68
68
- from typing import TYPE_CHECKING, Final, Generator, Iterable
69
69
+ from typing import TYPE_CHECKING, Final, Iterable
70
70
from urllib.parse import urlparse, urlunparse
71
-
71
+
72
72
import git
73
73
+ import httpx
74
74
+ from starlette.status import HTTP_200_OK, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND
75
-
75
+
76
76
from gitingest.utils.compat_func import removesuffix
77
77
from gitingest.utils.exceptions import InvalidGitHubTokenError
78
78
@@ -135,15 +136,35 @@ async def check_repo_exists(url: str, token: str | None = None) -> bool:
79
79
bool
80
80
``True`` if the repository exists, ``False`` otherwise.
81
-
81
+
82
82
+ Raises
83
83
+ ------
84
84
+ RuntimeError
@@ -99,7 +99,7 @@ index 1c1a986..b7f293a 100644
99
99
+ base_api = "https://api.github.com" if host == "github.com" else f"https://{host}/api/v3"
100
100
+ url = f"{base_api}/repos/{owner}/{repo}"
101
101
+ headers["Authorization"] = f"Bearer {token}"
102
-
102
+
103
103
- return True
104
104
+ async with httpx.AsyncClient(follow_redirects=True) as client:
105
105
+ try:
@@ -115,11 +115,11 @@ index 1c1a986..b7f293a 100644
115
115
+ return False
116
116
+ msg = f"Unexpected HTTP status {status_code} for {url}"
117
117
+ raise RuntimeError(msg)
118
-
119
-
118
+
119
+
120
120
def _parse_github_url(url: str) -> tuple[str, str, str]:
121
121
@@ -217,6 +238,13 @@ async def fetch_remote_branches_or_tags(url: str, *, ref_type: str, token: str |
122
-
122
+
123
123
# Use GitPython to get remote references
124
124
try:
125
125
+ git_cmd = git.Git()
@@ -131,25 +131,25 @@ index 1c1a986..b7f293a 100644
131
131
+
132
132
fetch_tags = ref_type == "tags"
133
133
to_fetch = "tags" if fetch_tags else "heads"
134
-
134
+
135
135
@@ -226,11 +254,8 @@ async def fetch_remote_branches_or_tags(url: str, *, ref_type: str, token: str |
136
136
cmd_args.append("--refs") # Filter out peeled tag objects
137
137
cmd_args.append(url)
138
-
138
+
139
139
- # Run the command with proper authentication
140
140
- with git_auth_context(url, token) as (git_cmd, auth_url):
141
141
- # Replace the URL in cmd_args with the authenticated URL
142
142
- cmd_args[-1] = auth_url # URL is the last argument
143
143
- output = git_cmd.ls_remote(*cmd_args)
144
144
+ # Run the command using git_cmd.ls_remote() method
145
145
+ output = git_cmd.ls_remote(*cmd_args)
146
-
146
+
147
147
# Parse output
148
148
return [
149
149
@@ -314,70 +339,6 @@ def create_git_auth_header(token: str, url: str = "https://github.com") -> str:
150
150
return f"http.https://{hostname}/.extraheader=Authorization: Basic {basic}"
151
-
152
-
151
+
152
+
153
153
- def create_authenticated_url(url: str, token: str | None = None) -> str:
154
154
- """Create an authenticated URL for Git operations.
155
155
-
@@ -216,9 +216,9 @@ index 1c1a986..b7f293a 100644
216
216
-
217
217
def validate_github_token(token: str) -> None:
218
218
"""Validate the format of a GitHub Personal Access Token.
219
-
219
+
220
220
@@ -479,9 +440,15 @@ async def _resolve_ref_to_sha(url: str, pattern: str, token: str | None = None)
221
-
221
+
222
222
"""
223
223
try:
224
224
- # Execute ls-remote command with proper authentication
@@ -234,20 +234,20 @@ index 1c1a986..b7f293a 100644
234
234
+ # Execute ls-remote command
235
235
+ output = git_cmd.ls_remote(auth_url, pattern)
236
236
lines = output.splitlines()
237
-
237
+
238
238
sha = _pick_commit_sha(lines)
239
239
@@ -490,7 +457,7 @@ async def _resolve_ref_to_sha(url: str, pattern: str, token: str | None = None)
240
240
raise ValueError(msg)
241
-
241
+
242
242
except git.GitCommandError as exc:
243
243
- msg = f"Failed to resolve {pattern} in {url}:\n{exc}"
244
244
+ msg = f"Failed to resolve {pattern} in {url}: {exc}"
245
245
raise ValueError(msg) from exc
246
-
246
+
247
247
return sha
248
248
@@ -547,8 +514,6 @@ def _add_token_to_url(url: str, token: str) -> str:
249
249
The URL with embedded authentication.
250
-
250
+
251
251
"""
252
252
- from urllib.parse import urlparse, urlunparse
253
253
-
@@ -264,35 +264,35 @@ index f2f2ae9..03f52f1 100644
264
264
_cleanup_repository(clone_config)
265
265
- return IngestErrorResponse(error=f"{exc!s}")
266
266
+ return IngestErrorResponse(error=str(exc))
267
-
267
+
268
268
if len(content) > MAX_DISPLAY_SIZE:
269
269
content = (
270
270
diff --git a/tests/test_clone.py b/tests/test_clone.py
271
271
index 6abbd87..8c44523 100644
272
272
--- a/tests/test_clone.py
273
273
+++ b/tests/test_clone.py
274
274
@@ -8,8 +8,11 @@ from __future__ import annotations
275
-
275
+
276
276
import sys
277
277
from typing import TYPE_CHECKING
278
278
+ from unittest.mock import AsyncMock
279
-
279
+
280
280
+ import httpx
281
281
import pytest
282
282
+ from starlette.status import HTTP_200_OK, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND
283
-
283
+
284
284
from gitingest.clone import clone_repo
285
285
from gitingest.schemas import CloneConfig
286
286
@@ -18,7 +21,6 @@ from tests.conftest import DEMO_URL, LOCAL_REPO_PATH
287
-
287
+
288
288
if TYPE_CHECKING:
289
289
from pathlib import Path
290
290
- from unittest.mock import AsyncMock
291
-
291
+
292
292
from pytest_mock import MockerFixture
293
-
293
+
294
294
@@ -91,30 +93,24 @@ async def test_clone_nonexistent_repository(repo_exists_true: AsyncMock) -> None
295
-
295
+
296
296
@pytest.mark.asyncio
297
297
@pytest.mark.parametrize(
298
298
- ("git_command_succeeds", "expected"),
@@ -325,23 +325,23 @@ index 6abbd87..8c44523 100644
325
325
+ mock_client.__aenter__.return_value = mock_client # context-manager protocol
326
326
+ mock_client.head.return_value = httpx.Response(status_code=status_code)
327
327
+ mocker.patch("httpx.AsyncClient", return_value=mock_client)
328
-
328
+
329
329
result = await check_repo_exists(DEMO_URL)
330
-
330
+
331
331
assert result is expected
332
332
- mock_resolve.assert_called_once_with(DEMO_URL, "HEAD", token=None)
333
-
334
-
333
+
334
+
335
335
@pytest.mark.asyncio
336
336
@@ -206,18 +202,19 @@ async def test_clone_with_include_submodules(gitpython_mocks: dict) -> None:
337
-
338
-
337
+
338
+
339
339
@pytest.mark.asyncio
340
340
- async def test_check_repo_exists_with_auth_token(mocker: MockerFixture) -> None:
341
341
- """Test ``check_repo_exists`` with authentication token.
342
342
+ async def test_check_repo_exists_with_redirect(mocker: MockerFixture) -> None:
343
343
+ """Test ``check_repo_exists`` when a redirect (302) is returned.
344
-
344
+
345
345
- Given a GitHub URL and a token:
346
346
+ Given a URL that responds with "302 Found":
347
347
When ``check_repo_exists`` is called,
@@ -355,11 +355,11 @@ index 6abbd87..8c44523 100644
355
355
+ mock_process.communicate.return_value = (b"302\n", b"")
356
356
+ mock_process.returncode = 0 # Simulate successful request
357
357
+ mock_exec.return_value = mock_process
358
-
358
+
359
359
- test_token = "token123" # noqa: S105
360
360
- result = await check_repo_exists("https://github.com/test/repo", token=test_token)
361
361
+ repo_exists = await check_repo_exists(DEMO_URL)
362
-
362
+
363
363
- assert result is True
364
364
- mock_resolve.assert_called_once_with("https://github.com/test/repo", "HEAD", token=test_token)
365
365
+ assert repo_exists is False
0 commit comments