fix(update): expose PM's git only for git checkouts, and record the installer's copy
A git-less Windows ZIP install has no .git and never runs git, yet _prepare_git_command called expose_pm_git() before it checked use_zip_update: offline or with no pin, pm.ensure raised and aborted an update that never needed git. expose_pm_git now takes the project root and does nothing unless it is a git checkout (both callers). When install.ps1 runs as one process, the git it staged into PM's store is on the inherited PATH, so expose_pm_git returned early and PM's facts never recorded it; plain hermes processes (plugin git installs, doctor, version info) had no git until the first `hermes update`. A git found under PM's store is now treated as that unrecorded copy and ensured, so the source completion writes the git fact at install time. Co-authored-by: JoaoMarcos44 <joaomarcosdias444@gmail.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Mapping, NoReturn, Sequence
|
||||
|
||||
__all__ = [
|
||||
@@ -372,22 +373,26 @@ def selected_git_env(base: Mapping[str, str] | None = None) -> dict[str, str]:
|
||||
return env
|
||||
|
||||
|
||||
def expose_pm_git() -> None:
|
||||
"""Put PM's git on this process's PATH when Windows has no git of its own.
|
||||
def expose_pm_git(project_root: Path) -> None:
|
||||
"""Put PM's git on PATH, and in PM's facts, for a Windows git checkout.
|
||||
|
||||
On a machine without git, install.ps1 stages the pinned Git for Windows for
|
||||
its own process only; PM's facts never record it. Every later bare ``git``
|
||||
(``hermes update``, the source-completion stamp) then died with
|
||||
``[WinError 2]``. Callers are explicit user actions (like
|
||||
``ensure_tools_for_sync``), so acquire PM's git outright; children inherit
|
||||
the PATH. A machine with a working git is untouched. Raises what
|
||||
``pm.ensure`` raises.
|
||||
install.ps1 stages the pinned Git for Windows into PM's store for its own
|
||||
process only, and PM's facts never record it, so every later bare ``git``
|
||||
(``hermes update``, the source-completion stamp, plugin installs, doctor)
|
||||
died with ``[WinError 2]``. A git found under PM's store is that unrecorded
|
||||
copy inherited from the installer, so it is recorded too. Callers are
|
||||
explicit user actions (like ``ensure_tools_for_sync``), so acquire PM's git
|
||||
outright; children inherit the PATH. The machine's own git, and a git-less
|
||||
ZIP install that never runs git, are untouched. Raises what ``pm.ensure``
|
||||
raises.
|
||||
"""
|
||||
if sys.platform != "win32":
|
||||
if sys.platform != "win32" or not (Path(project_root) / ".git").exists():
|
||||
return
|
||||
from hermes_platform.resolver import locate_command
|
||||
from pm.paths import store_root
|
||||
|
||||
if locate_command("git").found:
|
||||
found = locate_command("git").command
|
||||
if found and not Path(found[0]).resolve().is_relative_to(store_root()):
|
||||
return
|
||||
from pm import ensure
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ def complete_source_checkout(
|
||||
|
||||
# The builds, the release-history refresh and the install stamp all run
|
||||
# git; a fresh Windows machine has only PM's.
|
||||
expose_pm_git()
|
||||
expose_pm_git(root)
|
||||
except Exception as exc: # noqa: BLE001 — git-less steps below still complete
|
||||
print(f"⚠ Could not provide git for the source completion: {exc}", file=sys.stderr)
|
||||
publish_launchers(root)
|
||||
|
||||
@@ -1106,7 +1106,7 @@ def _prepare_git_command() -> tuple[bool, list, bool]:
|
||||
|
||||
from hermes_cli._subprocess_compat import expose_pm_git
|
||||
|
||||
expose_pm_git()
|
||||
expose_pm_git(_m().PROJECT_ROOT)
|
||||
git_cmd = _base_git_cmd()
|
||||
if sys.platform == "win32" and git_dir.exists():
|
||||
_git_run(git_cmd, ["config", "windows.appendAtomically", "false"])
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""Windows machines whose only git is the one PM provides (install.ps1 fresh installs)."""
|
||||
"""Windows installs whose git is PM's: install.ps1 stages it, PM's facts must record it."""
|
||||
import os
|
||||
|
||||
import pm
|
||||
import pm.paths
|
||||
import pytest
|
||||
|
||||
from hermes_cli import _subprocess_compat as compat
|
||||
@@ -9,12 +10,21 @@ from pm.package import Runner
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def windows(monkeypatch):
|
||||
def windows(monkeypatch, tmp_path):
|
||||
store = tmp_path / "tools"
|
||||
staged_git = store / "git-2.53.0+3-win32-x64" / "cmd" / "git.exe"
|
||||
staged_git.parent.mkdir(parents=True)
|
||||
staged_git.touch()
|
||||
(tmp_path / "checkout" / ".git").mkdir(parents=True)
|
||||
monkeypatch.setattr(compat.sys, "platform", "win32")
|
||||
monkeypatch.setattr(pm.paths, "store_root", lambda: store)
|
||||
monkeypatch.setenv("PATH", r"C:\Windows\System32")
|
||||
return tmp_path, staged_git
|
||||
|
||||
|
||||
def test_pm_git_goes_on_path_when_windows_has_none(windows, monkeypatch):
|
||||
@pytest.mark.parametrize("git_on_path", ["none", "installer-staged"])
|
||||
def test_pm_git_is_acquired_and_recorded_when_windows_has_no_git_of_its_own(windows, monkeypatch, git_on_path):
|
||||
root, staged_git = windows
|
||||
calls = []
|
||||
store_path = r"C:\store\git-2.53.0+3-win32-x64\cmd;C:\store\git-2.53.0+3-win32-x64\usr\bin;C:\Windows\System32"
|
||||
|
||||
@@ -22,19 +32,25 @@ def test_pm_git_goes_on_path_when_windows_has_none(windows, monkeypatch):
|
||||
calls.append((name, kwargs))
|
||||
return Runner(name, {"Path": store_path})
|
||||
|
||||
monkeypatch.setattr(compat.shutil, "which", lambda name, *a, **k: None)
|
||||
found = str(staged_git) if git_on_path == "installer-staged" else None
|
||||
monkeypatch.setattr(compat.shutil, "which", lambda name, *a, **k: found)
|
||||
monkeypatch.setattr(pm, "ensure", ensure)
|
||||
|
||||
compat.expose_pm_git()
|
||||
compat.expose_pm_git(root / "checkout")
|
||||
|
||||
assert calls == [("git", {"explicit": True})]
|
||||
assert os.environ["PATH"] == store_path
|
||||
|
||||
|
||||
def test_a_working_windows_git_is_left_alone(windows, monkeypatch):
|
||||
monkeypatch.setattr(compat.shutil, "which", lambda name, *a, **k: r"C:\Program Files\Git\cmd\git.exe")
|
||||
monkeypatch.setattr(pm, "ensure", lambda *a, **k: pytest.fail("acquired PM git over a working one"))
|
||||
@pytest.mark.parametrize("install", ["own-git", "git-less-zip"])
|
||||
def test_own_git_and_git_less_zip_installs_are_left_alone(windows, monkeypatch, install):
|
||||
root, _ = windows
|
||||
own_git = str(root / "Git" / "cmd" / "git.exe") if install == "own-git" else None
|
||||
if install == "git-less-zip":
|
||||
(root / "checkout" / ".git").rmdir()
|
||||
monkeypatch.setattr(compat.shutil, "which", lambda name, *a, **k: own_git)
|
||||
monkeypatch.setattr(pm, "ensure", lambda *a, **k: pytest.fail("acquired PM git"))
|
||||
|
||||
compat.expose_pm_git()
|
||||
compat.expose_pm_git(root / "checkout")
|
||||
|
||||
assert os.environ["PATH"] == r"C:\Windows\System32"
|
||||
|
||||
Reference in New Issue
Block a user