Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit faf1911

Browse files
fix pre-commit things
1 parent 4a462d3 commit faf1911

File tree

6 files changed

+84
-58
lines changed

6 files changed

+84
-58
lines changed

‎.pre-commit-config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ repos:
117117
boto3>=1.28.0,
118118
click>=8.0.0,
119119
'fastapi[standard]>=0.109.1',
120+
gitpython>=3.1.0,
120121
httpx,
121122
loguru>=0.7.0,
122123
pathspec>=0.12.1,
@@ -144,6 +145,7 @@ repos:
144145
boto3>=1.28.0,
145146
click>=8.0.0,
146147
'fastapi[standard]>=0.109.1',
148+
gitpython>=3.1.0,
147149
httpx,
148150
loguru>=0.7.0,
149151
pathspec>=0.12.1,

‎src/gitingest/clone.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ async def clone_repo(config: CloneConfig, *, token: str | None = None) -> None:
4747
------
4848
ValueError
4949
If the repository is not found, if the provided URL is invalid, or if the token format is invalid.
50+
RuntimeError
51+
If Git operations fail during the cloning process.
5052
5153
"""
5254
# Extract and validate query parameters
@@ -121,7 +123,40 @@ async def clone_repo(config: CloneConfig, *, token: str | None = None) -> None:
121123
await checkout_partial_clone(config, token=token)
122124
logger.debug("Partial clone setup completed")
123125

124-
# Create repo object and perform operations
126+
# Perform post-clone operations
127+
await _perform_post_clone_operations(config, local_path, url, token, commit)
128+
129+
logger.info("Git clone operation completed successfully", extra={"local_path": local_path})
130+
131+
132+
async def _perform_post_clone_operations(
133+
config: CloneConfig,
134+
local_path: str,
135+
url: str,
136+
token: str | None,
137+
commit: str,
138+
) -> None:
139+
"""Perform post-clone operations like fetching, checkout, and submodule updates.
140+
141+
Parameters
142+
----------
143+
config : CloneConfig
144+
The configuration for cloning the repository.
145+
local_path : str
146+
The local path where the repository was cloned.
147+
url : str
148+
The repository URL.
149+
token : str | None
150+
GitHub personal access token (PAT) for accessing private repositories.
151+
commit : str
152+
The commit SHA to checkout.
153+
154+
Raises
155+
------
156+
RuntimeError
157+
If any Git operation fails.
158+
159+
"""
125160
try:
126161
repo = create_git_repo(local_path, url, token)
127162

@@ -141,5 +176,3 @@ async def clone_repo(config: CloneConfig, *, token: str | None = None) -> None:
141176
except git.GitCommandError as exc:
142177
msg = f"Git operation failed: {exc}"
143178
raise RuntimeError(msg) from exc
144-
145-
logger.info("Git clone operation completed successfully", extra={"local_path": local_path})

‎src/gitingest/utils/git_utils.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import sys
99
from pathlib import Path
1010
from typing import TYPE_CHECKING, Final, Iterable
11-
from urllib.parse import urlparse
11+
from urllib.parse import urlparse, urlunparse
1212

1313
import git
1414
import httpx
@@ -226,6 +226,8 @@ async def fetch_remote_branches_or_tags(url: str, *, ref_type: str, token: str |
226226
------
227227
ValueError
228228
If the ``ref_type`` parameter is not "branches" or "tags".
229+
RuntimeError
230+
If fetching branches or tags from the remote repository fails.
229231
230232
"""
231233
if ref_type not in ("branches", "tags"):
@@ -238,8 +240,7 @@ async def fetch_remote_branches_or_tags(url: str, *, ref_type: str, token: str |
238240
try:
239241
git_cmd = git.Git()
240242

241-
# Prepare environment with authentication if needed
242-
env = None
243+
# Prepare authentication if needed
243244
if token and is_github_host(url):
244245
auth_url = _add_token_to_url(url, token)
245246
url = auth_url
@@ -284,6 +285,11 @@ def create_git_repo(local_path: str, url: str, token: str | None = None) -> git.
284285
git.Repo
285286
A GitPython Repo object configured with authentication.
286287
288+
Raises
289+
------
290+
ValueError
291+
If the local path is not a valid git repository.
292+
287293
"""
288294
try:
289295
repo = git.Repo(local_path)
@@ -295,11 +301,12 @@ def create_git_repo(local_path: str, url: str, token: str | None = None) -> git.
295301
key, value = auth_header.split("=", 1)
296302
repo.git.config(key, value)
297303

298-
return repo
299304
except git.InvalidGitRepositoryError as exc:
300305
msg = f"Invalid git repository at {local_path}"
301306
raise ValueError(msg) from exc
302307

308+
return repo
309+
303310

304311
def create_git_auth_header(token: str, url: str = "https://github.com") -> str:
305312
"""Create a Basic authentication header for GitHub git operations.
@@ -360,6 +367,11 @@ async def checkout_partial_clone(config: CloneConfig, token: str | None) -> None
360367
token : str | None
361368
GitHub personal access token (PAT) for accessing private repositories.
362369
370+
Raises
371+
------
372+
RuntimeError
373+
If the sparse-checkout configuration fails.
374+
363375
"""
364376
subpath = config.subpath.lstrip("/")
365377
if config.blob:
@@ -444,11 +456,12 @@ async def _resolve_ref_to_sha(url: str, pattern: str, token: str | None = None)
444456
msg = f"{pattern!r} not found in {url}"
445457
raise ValueError(msg)
446458

447-
return sha
448459
except git.GitCommandError as exc:
449460
msg = f"Failed to resolve {pattern} in {url}: {exc}"
450461
raise ValueError(msg) from exc
451462

463+
return sha
464+
452465

453466
def _pick_commit_sha(lines: Iterable[str]) -> str | None:
454467
"""Return a commit SHA from ``git ls-remote`` output.
@@ -501,8 +514,6 @@ def _add_token_to_url(url: str, token: str) -> str:
501514
The URL with embedded authentication.
502515
503516
"""
504-
from urllib.parse import urlparse, urlunparse
505-
506517
parsed = urlparse(url)
507518
# Add token as username in URL (GitHub supports this)
508519
netloc = f"x-oauth-basic:{token}@{parsed.hostname}"

‎tests/conftest.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -189,12 +189,12 @@ def _factory(branches: list[str]) -> None:
189189
new_callable=AsyncMock,
190190
return_value=branches,
191191
)
192-
192+
193193
# Patch GitPython's ls_remote method to return the mocked output
194194
ls_remote_output = "\n".join(f"{DEMO_COMMIT[:12]}{i:02d}\trefs/heads/{b}" for i, b in enumerate(branches))
195195
mock_git_cmd = mocker.patch("git.Git")
196196
mock_git_cmd.return_value.ls_remote.return_value = ls_remote_output
197-
197+
198198
# Also patch the git module imports in our utils
199199
mocker.patch("gitingest.utils.git_utils.git.Git", return_value=mock_git_cmd.return_value)
200200

@@ -216,10 +216,10 @@ def run_command_mock(mocker: MockerFixture) -> AsyncMock:
216216
"""
217217
mock = AsyncMock(side_effect=_fake_run_command)
218218
mocker.patch("gitingest.utils.git_utils.run_command", mock)
219-
219+
220220
# Mock GitPython components
221221
_setup_gitpython_mocks(mocker)
222-
222+
223223
return mock
224224

225225

@@ -238,7 +238,7 @@ def _setup_gitpython_mocks(mocker: MockerFixture) -> dict[str, MagicMock]:
238238
mock_git_cmd.execute.return_value = f"{DEMO_COMMIT}\trefs/heads/main\n"
239239
mock_git_cmd.ls_remote.return_value = f"{DEMO_COMMIT}\trefs/heads/main\n"
240240
mock_git_cmd.clone.return_value = ""
241-
241+
242242
# Mock git.Repo class
243243
mock_repo = MagicMock()
244244
mock_repo.git = MagicMock()
@@ -248,21 +248,21 @@ def _setup_gitpython_mocks(mocker: MockerFixture) -> dict[str, MagicMock]:
248248
mock_repo.git.execute = MagicMock()
249249
mock_repo.git.config = MagicMock()
250250
mock_repo.git.sparse_checkout = MagicMock()
251-
251+
252252
# Mock git.Repo.clone_from
253253
mock_clone_from = MagicMock(return_value=mock_repo)
254-
254+
255255
git_git_mock = mocker.patch("git.Git", return_value=mock_git_cmd)
256256
git_repo_mock = mocker.patch("git.Repo", return_value=mock_repo)
257257
mocker.patch("git.Repo.clone_from", mock_clone_from)
258-
258+
259259
# Patch imports in our modules
260260
mocker.patch("gitingest.utils.git_utils.git.Git", return_value=mock_git_cmd)
261261
mocker.patch("gitingest.utils.git_utils.git.Repo", return_value=mock_repo)
262262
mocker.patch("gitingest.clone.git.Git", return_value=mock_git_cmd)
263263
mocker.patch("gitingest.clone.git.Repo", return_value=mock_repo)
264264
mocker.patch("gitingest.clone.git.Repo.clone_from", mock_clone_from)
265-
265+
266266
return {
267267
"git_cmd": mock_git_cmd,
268268
"repo": mock_repo,

‎tests/test_clone.py

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from __future__ import annotations
88

9-
import asyncio
109
import sys
1110
from typing import TYPE_CHECKING
1211
from unittest.mock import AsyncMock
@@ -17,9 +16,8 @@
1716

1817
from gitingest.clone import clone_repo
1918
from gitingest.schemas import CloneConfig
20-
from gitingest.utils.exceptions import AsyncTimeoutError
2119
from gitingest.utils.git_utils import check_repo_exists
22-
from tests.conftest import DEMO_COMMIT, DEMO_URL, LOCAL_REPO_PATH
20+
from tests.conftest import DEMO_URL, LOCAL_REPO_PATH
2321

2422
if TYPE_CHECKING:
2523
from pathlib import Path
@@ -53,26 +51,23 @@ async def test_clone_with_commit(repo_exists_true: AsyncMock, gitpython_mocks: d
5351
await clone_repo(clone_config)
5452

5553
repo_exists_true.assert_any_call(clone_config.url, token=None)
56-
54+
5755
# Verify GitPython calls were made
5856
mock_git_cmd = gitpython_mocks["git_cmd"]
5957
mock_repo = gitpython_mocks["repo"]
6058
mock_clone_from = gitpython_mocks["clone_from"]
61-
59+
6260
# Should have called version (for ensure_git_installed)
6361
mock_git_cmd.version.assert_called()
64-
62+
6563
# Should have called clone_from (since partial_clone=False)
6664
mock_clone_from.assert_called_once()
67-
65+
6866
# Should have called fetch and checkout on the repo
6967
mock_repo.git.fetch.assert_called()
7068
mock_repo.git.checkout.assert_called_with(commit_hash)
7169

7270

73-
74-
75-
7671
@pytest.mark.asyncio
7772
async def test_clone_nonexistent_repository(repo_exists_true: AsyncMock) -> None:
7873
"""Test cloning a nonexistent repository URL.
@@ -118,15 +113,6 @@ async def test_check_repo_exists(status_code: int, *, expected: bool, mocker: Mo
118113
assert result is expected
119114

120115

121-
122-
123-
124-
125-
126-
127-
128-
129-
130116
@pytest.mark.asyncio
131117
async def test_clone_without_commit(repo_exists_true: AsyncMock, gitpython_mocks: dict) -> None:
132118
"""Test cloning a repository when no commit hash is provided.
@@ -140,12 +126,12 @@ async def test_clone_without_commit(repo_exists_true: AsyncMock, gitpython_mocks
140126
await clone_repo(clone_config)
141127

142128
repo_exists_true.assert_any_call(clone_config.url, token=None)
143-
129+
144130
# Verify GitPython calls were made
145131
mock_git_cmd = gitpython_mocks["git_cmd"]
146132
mock_repo = gitpython_mocks["repo"]
147133
mock_clone_from = gitpython_mocks["clone_from"]
148-
134+
149135
# Should have resolved the commit via ls_remote
150136
mock_git_cmd.ls_remote.assert_called()
151137
# Should have cloned the repo
@@ -170,7 +156,7 @@ async def test_clone_creates_parent_directory(tmp_path: Path, gitpython_mocks: d
170156

171157
# Verify parent directories were created
172158
assert nested_path.parent.exists()
173-
159+
174160
# Verify clone operation happened
175161
mock_clone_from = gitpython_mocks["clone_from"]
176162
mock_clone_from.assert_called_once()
@@ -192,7 +178,7 @@ async def test_clone_with_specific_subpath(gitpython_mocks: dict) -> None:
192178
# Verify partial clone (using git.clone instead of Repo.clone_from)
193179
mock_git_cmd = gitpython_mocks["git_cmd"]
194180
mock_git_cmd.clone.assert_called()
195-
181+
196182
# Verify sparse checkout was configured
197183
mock_repo = gitpython_mocks["repo"]
198184
mock_repo.git.sparse_checkout.assert_called()
@@ -232,9 +218,3 @@ async def test_check_repo_exists_with_redirect(mocker: MockerFixture) -> None:
232218
repo_exists = await check_repo_exists(DEMO_URL)
233219

234220
assert repo_exists is False
235-
236-
237-
238-
239-
240-

‎tests/test_git_utils.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,20 @@ def test_create_git_repo(
8282
local_path: str,
8383
url: str,
8484
token: str | None,
85-
should_configure_auth: bool,
85+
should_configure_auth: bool,# noqa: FBT001
8686
mocker: MockerFixture,
8787
) -> None:
8888
"""Test that ``create_git_repo`` creates a proper Git repo object."""
8989
# Mock git.Repo to avoid actual filesystem operations
9090
mock_repo = mocker.MagicMock()
9191
mock_repo_class = mocker.patch("git.Repo", return_value=mock_repo)
92-
92+
9393
repo = create_git_repo(local_path, url, token)
94-
94+
9595
# Should create repo with correct path
9696
mock_repo_class.assert_called_once_with(local_path)
9797
assert repo == mock_repo
98-
98+
9999
# Check auth configuration
100100
if should_configure_auth:
101101
mock_repo.git.config.assert_called_once()
@@ -140,7 +140,7 @@ def test_create_git_repo_helper_calls(
140140
mock_repo = mocker.MagicMock()
141141
mocker.patch("git.Repo", return_value=mock_repo)
142142

143-
repo=create_git_repo(str(work_dir), url, token)
143+
create_git_repo(str(work_dir), url, token)
144144

145145
if should_call:
146146
header_mock.assert_called_once_with(token, url=url)
@@ -241,13 +241,13 @@ def test_create_git_repo_with_ghe_urls(
241241
"""Test that ``create_git_repo`` handles GitHub Enterprise URLs correctly."""
242242
mock_repo = mocker.MagicMock()
243243
mocker.patch("git.Repo", return_value=mock_repo)
244-
245-
repo=create_git_repo(local_path, url, token)
244+
245+
create_git_repo(local_path, url, token)
246246

247247
# Should configure auth with the correct hostname
248248
mock_repo.git.config.assert_called_once()
249249
auth_config_call = mock_repo.git.config.call_args[0]
250-
250+
251251
# The first argument should contain the hostname
252252
assert expected_auth_hostname in auth_config_call[0]
253253

@@ -270,8 +270,8 @@ def test_create_git_repo_ignores_non_github_urls(
270270
"""Test that ``create_git_repo`` does not configure auth for non-GitHub URLs."""
271271
mock_repo = mocker.MagicMock()
272272
mocker.patch("git.Repo", return_value=mock_repo)
273-
274-
repo=create_git_repo(local_path, url, token)
273+
274+
create_git_repo(local_path, url, token)
275275

276276
# Should not configure auth for non-GitHub URLs
277277
mock_repo.git.config.assert_not_called()

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /