Skip to content

Commit faf1911

Browse files
committed
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)