Skip to content

Commit b8ac225

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 293b4ae commit b8ac225

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

@@ -258,11 +262,32 @@ async def _clone_repo_if_remote(query: IngestionQuery, *, token: str | None) ->
258262
try:
259263
yield
260264
finally:
261-
shutil.rmtree(query.local_path.parent)
265+
shutil.rmtree(query.local_path.parent, onerror=_handle_remove_readonly)
262266
else:
263267
yield
264268

265269

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

0 commit comments

Comments
 (0)