Skip to content

Commit 37d6f09

Browse files
authored
Merge pull request #417 from devchat-ai/fix/rmtree-windows-deletion-failure
fix: Improve directory deletion on Windows
2 parents ea3ba91 + fb88fb2 commit 37d6f09

File tree

5 files changed

+40
-35
lines changed

5 files changed

+40
-35
lines changed

devchat/_cli/run.py

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import click
44

5+
from devchat.utils import rmtree
6+
57

68
@click.command(
79
help="The 'command' argument is the name of the command to run or get information about."
@@ -115,28 +117,6 @@ def run(
115117
return
116118

117119

118-
def __onerror(func, path, _1):
119-
"""
120-
Error handler for shutil.rmtree.
121-
122-
If the error is due to an access error (read only file)
123-
it attempts to add write permission and then retries.
124-
125-
If the error is for another reason it re-raises the error.
126-
127-
Usage : shutil.rmtree(path, onerror=onerror)
128-
"""
129-
import os
130-
import stat
131-
132-
# Check if file access issue
133-
if not os.access(path, os.W_OK):
134-
# Try to change the file to be writable (remove read-only flag)
135-
os.chmod(path, stat.S_IWUSR)
136-
# Retry the function that failed
137-
func(path)
138-
139-
140120
def __make_files_writable(directory):
141121
"""
142122
Recursively make all files in the directory writable.
@@ -180,9 +160,9 @@ def _clone_or_pull_git_repo(target_dir: str, repo_urls: List[Tuple[str, str]], z
180160
bak_dir = target_dir + "_bak"
181161
new_dir = target_dir + "_old"
182162
if os.path.exists(new_dir):
183-
shutil.rmtree(new_dir, onerror=__onerror)
163+
rmtree(new_dir)
184164
if os.path.exists(bak_dir):
185-
shutil.rmtree(bak_dir, onerror=__onerror)
165+
rmtree(bak_dir)
186166
print(f"{target_dir} is already exists. Moved to {new_dir}")
187167
clone_git_repo(bak_dir, repo_urls)
188168
try:

devchat/_cli/utils.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import os
2-
import shutil
32
import sys
43
import zipfile
54
from contextlib import contextmanager
65
from typing import Any, List, Optional, Tuple
76

87
from devchat._cli.errors import MissContentInPromptException
9-
from devchat.utils import add_gitignore, find_root_dir, get_logger, setup_logger
8+
from devchat.utils import add_gitignore, find_root_dir, get_logger, rmtree, setup_logger
109

1110
logger = get_logger(__name__)
1211

@@ -31,7 +30,7 @@ def download_and_extract_workflow(workflow_url, target_dir):
3130

3231
# Delete target directory if exists
3332
if os.path.exists(target_dir):
34-
shutil.rmtree(target_dir)
33+
rmtree(target_dir)
3534

3635
# Rename extracted directory to target directory
3736
extracted_dir = os.path.join(parent_dir, "workflows-main")

devchat/_service/route/workflows.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import shutil
21
from pathlib import Path
32
from typing import List
43

@@ -7,7 +6,7 @@
76
from fastapi.responses import JSONResponse
87

98
from devchat._service.schema import response
10-
from devchat.utils import get_logger
9+
from devchat.utils import get_logger, rmtree
1110
from devchat.workflow.namespace import (
1211
WorkflowMeta,
1312
get_prioritized_namespace_path,
@@ -100,7 +99,7 @@ def update_custom_workflows():
10099

101100
if repo_path.exists():
102101
logger.info(f"Repo path not empty {repo_path}, removing it.")
103-
shutil.rmtree(repo_path)
102+
rmtree(repo_path)
104103

105104
logger.info(
106105
f"Starting update for {repo_name} at {repo_path} "

devchat/utils.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,30 @@ def get_encoding(name: str):
243243
def openai_response_tokens(message: dict, model: str) -> int:
244244
"""Returns the number of tokens used by a response."""
245245
return openai_message_tokens(message, model)
246+
247+
248+
def rmtree(path: str) -> None:
249+
import shutil
250+
251+
def __onerror(func, path, _1):
252+
"""
253+
Error handler for shutil.rmtree.
254+
255+
If the error is due to an access error (read only file)
256+
it attempts to add write permission and then retries.
257+
258+
If the error is for another reason it re-raises the error.
259+
260+
Usage : shutil.rmtree(path, onerror=onerror)
261+
"""
262+
import os
263+
import stat
264+
265+
# Check if file access issue
266+
if not os.access(path, os.W_OK):
267+
# Try to change the file to be writable (remove read-only flag)
268+
os.chmod(path, stat.S_IWUSR)
269+
# Retry the function that failed
270+
func(path)
271+
272+
shutil.rmtree(path, onerror=__onerror)

devchat/workflow/update_util.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import requests
1010

11-
from devchat.utils import get_logger
11+
from devchat.utils import get_logger, rmtree
1212
from devchat.workflow.path import (
1313
CHAT_DIR,
1414
CUSTOM_BASE,
@@ -167,7 +167,7 @@ def update_by_zip(workflow_base: Path) -> Tuple[bool, str]:
167167
# Has previous workflows, download as tmp_new
168168
tmp_new = parent / f"{WORKFLOWS_BASE_NAME}_new"
169169
if tmp_new.exists():
170-
shutil.rmtree(tmp_new)
170+
rmtree(tmp_new)
171171
# TODO: handle error?
172172
# shutil.rmtree(tmp_new, onerror=__onerror)
173173

@@ -182,7 +182,7 @@ def update_by_zip(workflow_base: Path) -> Tuple[bool, str]:
182182
backup_zip = _backup(workflow_base)
183183

184184
# rename the new dir to the workflow_base
185-
shutil.rmtree(workflow_base)
185+
rmtree(workflow_base)
186186
shutil.move(tmp_new, workflow_base)
187187

188188
msg = f"Updated {workflow_base} by zip. (backup: {backup_zip})"
@@ -223,7 +223,7 @@ def update_by_git(workflow_base: Path) -> Tuple[bool, str]:
223223
# try to clone the new repo to tmp_new
224224
tmp_new = parent / f"{WORKFLOWS_BASE_NAME}_new"
225225
if tmp_new.exists():
226-
shutil.rmtree(tmp_new)
226+
rmtree(tmp_new)
227227

228228
clone_ok = _clone_repo_to_dir(REPO_URLS, tmp_new)
229229
if not clone_ok:
@@ -235,7 +235,7 @@ def update_by_git(workflow_base: Path) -> Tuple[bool, str]:
235235
backup_zip = _backup(workflow_base)
236236

237237
# rename the new dir to the workflow_base
238-
shutil.rmtree(workflow_base)
238+
rmtree(workflow_base)
239239
shutil.move(tmp_new, workflow_base)
240240

241241
msg = f"Updated {workflow_base} by git. (backup: {backup_zip})"

0 commit comments

Comments
 (0)