Arregla l'esborrat a Windows: lleva el bit de només-lectura del .git

This commit is contained in:
Sergio Valor Martinez
2026-06-01 12:55:50 +02:00
parent 9147eb56fb
commit 1e530a413d
+29 -2
View File
@@ -10,7 +10,9 @@ import datetime as _dt
import json
import os
import shutil
import stat
import subprocess
import sys
import urllib.error
import urllib.request
from collections.abc import Callable
@@ -49,6 +51,31 @@ def _noop(_: str) -> None:
pass
def _force_rmtree(path: Path, log: LogFn = _noop) -> None:
"""Esborra un arbre de fitxers, fins i tot amb fitxers de només-lectura.
A Windows els objectes de ``.git`` es creen com a només-lectura i fan que
``shutil.rmtree`` falli amb ``PermissionError``; cal llevar el bit d'escriptura
i reintentar. A diferència de ``ignore_errors=True``, aquí els errors que no
puguem resoldre es registren al log en lloc d'empassar-se en silenci.
"""
if not path.exists():
return
def handle(func: Callable, p: str, _exc: object) -> None:
try:
os.chmod(p, stat.S_IWRITE)
func(p) # reintenta l'operació (unlink/rmdir) que havia fallat
except OSError as exc:
log(f"No s'ha pogut esborrar {p}: {exc}")
# Python 3.12 va renombrar el paràmetre `onerror` a `onexc`; suportem tots dos.
if sys.version_info >= (3, 12):
shutil.rmtree(path, onexc=handle)
else:
shutil.rmtree(path, onerror=handle)
def _auth_args(token: str) -> list[str]:
"""Args -c para autenticar git ante Gitea con un token, sin tocar .git/config."""
if not token:
@@ -166,7 +193,7 @@ def delete_local(root: Path, game: Game, log: LogFn = _noop) -> None:
log(f"{game.name}: no hi ha res a esborrar")
return
log(f"Esborrant la descàrrega local de {game.name}")
shutil.rmtree(target, ignore_errors=True)
_force_rmtree(target, log)
def download(
@@ -201,7 +228,7 @@ def download(
else:
log(f"Clonant {game.name}")
if repo.exists(): # carpeta a medias sin .git: limpiarla
shutil.rmtree(repo, ignore_errors=True)
_force_rmtree(repo, log)
_run_git(
["clone", game.clone_url, str(repo)],
None,