Arregla l'esborrat a Windows: lleva el bit de només-lectura del .git
This commit is contained in:
+29
-2
@@ -10,7 +10,9 @@ import datetime as _dt
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
@@ -49,6 +51,31 @@ def _noop(_: str) -> None:
|
|||||||
pass
|
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]:
|
def _auth_args(token: str) -> list[str]:
|
||||||
"""Args -c para autenticar git ante Gitea con un token, sin tocar .git/config."""
|
"""Args -c para autenticar git ante Gitea con un token, sin tocar .git/config."""
|
||||||
if not token:
|
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")
|
log(f"{game.name}: no hi ha res a esborrar")
|
||||||
return
|
return
|
||||||
log(f"Esborrant la descàrrega local de {game.name}…")
|
log(f"Esborrant la descàrrega local de {game.name}…")
|
||||||
shutil.rmtree(target, ignore_errors=True)
|
_force_rmtree(target, log)
|
||||||
|
|
||||||
|
|
||||||
def download(
|
def download(
|
||||||
@@ -201,7 +228,7 @@ def download(
|
|||||||
else:
|
else:
|
||||||
log(f"Clonant {game.name}…")
|
log(f"Clonant {game.name}…")
|
||||||
if repo.exists(): # carpeta a medias sin .git: limpiarla
|
if repo.exists(): # carpeta a medias sin .git: limpiarla
|
||||||
shutil.rmtree(repo, ignore_errors=True)
|
_force_rmtree(repo, log)
|
||||||
_run_git(
|
_run_git(
|
||||||
["clone", game.clone_url, str(repo)],
|
["clone", game.clone_url, str(repo)],
|
||||||
None,
|
None,
|
||||||
|
|||||||
Reference in New Issue
Block a user