Skip to content

Commit 234f22b

Browse files
fix(entrypoint): handle read-only Git objects on Windows
Add `_handle_remove_readonly` on-error callback for `shutil.rmtree` that removes the read-only attribute and retries, preventing WinError 5 during temp-repo cleanup in tests.
1 parent 83370bc commit 234f22b

File tree

1 file changed

+27
-2
lines changed

1 file changed

+27
-2
lines changed

src/gitingest/entrypoint.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
from __future__ import annotations
44

55
import asyncio
6+
import errno
67
import shutil
8+
import stat
79
import sys
810
import warnings
911
from contextlib import asynccontextmanager
1012
from pathlib import Path
11-
from typing import TYPE_CHECKING, AsyncGenerator
13+
from typing import TYPE_CHECKING, AsyncGenerator, Callable
1214
from urllib.parse import urlparse
1315

1416
from gitingest.clone import clone_repo
@@ -22,6 +24,8 @@
2224
from gitingest.utils.query_parser_utils import KNOWN_GIT_HOSTS
2325

2426
if TYPE_CHECKING:
27+
from types import TracebackType
28+
2529
from gitingest.schemas import IngestionQuery
2630

2731

@@ -254,11 +258,32 @@ async def _clone_repo_if_remote(query: IngestionQuery, *, token: str | None) ->
254258
try:
255259
yield
256260
finally:
257-
shutil.rmtree(query.local_path.parent)
261+
shutil.rmtree(query.local_path.parent, onerror=_handle_remove_readonly)
258262
else:
259263
yield
260264

261265

266+
def _handle_remove_readonly(
267+
func: Callable,
268+
path: str,
269+
exc_info: tuple[type[BaseException], BaseException, TracebackType],
270+
) -> None:
271+
"""Handle permission errors raised by ``shutil.rmtree()``.
272+
273+
* Makes the target writable (removes the read-only attribute).
274+
* Retries the original operation (``func``) once.
275+
276+
"""
277+
exc = exc_info[1]
278+
# Handle only'Permission denied' and 'Operation not permitted'
279+
if not isinstance(exc, OSError) or exc.errno not in {errno.EACCES, errno.EPERM}:
280+
raise exc
281+
282+
# Make the target writable
283+
Path(path).chmod(stat.S_IWRITE)
284+
func(path)
285+
286+
262287
async def _write_output(tree: str, content: str, target: str | None) -> None:
263288
"""Write combined output to ``target`` (``"-"`` ⇒ stdout).
264289

0 commit comments

Comments
 (0)