mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
perf(update): cut redundant network + subprocess work from hermes update
Four fixes on the updater path: 1. uv self update freshness gate + timeout (managed_uv.py): the network self-update ran on EVERY hermes update — including the 'Already up to date!' fast path — with NO timeout (unbounded hang risk offline). Now skipped when it succeeded within 7 days (stamp file under HERMES_HOME/cache), capped at 60s, force= override available. The CVE-driven vulnerable-runtime repair probe is NEVER gated — it still runs on every invocation. 2. Drop the second network fetch from the pull step (main.py): the update flow fetched origin/<branch>, counted commits, then ran 'git pull --ff-only origin <branch>' — a SECOND fetch of the same ref (~0.5-1.5s). Now merges the already-fetched tracking ref via 'git merge --ff-only origin/<branch>'; the diverged-history reset fallback is unchanged. 3. Probe the upstream remote locally before fetching it (_cmd_update_check): non-fork installs have no 'upstream' remote, and --check burned a failed network attempt (~0.3-1s) on every run before falling back to origin. 'git remote get-url upstream' (~1ms local) now gates the fetch. 4. Desktop rebuild check reads the content-hash stamp in-process before spawning 'hermes desktop --build-only' (a full CLI re-import, ~1-3s) just to learn nothing changed. Stamp errors fall through to the subprocess path unchanged. Savings on a no-op 'hermes update': ~2-6s (uv self-update 0.5-3s + second fetch 0.5-1.5s + desktop spawn 1-3s when applicable). 187 targeted updater tests green incl. 5 new stamp-gate tests.
This commit is contained in:
parent
9179fb72ea
commit
3a69e34702
6 changed files with 244 additions and 74 deletions
2
.lazy-refresh-incomplete
Normal file
2
.lazy-refresh-incomplete
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
started=1785343166.9711895
|
||||
pid=2093896
|
||||
|
|
@ -11030,15 +11030,33 @@ def _cmd_update_check(branch: str = "main", *, branch_explicit: bool = False):
|
|||
depth_args = ["--depth", "1"] if is_shallow else []
|
||||
|
||||
if branch == "main":
|
||||
print("→ Fetching from upstream...")
|
||||
fetch_result = subprocess.run(
|
||||
git_cmd + ["fetch"] + depth_args + ["upstream", branch],
|
||||
cwd=PROJECT_ROOT,
|
||||
capture_output=True,
|
||||
text=True, encoding="utf-8", errors="replace",
|
||||
# Probe locally (~6 ms) whether an 'upstream' remote exists at all
|
||||
# before spending a network fetch on it. Non-fork installs have no
|
||||
# 'upstream' remote, and the old flow burned a failed network attempt
|
||||
# (~0.3-1 s) on every --check before falling back to origin.
|
||||
has_upstream_remote = (
|
||||
subprocess.run(
|
||||
git_cmd + ["remote", "get-url", "upstream"],
|
||||
cwd=PROJECT_ROOT,
|
||||
capture_output=True,
|
||||
text=True, encoding="utf-8", errors="replace",
|
||||
).returncode
|
||||
== 0
|
||||
)
|
||||
if fetch_result.returncode != 0:
|
||||
# Fallback to origin if upstream doesn't exist
|
||||
fetch_result = None
|
||||
if has_upstream_remote:
|
||||
print("→ Fetching from upstream...")
|
||||
fetch_result = subprocess.run(
|
||||
git_cmd + ["fetch"] + depth_args + ["upstream", branch],
|
||||
cwd=PROJECT_ROOT,
|
||||
capture_output=True,
|
||||
text=True, encoding="utf-8", errors="replace",
|
||||
)
|
||||
if fetch_result is not None and fetch_result.returncode == 0:
|
||||
upstream_exists = True
|
||||
compare_branch = f"upstream/{branch}"
|
||||
else:
|
||||
# No upstream remote, or the upstream fetch failed — use origin.
|
||||
print("→ Fetching from origin...")
|
||||
fetch_result = subprocess.run(
|
||||
git_cmd + ["fetch"] + depth_args + ["origin", branch],
|
||||
|
|
@ -11048,9 +11066,6 @@ def _cmd_update_check(branch: str = "main", *, branch_explicit: bool = False):
|
|||
)
|
||||
upstream_exists = False
|
||||
compare_branch = f"origin/{branch}"
|
||||
else:
|
||||
upstream_exists = True
|
||||
compare_branch = f"upstream/{branch}"
|
||||
else:
|
||||
# Non-default branch: compare against origin/<branch> directly.
|
||||
print("→ Fetching from origin...")
|
||||
|
|
@ -12551,8 +12566,14 @@ def _cmd_update_impl(args, gateway_mode: bool):
|
|||
# the bad commit and the fix landing).
|
||||
pre_pull_sha = _capture_head_sha(git_cmd, PROJECT_ROOT)
|
||||
try:
|
||||
# Merge the ref we already fetched above (→ Fetching updates...)
|
||||
# instead of `git pull`, which performs a SECOND network fetch of
|
||||
# the same branch (~0.5-1.5 s of redundant round-trip per update).
|
||||
# `merge --ff-only origin/<branch>` is byte-identical in effect to
|
||||
# `pull --ff-only origin <branch>` given the fresh tracking ref;
|
||||
# the divergence fallback below is unchanged.
|
||||
pull_result = subprocess.run(
|
||||
git_cmd + ["pull", "--ff-only", "origin", branch],
|
||||
git_cmd + ["merge", "--ff-only", f"origin/{branch}"],
|
||||
cwd=PROJECT_ROOT,
|
||||
capture_output=True,
|
||||
text=True, encoding="utf-8", errors="replace",
|
||||
|
|
@ -12785,34 +12806,51 @@ def _cmd_update_impl(args, gateway_mode: bool):
|
|||
has_desktop_app = _desktop_packaged_executable(desktop_dir) is not None or _desktop_dist_exists(desktop_dir)
|
||||
if (desktop_dir / "package.json").exists() and _resolve_node_runtime_npm() and has_desktop_app:
|
||||
print("→ Checking if desktop app needs rebuilding...")
|
||||
_desktop_build_cmd = [sys.executable, "-m", "hermes_cli.main", "desktop", "--build-only"]
|
||||
# Capture the (very loud) Electron/vite build output into
|
||||
# update.log instead of streaming it to the terminal. On the rare
|
||||
# nonzero exit, retry once after waiting again for the venv — this
|
||||
# covers a still-settling rebuild window the first wait didn't fully
|
||||
# catch — then surface the captured tail so the failure is
|
||||
# debuggable.
|
||||
#
|
||||
# Start the build subprocess with the Hermes-managed Node on PATH:
|
||||
# when `hermes update` runs inside the desktop updater chain
|
||||
# (Desktop → hermes-setup → hermes update), the shell PATH
|
||||
# customizations are lost, so a bare-PATH child would fail with
|
||||
# `node: not found` before cmd_gui can self-heal.
|
||||
from hermes_constants import with_hermes_node_path
|
||||
|
||||
_build_env = with_hermes_node_path()
|
||||
build_result = _run_logged_subprocess(_desktop_build_cmd, cwd=PROJECT_ROOT, env=_build_env)
|
||||
if build_result.returncode != 0:
|
||||
build_result = _run_logged_subprocess(_desktop_build_cmd, cwd=PROJECT_ROOT, env=_build_env)
|
||||
if build_result.returncode != 0:
|
||||
print(" ⚠ Desktop build failed (non-fatal; run `hermes desktop` to retry)")
|
||||
tail = "\n".join((build_result.stdout or "").strip().splitlines()[-15:])
|
||||
if tail:
|
||||
print(tail)
|
||||
from hermes_constants import display_hermes_home as _dhh
|
||||
print(f" Full build log: {_dhh()}/logs/update.log")
|
||||
else:
|
||||
# Consult the content-hash stamp IN-PROCESS first. The spawned
|
||||
# `hermes desktop --build-only` subprocess re-imports the whole
|
||||
# CLI stack (~1-3 s) just to reach the same _desktop_build_needed
|
||||
# check; when the stamp already says "up to date" we can skip the
|
||||
# spawn entirely. The update path never passes --source, so the
|
||||
# subprocess would run with source_mode=False — mirror that here.
|
||||
# Any error in the pre-check falls through to the subprocess.
|
||||
_skip_desktop_build = False
|
||||
try:
|
||||
_skip_desktop_build = not _desktop_build_needed(
|
||||
desktop_dir, PROJECT_ROOT, source_mode=False
|
||||
)
|
||||
except Exception:
|
||||
_skip_desktop_build = False
|
||||
if _skip_desktop_build:
|
||||
print(" ✓ Desktop app up to date")
|
||||
else:
|
||||
_desktop_build_cmd = [sys.executable, "-m", "hermes_cli.main", "desktop", "--build-only"]
|
||||
# Capture the (very loud) Electron/vite build output into
|
||||
# update.log instead of streaming it to the terminal. On the rare
|
||||
# nonzero exit, retry once after waiting again for the venv — this
|
||||
# covers a still-settling rebuild window the first wait didn't fully
|
||||
# catch — then surface the captured tail so the failure is
|
||||
# debuggable.
|
||||
#
|
||||
# Start the build subprocess with the Hermes-managed Node on PATH:
|
||||
# when `hermes update` runs inside the desktop updater chain
|
||||
# (Desktop → hermes-setup → hermes update), the shell PATH
|
||||
# customizations are lost, so a bare-PATH child would fail with
|
||||
# `node: not found` before cmd_gui can self-heal.
|
||||
from hermes_constants import with_hermes_node_path
|
||||
|
||||
_build_env = with_hermes_node_path()
|
||||
build_result = _run_logged_subprocess(_desktop_build_cmd, cwd=PROJECT_ROOT, env=_build_env)
|
||||
if build_result.returncode != 0:
|
||||
build_result = _run_logged_subprocess(_desktop_build_cmd, cwd=PROJECT_ROOT, env=_build_env)
|
||||
if build_result.returncode != 0:
|
||||
print(" ⚠ Desktop build failed (non-fatal; run `hermes desktop` to retry)")
|
||||
tail = "\n".join((build_result.stdout or "").strip().splitlines()[-15:])
|
||||
if tail:
|
||||
print(tail)
|
||||
from hermes_constants import display_hermes_home as _dhh
|
||||
print(f" Full build log: {_dhh()}/logs/update.log")
|
||||
else:
|
||||
print(" ✓ Desktop app up to date")
|
||||
|
||||
print()
|
||||
print("✓ Code updated!")
|
||||
|
|
|
|||
|
|
@ -274,9 +274,46 @@ def ensure_uv(
|
|||
return _UvResult(result)
|
||||
|
||||
|
||||
def _uv_self_update_is_fresh(now: float | None = None) -> bool:
|
||||
"""Return True when ``uv self update`` ran recently enough to skip.
|
||||
|
||||
uv releases roughly weekly while many users run ``hermes update`` daily;
|
||||
re-running a blocking network self-update on every invocation is waste
|
||||
and, offline, an unbounded hang risk. A stamp file under HERMES_HOME
|
||||
caches the last successful self-update time.
|
||||
"""
|
||||
try:
|
||||
from hermes_constants import get_hermes_home
|
||||
|
||||
stamp = get_hermes_home() / "cache" / ".uv_self_update_stamp"
|
||||
age = (now if now is not None else time.time()) - stamp.stat().st_mtime
|
||||
return 0 <= age < UV_SELF_UPDATE_INTERVAL_SECONDS
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _touch_uv_self_update_stamp() -> None:
|
||||
try:
|
||||
from hermes_constants import get_hermes_home
|
||||
|
||||
stamp = get_hermes_home() / "cache" / ".uv_self_update_stamp"
|
||||
stamp.parent.mkdir(parents=True, exist_ok=True)
|
||||
stamp.touch()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
# uv ships releases ~weekly; refresh the managed binary at most this often.
|
||||
UV_SELF_UPDATE_INTERVAL_SECONDS = 7 * 24 * 3600
|
||||
# `uv self update` is a network call; unbounded it can hang forever on a
|
||||
# blackholed connection (no default timeout in uv's downloader path).
|
||||
UV_SELF_UPDATE_TIMEOUT_SECONDS = 60
|
||||
|
||||
|
||||
def update_managed_uv(
|
||||
*,
|
||||
repair_observer: Callable[[RuntimeRepairResult], None] | None = None,
|
||||
force: bool = False,
|
||||
) -> Optional[str]:
|
||||
"""Run ``uv self update`` on the managed uv binary.
|
||||
|
||||
|
|
@ -284,31 +321,43 @@ def update_managed_uv(
|
|||
Returns the managed path when uv is available and ``None`` otherwise.
|
||||
A self-update failure is non-fatal because the old version still works.
|
||||
``repair_observer``, when provided, receives the runtime repair result.
|
||||
|
||||
The network self-update is skipped when it succeeded within the last
|
||||
``UV_SELF_UPDATE_INTERVAL_SECONDS`` (7 days) unless ``force=True``; the
|
||||
vulnerable-runtime repair probe below ALWAYS runs — CVE-driven runtime
|
||||
repair must never be gated behind the freshness stamp.
|
||||
"""
|
||||
existing = resolve_uv()
|
||||
if not existing:
|
||||
# Not installed yet — ensure_uv() will handle that elsewhere.
|
||||
return None
|
||||
|
||||
result = subprocess.run(
|
||||
[existing, "self", "update"],
|
||||
capture_output=True,
|
||||
text=True, encoding='utf-8', errors='replace',
|
||||
check=False,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
version = subprocess.run(
|
||||
[existing, "--version"],
|
||||
capture_output=True,
|
||||
text=True, encoding='utf-8', errors='replace',
|
||||
check=False,
|
||||
).stdout.strip()
|
||||
print(f" ✓ Managed uv updated ({version})")
|
||||
else:
|
||||
# Non-fatal — old uv still works fine.
|
||||
logger.debug(
|
||||
"uv self update failed (rc=%d): %s", result.returncode, result.stderr
|
||||
)
|
||||
if force or not _uv_self_update_is_fresh():
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[existing, "self", "update"],
|
||||
capture_output=True,
|
||||
text=True, encoding='utf-8', errors='replace',
|
||||
check=False,
|
||||
timeout=UV_SELF_UPDATE_TIMEOUT_SECONDS,
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.debug("uv self update timed out after %ss", UV_SELF_UPDATE_TIMEOUT_SECONDS)
|
||||
result = None
|
||||
if result is not None and result.returncode == 0:
|
||||
_touch_uv_self_update_stamp()
|
||||
version = subprocess.run(
|
||||
[existing, "--version"],
|
||||
capture_output=True,
|
||||
text=True, encoding='utf-8', errors='replace',
|
||||
check=False,
|
||||
).stdout.strip()
|
||||
print(f" ✓ Managed uv updated ({version})")
|
||||
elif result is not None:
|
||||
# Non-fatal — old uv still works fine.
|
||||
logger.debug(
|
||||
"uv self update failed (rc=%d): %s", result.returncode, result.stderr
|
||||
)
|
||||
|
||||
# Keep this hook inside the long-standing API. During an update, main.py is
|
||||
# already imported from the old checkout, then ``git pull`` replaces this
|
||||
|
|
|
|||
|
|
@ -359,10 +359,11 @@ class TestCmdUpdateBranchFallback:
|
|||
assert "origin/main" in rev_list_cmds[0]
|
||||
assert "origin/fix/stoicneko" not in rev_list_cmds[0]
|
||||
|
||||
# pull should use main, not fix/stoicneko
|
||||
pull_cmds = [c for c in commands if "pull" in c]
|
||||
assert len(pull_cmds) == 1
|
||||
assert "main" in pull_cmds[0]
|
||||
# the ff-only merge should target origin/main, not the feature branch
|
||||
merge_cmds = [c for c in commands if "merge --ff-only" in c]
|
||||
assert len(merge_cmds) == 1
|
||||
assert "origin/main" in merge_cmds[0]
|
||||
assert "fix/stoicneko" not in merge_cmds[0]
|
||||
|
||||
@patch("shutil.which", return_value=None)
|
||||
@patch("subprocess.run")
|
||||
|
|
@ -381,9 +382,9 @@ class TestCmdUpdateBranchFallback:
|
|||
assert len(rev_list_cmds) == 1
|
||||
assert "origin/main" in rev_list_cmds[0]
|
||||
|
||||
pull_cmds = [c for c in commands if "pull" in c]
|
||||
assert len(pull_cmds) == 1
|
||||
assert "main" in pull_cmds[0]
|
||||
merge_cmds = [c for c in commands if "merge --ff-only" in c]
|
||||
assert len(merge_cmds) == 1
|
||||
assert "origin/main" in merge_cmds[0]
|
||||
|
||||
@patch("shutil.which", return_value=None)
|
||||
@patch("subprocess.run")
|
||||
|
|
@ -408,9 +409,9 @@ class TestCmdUpdateBranchFallback:
|
|||
assert update_observer.__self__ is ensure_observer.__self__
|
||||
assert update_observer.__self__ == []
|
||||
|
||||
# Should NOT have called pull
|
||||
# Should NOT have advanced the checkout (no pull, no ff-only merge)
|
||||
commands = [" ".join(str(a) for a in c.args[0]) for c in mock_run.call_args_list]
|
||||
pull_cmds = [c for c in commands if "pull" in c]
|
||||
pull_cmds = [c for c in commands if "pull" in c or "merge --ff-only" in c]
|
||||
assert len(pull_cmds) == 0
|
||||
|
||||
@patch("shutil.which", return_value=None)
|
||||
|
|
@ -824,9 +825,9 @@ class TestCmdUpdateBranchFlag:
|
|||
assert any("origin/bb/gui" in c for c in rev_list_cmds), rev_list_cmds
|
||||
assert not any("origin/main" in c for c in rev_list_cmds), rev_list_cmds
|
||||
|
||||
# pull must target bb/gui
|
||||
pull_cmds = [c for c in commands if "pull" in c and "ff-only" in c]
|
||||
assert any("bb/gui" in c and "main" not in c.split() for c in pull_cmds), pull_cmds
|
||||
# the ff-only merge must target origin/bb/gui
|
||||
merge_cmds = [c for c in commands if "merge --ff-only" in c]
|
||||
assert any("origin/bb/gui" in c and "origin/main" not in c for c in merge_cmds), merge_cmds
|
||||
|
||||
@patch("shutil.which", return_value=None)
|
||||
@patch("subprocess.run")
|
||||
|
|
|
|||
|
|
@ -296,6 +296,86 @@ class TestUpdateManagedUv:
|
|||
# Still returns the path — failure is non-fatal
|
||||
assert result == str(tmp_path / "bin" / "uv")
|
||||
|
||||
def test_fresh_stamp_skips_network_self_update_but_not_repair(self, tmp_path, monkeypatch):
|
||||
"""A recent success stamp must skip `uv self update` entirely while the
|
||||
vulnerable-runtime repair probe still runs (CVE repair is never gated)."""
|
||||
from hermes_cli.managed_uv import RuntimeRepairResult, update_managed_uv
|
||||
|
||||
uv = tmp_path / "bin" / "uv"
|
||||
_make_executable(uv)
|
||||
# Fresh stamp under the isolated HERMES_HOME.
|
||||
import hermes_constants
|
||||
stamp = hermes_constants.get_hermes_home() / "cache" / ".uv_self_update_stamp"
|
||||
stamp.parent.mkdir(parents=True, exist_ok=True)
|
||||
stamp.touch()
|
||||
|
||||
with patch("hermes_cli.managed_uv.get_hermes_home", return_value=tmp_path), \
|
||||
patch("hermes_cli.managed_uv.subprocess.run") as mock_run, \
|
||||
patch(
|
||||
"hermes_cli.managed_uv.repair_vulnerable_runtime",
|
||||
return_value=RuntimeRepairResult("skipped"),
|
||||
) as mock_repair:
|
||||
result = update_managed_uv()
|
||||
|
||||
assert result == str(uv)
|
||||
assert mock_run.call_count == 0, "fresh stamp must skip the network self-update"
|
||||
mock_repair.assert_called_once_with(str(uv))
|
||||
|
||||
def test_force_overrides_fresh_stamp(self, tmp_path):
|
||||
from hermes_cli.managed_uv import update_managed_uv
|
||||
|
||||
uv = tmp_path / "bin" / "uv"
|
||||
_make_executable(uv)
|
||||
import hermes_constants
|
||||
stamp = hermes_constants.get_hermes_home() / "cache" / ".uv_self_update_stamp"
|
||||
stamp.parent.mkdir(parents=True, exist_ok=True)
|
||||
stamp.touch()
|
||||
|
||||
with patch("hermes_cli.managed_uv.get_hermes_home", return_value=tmp_path), \
|
||||
patch("hermes_cli.managed_uv.subprocess.run") as mock_run:
|
||||
mock_run.return_value = MagicMock(returncode=0, stdout="uv 0.2.0")
|
||||
result = update_managed_uv(force=True)
|
||||
|
||||
assert result == str(uv)
|
||||
assert mock_run.call_args_list[0][0][0] == [str(uv), "self", "update"]
|
||||
|
||||
def test_stale_stamp_runs_self_update_and_refreshes_stamp(self, tmp_path):
|
||||
import os as _os
|
||||
import time as _time
|
||||
|
||||
from hermes_cli.managed_uv import UV_SELF_UPDATE_INTERVAL_SECONDS, update_managed_uv
|
||||
|
||||
uv = tmp_path / "bin" / "uv"
|
||||
_make_executable(uv)
|
||||
import hermes_constants
|
||||
stamp = hermes_constants.get_hermes_home() / "cache" / ".uv_self_update_stamp"
|
||||
stamp.parent.mkdir(parents=True, exist_ok=True)
|
||||
stamp.touch()
|
||||
old = _time.time() - UV_SELF_UPDATE_INTERVAL_SECONDS - 60
|
||||
_os.utime(stamp, (old, old))
|
||||
|
||||
with patch("hermes_cli.managed_uv.get_hermes_home", return_value=tmp_path), \
|
||||
patch("hermes_cli.managed_uv.subprocess.run") as mock_run:
|
||||
mock_run.return_value = MagicMock(returncode=0, stdout="uv 0.2.0")
|
||||
update_managed_uv()
|
||||
|
||||
assert mock_run.call_args_list[0][0][0] == [str(uv), "self", "update"]
|
||||
assert stamp.stat().st_mtime > old + 30, "successful self-update must refresh the stamp"
|
||||
|
||||
def test_self_update_timeout_non_fatal(self, tmp_path):
|
||||
import subprocess as _subprocess
|
||||
|
||||
from hermes_cli.managed_uv import update_managed_uv
|
||||
|
||||
uv = tmp_path / "bin" / "uv"
|
||||
_make_executable(uv)
|
||||
with patch("hermes_cli.managed_uv.get_hermes_home", return_value=tmp_path), \
|
||||
patch("hermes_cli.managed_uv.subprocess.run") as mock_run:
|
||||
mock_run.side_effect = _subprocess.TimeoutExpired(cmd="uv self update", timeout=60)
|
||||
result = update_managed_uv()
|
||||
# Timeout is non-fatal; path still returned.
|
||||
assert result == str(uv)
|
||||
|
||||
def test_old_updater_api_triggers_runtime_repair(self, tmp_path):
|
||||
"""The pre-pull main.py call site must activate the fresh module hook."""
|
||||
from hermes_cli.managed_uv import RuntimeRepairResult, update_managed_uv
|
||||
|
|
|
|||
|
|
@ -418,7 +418,7 @@ def test_cmd_update_retries_optional_extras_individually_when_all_fails(monkeypa
|
|||
return SimpleNamespace(stdout="main\n", stderr="", returncode=0)
|
||||
if cmd == ["git", "rev-list", "HEAD..origin/main", "--count"]:
|
||||
return SimpleNamespace(stdout="1\n", stderr="", returncode=0)
|
||||
if cmd == ["git", "pull", "--ff-only", "origin", "main"]:
|
||||
if cmd == ["git", "merge", "--ff-only", "origin/main"]:
|
||||
return SimpleNamespace(stdout="Updating\n", stderr="", returncode=0)
|
||||
if cmd == ["/usr/bin/uv", "pip", "install", "-e", ".[all]"]:
|
||||
raise CalledProcessError(returncode=1, cmd=cmd)
|
||||
|
|
@ -467,7 +467,7 @@ def test_cmd_update_succeeds_with_extras(monkeypatch, tmp_path):
|
|||
return SimpleNamespace(stdout="main\n", stderr="", returncode=0)
|
||||
if cmd == ["git", "rev-list", "HEAD..origin/main", "--count"]:
|
||||
return SimpleNamespace(stdout="1\n", stderr="", returncode=0)
|
||||
if cmd == ["git", "pull", "--ff-only", "origin", "main"]:
|
||||
if cmd == ["git", "merge", "--ff-only", "origin/main"]:
|
||||
return SimpleNamespace(stdout="Updating\n", stderr="", returncode=0)
|
||||
return SimpleNamespace(returncode=0, stdout="", stderr="")
|
||||
|
||||
|
|
@ -557,7 +557,7 @@ def test_cmd_update_refreshes_active_memory_provider_dependencies(monkeypatch, t
|
|||
return SimpleNamespace(stdout="main\n", stderr="", returncode=0)
|
||||
if cmd == ["git", "rev-list", "HEAD..origin/main", "--count"]:
|
||||
return SimpleNamespace(stdout="1\n", stderr="", returncode=0)
|
||||
if cmd == ["git", "pull", "--ff-only", "origin", "main"]:
|
||||
if cmd == ["git", "merge", "--ff-only", "origin/main"]:
|
||||
return SimpleNamespace(stdout="Updating\n", stderr="", returncode=0)
|
||||
return SimpleNamespace(returncode=0, stdout="", stderr="")
|
||||
|
||||
|
|
@ -583,7 +583,7 @@ def test_cmd_update_reloads_runtime_modules_before_lazy_refresh(monkeypatch, tmp
|
|||
return SimpleNamespace(stdout="main\n", stderr="", returncode=0)
|
||||
if cmd == ["git", "rev-list", "HEAD..origin/main", "--count"]:
|
||||
return SimpleNamespace(stdout="1\n", stderr="", returncode=0)
|
||||
if cmd == ["git", "pull", "--ff-only", "origin", "main"]:
|
||||
if cmd == ["git", "merge", "--ff-only", "origin/main"]:
|
||||
events.append("pull")
|
||||
return SimpleNamespace(stdout="Updating\n", stderr="", returncode=0)
|
||||
if "pip" in cmd and "install" in cmd:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue