fix(workspace-git): hide git subprocess console windows on Windows (#5692)
Add creationflags to _run_git() and _config_names_for_scope() so git child processes don't accumulate hidden cmd.exe/conhost windows on Windows. Uses a local module-level _windows_hide_flags() helper (mirrors the api/updates.py pattern: CREATE_NO_WINDOW on win32, 0 elsewhere) rather than importing from the OPTIONAL hermes_cli package — a standalone/agent-less WebUI (and the repo's own CI runners, which don't pip-install hermes-agent) must keep full workspace-git functionality. windows_hide_flags() is a safe no-op (0) on non-Windows. Scoped to workspace git commands. Adds mutation-checked tests asserting the kwarg is wired on both sites. Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
This commit is contained in:
parent
8fee7da3ee
commit
f106fcd1f5
2 changed files with 75 additions and 0 deletions
|
|
@ -12,6 +12,7 @@ import logging
|
|||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import re
|
||||
|
|
@ -22,6 +23,20 @@ from typing import Iterable
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _windows_hide_flags() -> int:
|
||||
"""Win32 ``creationflags`` that hide a short-lived console child's window
|
||||
(``CREATE_NO_WINDOW``) without detaching it, so ``capture_output`` still
|
||||
works. Returns ``0`` on non-Windows — the ``subprocess`` default, a genuine
|
||||
no-op. Mirrors the ``api/updates.py`` pattern; kept local so workspace-git
|
||||
never takes a hard dependency on the optional ``hermes_cli`` package (a
|
||||
standalone/agent-less WebUI must keep full git functionality). See #5692.
|
||||
"""
|
||||
if sys.platform == "win32":
|
||||
return getattr(subprocess, "CREATE_NO_WINDOW", 0)
|
||||
return 0
|
||||
|
||||
|
||||
from api.workspace import rmtree_anchored, safe_resolve_ws, unlink_anchored
|
||||
|
||||
|
||||
|
|
@ -248,6 +263,7 @@ def _run_git(
|
|||
text=True,
|
||||
timeout=timeout,
|
||||
env=run_env,
|
||||
creationflags=_windows_hide_flags(),
|
||||
)
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
raise GitWorkspaceError("Git command timed out", "timeout") from exc
|
||||
|
|
@ -290,6 +306,7 @@ def _config_names_for_scope(
|
|||
capture_output=True,
|
||||
timeout=GIT_TIMEOUT,
|
||||
env=env,
|
||||
creationflags=_windows_hide_flags(),
|
||||
)
|
||||
if result.returncode not in {0, 1}:
|
||||
if ignore_unsupported:
|
||||
|
|
|
|||
|
|
@ -2515,3 +2515,61 @@ def test_dirty_worktree_uses_filter_neutralization(tmp_path):
|
|||
_dirty_worktree(ctx)
|
||||
|
||||
assert not marker.exists()
|
||||
|
||||
|
||||
def test_run_git_passes_windows_hide_flags(monkeypatch, tmp_path):
|
||||
"""_run_git must pass creationflags=_windows_hide_flags() so git child
|
||||
processes don't accumulate visible console windows on Windows (#5692).
|
||||
windows_hide_flags() is 0 on non-Windows, so this is a safe no-op there;
|
||||
the test asserts the kwarg is wired regardless of platform."""
|
||||
import api.workspace_git as wg
|
||||
from api.workspace_git import _windows_hide_flags
|
||||
|
||||
repo = _init_repo(tmp_path / "repo")
|
||||
(repo / "f.txt").write_text("x\n", encoding="utf-8")
|
||||
_commit_all(repo)
|
||||
|
||||
captured = {}
|
||||
real_run = subprocess.run
|
||||
|
||||
def fake_run(*args, **kwargs):
|
||||
captured["creationflags"] = kwargs.get("creationflags", "MISSING")
|
||||
return real_run(*args, **kwargs)
|
||||
|
||||
monkeypatch.setattr(wg.subprocess, "run", fake_run)
|
||||
wg._run_git(repo, ["rev-parse", "HEAD"])
|
||||
|
||||
assert captured.get("creationflags") == _windows_hide_flags(), (
|
||||
"_run_git must pass creationflags=_windows_hide_flags() to subprocess.run"
|
||||
)
|
||||
|
||||
|
||||
def test_config_names_for_scope_passes_windows_hide_flags(monkeypatch, tmp_path):
|
||||
"""_config_names_for_scope must also pass creationflags=_windows_hide_flags()
|
||||
(#5692) — the git-config probe spawns a console window on Windows too."""
|
||||
import re as _re
|
||||
import api.workspace_git as wg
|
||||
from api.workspace_git import _windows_hide_flags
|
||||
|
||||
repo = _init_repo(tmp_path / "repo")
|
||||
|
||||
captured = {}
|
||||
real_run = subprocess.run
|
||||
|
||||
def fake_run(*args, **kwargs):
|
||||
captured["creationflags"] = kwargs.get("creationflags", "MISSING")
|
||||
return real_run(*args, **kwargs)
|
||||
|
||||
monkeypatch.setattr(wg.subprocess, "run", fake_run)
|
||||
wg._config_names_for_scope(
|
||||
"--local",
|
||||
repo,
|
||||
{},
|
||||
"filter\\..*",
|
||||
_re.compile(r"^filter\."),
|
||||
ignore_unsupported=True,
|
||||
)
|
||||
|
||||
assert captured.get("creationflags") == _windows_hide_flags(), (
|
||||
"_config_names_for_scope must pass creationflags=_windows_hide_flags()"
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue