fix(update): locate PortableGit under the shared root, not profile home

Review feedback on #88136 (monerostar): a profile-scoped `hermes update`
sets HERMES_HOME to <root>/profiles/<name>, but the Hermes-managed
PortableGit tree lives under the SHARED root (<root>/git/...). The locator
checked get_hermes_home() only, so a broken trampoline during a
profile-scoped update was not swapped and fell through to ZIP.

Extract _portable_git_candidates() (shared root first, profile home as
fallback) and add a regression test for the profile layout.
This commit is contained in:
SayHell0W0rld
2026-08-17 21:16:08 +08:00
committed by Teknium
parent 0bab9ff9e7
commit 6cccc2ef7e
2 changed files with 43 additions and 13 deletions

View File

@@ -37,7 +37,7 @@ from pathlib import Path
from typing import Optional
from hermes_cli.config import get_hermes_home
from hermes_constants import venv_python_path
from hermes_constants import get_default_hermes_root, venv_python_path
logger = logging.getLogger(__name__)
@@ -7381,6 +7381,26 @@ def _git_is_trampoline(git_cmd: list) -> bool:
return "fork bomb" in output
def _portable_git_candidates() -> list:
"""PortableGit candidate paths: shared root first, then profile home.
The Hermes-managed PortableGit tree lives under the SHARED root
(``<root>/git/...``), not the profile-scoped HERMES_HOME
(``<root>/profiles/<name>``), so a profile-scoped ``hermes update`` must
look there (monerostar review, #87876). The profile-home candidate is
kept as a fallback for custom layouts that place it there.
"""
candidates = []
try:
for root in (get_default_hermes_root(), Path(get_hermes_home())):
candidates.append(
root / "git" / "mingw64" / "libexec" / "git-core" / "git.exe"
)
except Exception:
pass
return candidates
def _locate_real_git() -> Optional[Path]:
"""Find a real Git-for-Windows binary that is not a broken trampoline.
@@ -7396,18 +7416,7 @@ def _locate_real_git() -> Optional[Path]:
candidates = [
Path(r"C:\Program Files\Git\mingw64\libexec\git-core\git.exe"),
Path(r"C:\Program Files (x86)\Git\mingw64\libexec\git-core\git.exe"),
]
try:
candidates.append(
Path(get_hermes_home())
/ "git"
/ "mingw64"
/ "libexec"
/ "git-core"
/ "git.exe"
)
except Exception:
pass
] + _portable_git_candidates()
for candidate in candidates:
if not candidate.exists():
continue

View File

@@ -1509,3 +1509,24 @@ class TestGitTrampolineSelfHeal:
result = update_cmd._ensure_non_trampoline_git(git_cmd)
assert result == git_cmd
run.assert_not_called()
def test_portable_git_candidates_check_shared_root_first(self, tmp_path, monkeypatch):
# Profile-scoped layout: HERMES_HOME = <root>/profiles/foo, but the
# PortableGit tree lives under the SHARED root (monerostar review on
# #88136). The candidate list must check get_default_hermes_root()
# before the profile home.
from hermes_cli import update_cmd
root = tmp_path / "root"
profile_home = root / "profiles" / "foo"
monkeypatch.setattr(update_cmd, "get_default_hermes_root", lambda: root)
monkeypatch.setattr(update_cmd, "get_hermes_home", lambda: profile_home)
candidates = update_cmd._portable_git_candidates()
assert candidates[0] == (
root / "git" / "mingw64" / "libexec" / "git-core" / "git.exe"
)
assert candidates[1] == (
profile_home / "git" / "mingw64" / "libexec" / "git-core" / "git.exe"
)