Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions tests/tools/test_file_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,44 @@ def test_escape_shell_arg_with_quotes(self, file_ops):
# Should be safely escaped
assert result.count("'") >= 4 # wrapping + escaping

def test_escape_shell_arg_rewrites_windows_drive_paths_to_msys(self, monkeypatch, file_ops):
# bash eats backslashes and MSYS mangles ``C:\...``; the Git Bash
# ``/c/...`` form is the reliable one (reuses _windows_to_msys_path).
import tools.environments.local as local_mod

monkeypatch.setattr(local_mod, "_IS_WINDOWS", True)
assert file_ops._escape_shell_arg(r"C:\Users\alice\notes.txt") == "'/c/Users/alice/notes.txt'"
# Non-drive paths are untouched.
assert file_ops._escape_shell_arg("/tmp/foo") == "'/tmp/foo'"

def test_read_file_uses_bash_safe_windows_paths(self, mock_env, monkeypatch):
import tools.environments.local as local_mod

monkeypatch.setattr(local_mod, "_IS_WINDOWS", True)
commands = []

def side_effect(command, **kwargs):
commands.append(command)
if command.startswith("wc -c"):
return {"output": "5\n", "returncode": 0}
if command.startswith("head -c"):
return {"output": "hello", "returncode": 0}
if command.startswith("sed -n"):
return {"output": "hello\n", "returncode": 0}
if command.startswith("wc -l"):
return {"output": "1\n", "returncode": 0}
return {"output": "", "returncode": 0}

mock_env.execute.side_effect = side_effect
ops = ShellFileOperations(mock_env)
result = ops.read_file(r"C:\Users\alice\notes.txt")

assert result.error is None
assert commands[0] == "wc -c < '/c/Users/alice/notes.txt' 2>/dev/null"
assert commands[1] == "head -c 1000 '/c/Users/alice/notes.txt' 2>/dev/null"
assert commands[2] == "sed -n '1,500p' '/c/Users/alice/notes.txt'"
assert commands[3] == "wc -l < '/c/Users/alice/notes.txt'"

def test_is_likely_binary_by_extension(self, file_ops):
assert file_ops._is_likely_binary("photo.png") is True
assert file_ops._is_likely_binary("data.db") is True
Expand Down
13 changes: 12 additions & 1 deletion tools/file_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -958,7 +958,18 @@ def _expand_path(self, path: str) -> str:
return path

def _escape_shell_arg(self, arg: str) -> str:
"""Escape a string for safe use in shell commands."""
"""Escape a string for safe use in shell commands.

On Windows a native drive path (``C:\\Users\\x``) is first rewritten to
the Git Bash / MSYS ``/c/Users/x`` form: bash eats the backslashes and
MSYS otherwise mangles ``C:\\...`` (the "Directory \\drivers\\etc does not
exist" class of failures). Reuses the env-layer translator so shell file
ops and the terminal ``cd`` agree on the path form. No-op off Windows and
for non-drive-qualified paths.
"""
from tools.environments.local import _windows_to_msys_path

arg = _windows_to_msys_path(arg)
# Use single quotes and escape any single quotes in the string
return "'" + arg.replace("'", "'\"'\"'") + "'"

Expand Down
Loading