fix terminal cwd handling on windows

This commit is contained in:
LeonSGP43 2026-05-31 10:54:35 +08:00 committed by Teknium
parent ad5f3341d3
commit 9ed7252a98
2 changed files with 73 additions and 0 deletions

View file

@ -26,6 +26,7 @@ from tools.environments.local import (
LocalEnvironment,
_msys_to_windows_path,
_resolve_safe_cwd,
_windows_to_msys_path,
)
@ -70,6 +71,35 @@ class TestMsysToWindowsPath:
assert _msys_to_windows_path("") == ""
# ---------------------------------------------------------------------------
# _windows_to_msys_path — reverse translation for bash builtin cd
# ---------------------------------------------------------------------------
class TestWindowsToMsysPath:
def test_noop_on_non_windows(self, monkeypatch):
monkeypatch.setattr(local_mod, "_IS_WINDOWS", False)
assert _windows_to_msys_path(r"C:\Users\NVIDIA") == r"C:\Users\NVIDIA"
def test_translates_backslash_path(self, monkeypatch):
monkeypatch.setattr(local_mod, "_IS_WINDOWS", True)
assert _windows_to_msys_path(r"C:\Users\NVIDIA") == "/c/Users/NVIDIA"
assert _windows_to_msys_path(r"D:\Projects\foo bar") == "/d/Projects/foo bar"
def test_translates_forward_slash_native_path(self, monkeypatch):
monkeypatch.setattr(local_mod, "_IS_WINDOWS", True)
assert _windows_to_msys_path("C:/Users/NVIDIA") == "/c/Users/NVIDIA"
def test_translates_drive_root(self, monkeypatch):
monkeypatch.setattr(local_mod, "_IS_WINDOWS", True)
assert _windows_to_msys_path(r"C:\\") == "/c/"
assert _windows_to_msys_path("D:/") == "/d/"
def test_does_not_translate_non_drive_path(self, monkeypatch):
monkeypatch.setattr(local_mod, "_IS_WINDOWS", True)
assert _windows_to_msys_path("/tmp/foo") == "/tmp/foo"
assert _windows_to_msys_path(r"\\server\share") == r"\\server\share"
# ---------------------------------------------------------------------------
# _resolve_safe_cwd — Windows fast path
# ---------------------------------------------------------------------------
@ -196,3 +226,23 @@ class TestExtractCwdFromOutputWindowsMsys:
env._extract_cwd_from_output(result)
assert env.cwd == str(new_dir)
# ---------------------------------------------------------------------------
# Command wrapping — native Windows cwd must be Git Bash-friendly for cd
# ---------------------------------------------------------------------------
class TestWrapCommandWindowsNativeCwd:
def test_wrap_command_converts_native_cwd_for_builtin_cd(self, monkeypatch):
monkeypatch.setattr(local_mod, "_IS_WINDOWS", True)
with patch.object(
LocalEnvironment, "init_session", autospec=True, return_value=None
):
env = LocalEnvironment(cwd=r"C:\Users\liush", timeout=10)
env._snapshot_ready = True
wrapped = env._wrap_command("pwd", r"C:\Users\liush")
assert "builtin cd -- /c/Users/liush || exit 126" in wrapped
assert r"builtin cd -- C:\Users\liush || exit 126" not in wrapped

View file

@ -40,6 +40,24 @@ def _msys_to_windows_path(cwd: str) -> str:
return f"{drive}:{tail or chr(92)}" # chr(92) = backslash, avoid raw-string escape
def _windows_to_msys_path(cwd: str) -> str:
"""Translate a native Windows path (``C:\\Users\\x``) to Git Bash /
MSYS form (``/c/Users/x``) so ``builtin cd`` resolves it reliably.
No-ops on non-Windows hosts or for paths that aren't drive-qualified
native Windows paths. Returns the input unchanged when no translation
applies.
"""
if not _IS_WINDOWS or not cwd:
return cwd
m = re.match(r'^([a-zA-Z]):[\\/]*(.*)$', cwd)
if not m:
return cwd
drive = m.group(1).lower()
tail = (m.group(2) or "").replace('\\', '/').lstrip('/')
return f"/{drive}/{tail}" if tail else f"/{drive}/"
def _resolve_safe_cwd(cwd: str) -> str:
"""Return ``cwd`` if it exists as a directory, else the nearest existing
ancestor. Falls back to ``tempfile.gettempdir()`` only if walking up the
@ -921,6 +939,11 @@ class LocalEnvironment(BaseEnvironment):
return "/tmp"
@staticmethod
def _quote_cwd_for_cd(cwd: str) -> str:
"""Use native paths for Python, but Git Bash-friendly paths for cd."""
return BaseEnvironment._quote_cwd_for_cd(_windows_to_msys_path(cwd))
def _run_bash(self, cmd_string: str, *, login: bool = False,
timeout: int = 120,
stdin_data: str | None = None) -> subprocess.Popen: