fix: Windows git clone failures — URL reinstall + pipe deadlock + file lock

Three fixes for Windows E2E failures:

1. cm_cli reinstall_node(): URL-based node specs used the full URL
   as lookup key, but internal dicts are keyed by repo basename or
   cnr_id. Use get_cnr_by_repo() for CNR-aware lookup with correct
   is_unknown flag.

2. git_helper.py gitclone(): disable tqdm progress when stderr is
   piped (sys.stderr.isatty() gate) to prevent pipe buffer deadlock.
   Also move stale directories from previous failed clones into
   .disabled/.trash/ before cloning (GitPython handle leak on Windows).

3. try_rmtree(): 3-tier deletion strategy for Windows file locks:
   retry 3x with delay, rename into .disabled/.trash/, then lazy-delete
   via reserve_script as final fallback.
This commit is contained in:
Dr.Lt.Data
2026-03-22 18:45:33 +09:00
parent 099aed1ad4
commit affc3e7d2e
5 changed files with 93 additions and 20 deletions
+18 -2
View File
@@ -100,8 +100,24 @@ def gitclone(custom_nodes_path, url, target_hash=None, repo_path=None):
if repo_path is None:
repo_path = os.path.join(custom_nodes_path, repo_name)
# Clone the repository from the remote URL
repo = git.Repo.clone_from(url, repo_path, recursive=True, progress=GitProgress())
# On Windows, previous failed clones may leave directories with locked
# .git/objects/pack/* files (GitPython memory-mapped handle leak).
# Rename stale directory out of the way so clone can proceed.
if os.path.exists(repo_path):
import shutil
import uuid as _uuid
trash_dir = os.path.join(custom_nodes_path, '.disabled', '.trash')
os.makedirs(trash_dir, exist_ok=True)
trash = os.path.join(trash_dir, repo_name + f'_{_uuid.uuid4().hex[:8]}')
try:
os.rename(repo_path, trash)
shutil.rmtree(trash, ignore_errors=True)
except OSError:
shutil.rmtree(repo_path, ignore_errors=True)
# Disable tqdm progress when stderr is piped to avoid deadlock on Windows.
progress = GitProgress() if sys.stderr.isatty() else None
repo = git.Repo.clone_from(url, repo_path, recursive=True, progress=progress)
if target_hash is not None:
print(f"CHECKOUT: {repo_name} [{target_hash}]")