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
8 changes: 3 additions & 5 deletions emrg/server/git_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,9 @@ def _detect_git_remote(cwd: str) -> str:
Returns '' if detection fails.
"""
try:
result = subprocess.run(
["git", "remote", "get-url", "origin"],
cwd=cwd, capture_output=True, text=True, encoding="utf-8", timeout=5,
**win32_no_window_kwargs(),
)
# 用解析后的 git(install-info → bundled → PATH 回退,见 git_cmd),
# 防 daemon 启动环境无 PATH git 时误判 "not a git repo"(2026-08-12 事故)。
result = git_cmd("remote", "get-url", "origin", cwd=cwd, timeout=5)
if result.returncode == 0:
url = result.stdout.strip()
# Extract owner/repo from various URL formats:
Expand Down
29 changes: 17 additions & 12 deletions emrg/server/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,11 @@ def __init__(
self._repo_configured = project_name == "emrg"
self._session_id = f"emrg-evolution-{name}"
self._source_dir = path or name
# 解析一次 git 可执行路径(install-info.json → bundled → PATH 回退)。
# 2026-08-12 事故:daemon 从无 PATH git 的环境重启后,裸 `git` 调用
# FileNotFoundError → _is_usable_git_repo() 误判 "not a git repo" →
# 演化周期全部跳过。此后所有 git 调用走 resolve_git_gh() 的确定性解析。
self._git_exe = resolve_git_gh()[0] or "git"
# One-shot https-origin probe per handler lifetime (see
# _ensure_origin_reachable) — avoids re-probing every cycle.
self._origin_probed = False
Expand All @@ -278,7 +283,7 @@ def _get_git_head(self) -> str | None:
"""Return current git HEAD hash, or None if not a git repo."""
try:
result = subprocess.run(
["git", "rev-parse", "HEAD"],
[self._git_exe, "rev-parse", "HEAD"],
cwd=self._source_dir,
capture_output=True,
text=True,
Expand Down Expand Up @@ -317,7 +322,7 @@ def _is_usable_git_repo(self, path: str) -> bool:
return False
try:
result = subprocess.run(
["git", "rev-parse", "--is-inside-work-tree"],
[self._git_exe, "rev-parse", "--is-inside-work-tree"],
cwd=path,
capture_output=True,
text=True,
Expand All @@ -339,7 +344,7 @@ def _ensure_git_identity(self, repo_dir: Path) -> None:
try:
for key, default in (("user.name", name), ("user.email", email)):
result = subprocess.run(
["git", "config", key],
[self._git_exe, "config", key],
cwd=repo_dir,
capture_output=True,
text=True,
Expand All @@ -350,7 +355,7 @@ def _ensure_git_identity(self, repo_dir: Path) -> None:
)
if not result.stdout.strip():
subprocess.run(
["git", "config", key, default],
[self._git_exe, "config", key, default],
cwd=repo_dir,
capture_output=True,
timeout=5,
Expand Down Expand Up @@ -381,7 +386,7 @@ def _align_to_installed_version(self, repo_dir: Path) -> None:
return
try:
result = subprocess.run(
["git", "tag", "-l", tag],
[self._git_exe, "tag", "-l", tag],
cwd=repo_dir,
capture_output=True,
text=True,
Expand All @@ -392,7 +397,7 @@ def _align_to_installed_version(self, repo_dir: Path) -> None:
)
if result.returncode == 0 and tag in result.stdout.split():
subprocess.run(
["git", "checkout", "-B", "master", tag],
[self._git_exe, "checkout", "-B", "master", tag],
cwd=repo_dir,
capture_output=True,
text=True,
Expand Down Expand Up @@ -507,7 +512,7 @@ def _clone_workspace(self, repo_url: str, target: Path) -> None:
block https git transport (observed on the packaged host). Other
failures (auth / 404 / repo-specific) propagate unchanged.
"""
cmd = ["git", "-c", "http.connectTimeout=10", "clone", repo_url, str(target)]
cmd = [self._git_exe, "-c", "http.connectTimeout=10", "clone", repo_url, str(target)]
reason = ""
try:
subprocess.run(
Expand All @@ -527,7 +532,7 @@ def _clone_workspace(self, repo_url: str, target: Path) -> None:
self.name, reason,
)
subprocess.run(
["git", "clone", ssh_url, str(target)],
[self._git_exe, "clone", ssh_url, str(target)],
capture_output=True, text=True, encoding="utf-8",
timeout=120, check=True, env=no_prompt_env(),
**win32_no_window_kwargs(),
Expand All @@ -553,7 +558,7 @@ def _ensure_origin_reachable(self) -> None:
if not ssh_url:
return # not a github.com https origin — nothing to switch
result = subprocess.run(
["git", "-c", "http.connectTimeout=4", "ls-remote", origin, "HEAD"],
[self._git_exe, "-c", "http.connectTimeout=4", "ls-remote", origin, "HEAD"],
cwd=self._source_dir,
capture_output=True,
text=True,
Expand All @@ -567,7 +572,7 @@ def _ensure_origin_reachable(self) -> None:
if not is_git_connection_error(result.stderr):
return # auth/404 etc — switching would not help
switch = subprocess.run(
["git", "remote", "set-url", "origin", ssh_url],
[self._git_exe, "remote", "set-url", "origin", ssh_url],
cwd=self._source_dir,
capture_output=True,
text=True,
Expand Down Expand Up @@ -731,7 +736,7 @@ def _remote_advanced(self) -> bool:
if not local:
return False
result = subprocess.run(
["git", "-c", "http.connectTimeout=4", "ls-remote", "origin", "master"],
[self._git_exe, "-c", "http.connectTimeout=4", "ls-remote", "origin", "master"],
cwd=self._source_dir,
capture_output=True,
text=True,
Expand All @@ -745,7 +750,7 @@ def _remote_advanced(self) -> bool:
ssh_url = https_to_ssh_url(git_origin_url(self._source_dir))
if ssh_url and is_git_connection_error(result.stderr):
result = subprocess.run(
["git", "ls-remote", ssh_url, "master"],
[self._git_exe, "ls-remote", ssh_url, "master"],
cwd=self._source_dir,
capture_output=True,
text=True,
Expand Down
14 changes: 9 additions & 5 deletions tests/test_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,11 @@ def _norm(cmd):
def __call__(self, cmd, *args, **kwargs):
self.calls.append((list(cmd), kwargs.get("cwd")))
cwd = kwargs.get("cwd") or ""
if cmd[0] == "git":
# cmd[0] 可能是字面 "git"(dev 环境)或 resolve_git_gh() 解析出的
# 绝对路径(bundled git,2026-08-12 workspace-not-ready 事故修复后)——
# 统一按 basename 判断,避免测试在两种环境下行为不一致。
cmd_head = Path(cmd[0]).name
if cmd_head in ("git", "git.exe"):
sub = self._norm(cmd)
if sub and sub[0] == "rev-parse":
if "--is-inside-work-tree" in sub:
Expand Down Expand Up @@ -971,7 +975,7 @@ def test_ensure_origin_reachable_switches_to_ssh_when_https_blocked(tmp_path):

set_url_calls = [
c for c in fake.calls
if c[0][0] == "git" and c[0][1] == "remote" and c[0][2] == "set-url"
if Path(c[0][0]).name in ("git", "git.exe") and c[0][1] == "remote" and c[0][2] == "set-url"
]
assert len(set_url_calls) == 1, f"expected one set-url, got {fake.calls}"
assert set_url_calls[0][0][4] == "git@github.com:argszero/emrg.git"
Expand Down Expand Up @@ -1000,7 +1004,7 @@ def test_ensure_origin_reachable_probes_only_once(tmp_path):

set_url_calls = [
c for c in fake.calls
if c[0][0] == "git" and c[0][1] == "remote" and c[0][2] == "set-url"
if Path(c[0][0]).name in ("git", "git.exe") and c[0][1] == "remote" and c[0][2] == "set-url"
]
assert len(set_url_calls) == 1

Expand All @@ -1024,7 +1028,7 @@ def test_ensure_origin_reachable_keeps_https_when_reachable(tmp_path):

set_url_calls = [
c for c in fake.calls
if c[0][0] == "git" and c[0][1] == "remote" and c[0][2] == "set-url"
if Path(c[0][0]).name in ("git", "git.exe") and c[0][1] == "remote" and c[0][2] == "set-url"
]
assert set_url_calls == []

Expand All @@ -1051,7 +1055,7 @@ def test_ensure_origin_reachable_ignores_non_connection_errors(tmp_path):

set_url_calls = [
c for c in fake.calls
if c[0][0] == "git" and c[0][1] == "remote" and c[0][2] == "set-url"
if Path(c[0][0]).name in ("git", "git.exe") and c[0][1] == "remote" and c[0][2] == "set-url"
]
assert set_url_calls == []

Expand Down
Loading