Skip to content

Commit 095871b

Browse files
mohamedashrraf222Saga4
authored andcommitted
simpler and more efficient solution:
1- just deletes the directory with shutil.rmtree 2- Uses onerror callback to properly handle Windows read-only files (git creates these in .git/objects) 3- Single exception handler 4- No git commands needed
1 parent 6aefec8 commit 095871b

1 file changed

Lines changed: 15 additions & 52 deletions

File tree

codeflash/code_utils/git_worktree_utils.py

Lines changed: 15 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
from __future__ import annotations
22

33
import configparser
4+
import os
45
import shutil
6+
import stat
57
import subprocess
68
import tempfile
79
import time
810
from pathlib import Path
911
from typing import Optional
1012

1113
import git
12-
from git.exc import GitCommandError
1314

1415
from codeflash.cli_cmds.console import logger
1516
from codeflash.code_utils.compat import codeflash_cache_dir
@@ -97,62 +98,24 @@ def create_detached_worktree(module_root: Path) -> Optional[Path]:
9798
return worktree_dir
9899

99100

100-
def _fallback_remove_worktree(worktree_dir: Path) -> None:
101-
"""Fallback worktree removal using shutil.rmtree when git commands fail."""
102-
if worktree_dir.exists():
103-
shutil.rmtree(worktree_dir, ignore_errors=True)
104-
logger.debug(f"Removed worktree directory using fallback method: {worktree_dir}")
101+
def _handle_remove_readonly(func, path, exc_info):
102+
"""Error handler for shutil.rmtree to handle read-only files on Windows."""
103+
if isinstance(exc_info[1], PermissionError):
104+
os.chmod(path, stat.S_IWUSR | stat.S_IRUSR | stat.S_IXUSR)
105+
func(path)
106+
else:
107+
raise exc_info[1]
105108

106109

107110
def remove_worktree(worktree_dir: Path) -> None:
108-
"""Remove a git worktree, with retry logic for Windows permission errors."""
109-
# Try to get repository reference
110-
try:
111-
repository = git.Repo(worktree_dir, search_parent_directories=True)
112-
except git.InvalidGitRepositoryError:
113-
# Worktree is not a valid git repository (corrupted or partially created)
114-
logger.debug(f"Worktree is not a valid git repository, using fallback deletion: {worktree_dir}")
115-
_fallback_remove_worktree(worktree_dir)
111+
"""Remove a git worktree directory."""
112+
if not worktree_dir.exists():
116113
return
114+
try:
115+
shutil.rmtree(worktree_dir, onerror=_handle_remove_readonly)
116+
logger.debug(f"Removed worktree: {worktree_dir}")
117117
except Exception:
118-
logger.exception(f"Failed to open worktree repository: {worktree_dir}")
119-
_fallback_remove_worktree(worktree_dir)
120-
return
121-
122-
# Try git worktree remove first
123-
for attempt in range(2):
124-
try:
125-
repository.git.worktree("remove", "--force", worktree_dir)
126-
logger.debug(f"Successfully removed worktree: {worktree_dir}")
127-
return
128-
except GitCommandError as e:
129-
error_msg = str(e).lower()
130-
# Check if it's a permission error or not a git repository error
131-
if "permission denied" in error_msg or "failed to delete" in error_msg:
132-
if attempt == 0:
133-
# Retry once with a small delay to allow file handles to close
134-
logger.debug(f"Permission error removing worktree (attempt {attempt + 1}), retrying after delay: {worktree_dir}")
135-
time.sleep(0.5)
136-
continue
137-
elif "not a git repository" in error_msg:
138-
# Worktree reference is broken, just delete the directory
139-
logger.debug(f"Worktree git reference is broken, using fallback deletion: {worktree_dir}")
140-
_fallback_remove_worktree(worktree_dir)
141-
return
142-
143-
# Fallback to shutil.rmtree for any persistent error
144-
logger.warning(f"Git worktree remove failed, using fallback deletion: {worktree_dir}")
145-
_fallback_remove_worktree(worktree_dir)
146-
# Try to prune stale worktree references
147-
try:
148-
repository.git.worktree("prune")
149-
except Exception:
150-
pass
151-
return
152-
except Exception:
153-
logger.exception(f"Failed to remove worktree: {worktree_dir}")
154-
_fallback_remove_worktree(worktree_dir)
155-
return
118+
logger.exception(f"Failed to remove worktree: {worktree_dir}")
156119

157120

158121
def create_diff_patch_from_worktree(

0 commit comments

Comments
 (0)