Conflict resolutions and semantic fixups: - utils.py / hermes_yaml.py: main widened ruamel's round-trip emitter so a long double-quoted scalar is never folded after an escaped backslash. pm-clean builds every rt emitter through hermes_yaml.roundtrip_yaml(), so the width lives there (ROUNDTRIP_YAML_WIDTH moves with it); xai_retirement imports it from hermes_yaml. - hermes_cli/banner.py: keep pm-clean's removal of the banner update check. Main's GIT_NO_LAZY_FETCH fix for it applies to its replacement, source_check: every read-only probe (source_git_env) now refuses promisor lazy fetches, and the partial-clone test targets that probe (red without the flag). - .github/workflows/tests.yml: keep setup-pm; main's uv pin bump does not apply. Main's WAL-capable SQLite gates are kept, run against $HERMES_PYTHON (the PM-pinned interpreter, SQLite 3.53.1). The e2e step takes main's --include-integration invocation. - apps/desktop: package.json has no build block here, so main's macOS locale-marker restore joins the darwin branch of the existing after-pack.mjs, and its test loads the hook from electron-builder.config.cjs and imports PlatformPackager from app-builder-lib's root (electron-builder 27 exports no ./out paths). The win32 row is dropped: this hook sanitizes and signs PE trees on win32 by design. - reconciliation.ts: main's rowId hydration (#119326) was merged into the first of pm-clean's split helpers only; the resolver is now one helper both halves use. - en.ts: both sides' keys kept. tests/tools/test_lazy_deps.py stays deleted. - Tests main added with `import yaml` use hermes_yaml, like the rest of the tree.
76 lines
3.4 KiB
Python
76 lines
3.4 KiB
Python
"""Read-only git probes never lazy-fetch from a partial clone's promisor remote.
|
|
|
|
In a blobless clone, asking about an object the clone has not fetched (the fresh upstream tip the
|
|
startup update check compares against) makes git download it — for a real install, the whole
|
|
commit/tree history — and the probe's timeout kills only its own git, orphaning the fetch.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from hermes_cli import source_check
|
|
from hermes_cli._subprocess_compat import bounded_git_probe
|
|
|
|
_ENV = {**os.environ, "GIT_CONFIG_GLOBAL": os.devnull, "GIT_CONFIG_NOSYSTEM": "1"}
|
|
|
|
|
|
def _git(*args, cwd=None, env=_ENV):
|
|
return subprocess.run(["git", "-c", "user.name=t", "-c", "user.email=t@t", *args], cwd=cwd, env=env,
|
|
check=True, capture_output=True, text=True).stdout.strip()
|
|
|
|
|
|
def _git_supports_no_lazy_fetch() -> bool:
|
|
m = re.search(r"(\d+)\.(\d+)", _git("--version"))
|
|
return bool(m) and (int(m[1]), int(m[2])) >= (2, 44)
|
|
|
|
|
|
pytestmark = pytest.mark.skipif(not _git_supports_no_lazy_fetch(), reason="GIT_NO_LAZY_FETCH needs git >= 2.44")
|
|
|
|
|
|
@pytest.fixture
|
|
def partial_clone(tmp_path: Path):
|
|
"""(clone, local_head, unfetched_upstream_tip) for a blobless clone of a local upstream."""
|
|
seed, up, clone = tmp_path / "seed", tmp_path / "up.git", tmp_path / "clone"
|
|
_git("init", "-q", "-b", "main", str(seed))
|
|
for i in range(2):
|
|
(seed / f"f{i}.txt").write_text(f"v{i}\n" * 50, encoding="utf-8")
|
|
_git("add", "-A", cwd=seed)
|
|
_git("commit", "-qm", f"c{i}", cwd=seed)
|
|
_git("clone", "-q", "--bare", str(seed), str(up))
|
|
_git("config", "uploadpack.allowFilter", "true", cwd=up)
|
|
_git("config", "uploadpack.allowAnySHA1InWant", "true", cwd=up)
|
|
_git("clone", "-q", "--filter=blob:none", "--no-checkout", up.as_uri(), str(clone))
|
|
(seed / "new.txt").write_text("upstream moved\n", encoding="utf-8")
|
|
_git("add", "-A", cwd=seed)
|
|
_git("commit", "-qm", "upstream", cwd=seed)
|
|
_git("push", "-q", str(up), "main", cwd=seed)
|
|
return clone, _git("rev-parse", "HEAD", cwd=clone), _git("rev-parse", "main", cwd=up)
|
|
|
|
|
|
def _fetched(clone: Path, sha: str) -> bool:
|
|
env = {**_ENV, "GIT_NO_LAZY_FETCH": "1"}
|
|
return subprocess.run(["git", "cat-file", "-e", sha], cwd=clone, env=env, capture_output=True).returncode == 0
|
|
|
|
|
|
def test_update_check_ancestry_probe_never_fetches_from_the_promisor(partial_clone):
|
|
clone, head, upstream_tip = partial_clone
|
|
|
|
# The exact ancestry probe check_for_updates runs before it falls back to the compare API.
|
|
assert not source_check._git_ok(["merge-base", "--is-ancestor", upstream_tip, head], cwd=clone)
|
|
assert not _fetched(clone, upstream_tip), "the update check downloaded upstream history"
|
|
# Control: an upstream tip already in local history still reads as up to date.
|
|
parent = _git("rev-parse", "HEAD~1", cwd=clone)
|
|
assert source_check._git_ok(["merge-base", "--is-ancestor", parent, head], cwd=clone)
|
|
|
|
|
|
def test_bounded_git_probe_never_fetches_from_the_promisor(partial_clone):
|
|
clone, head, upstream_tip = partial_clone
|
|
|
|
assert bounded_git_probe(["git", "-C", str(clone), "log", "-1", "--format=%H", upstream_tip], timeout=10) == ""
|
|
assert not _fetched(clone, upstream_tip), "a session-start git probe downloaded upstream history"
|
|
assert bounded_git_probe(["git", "-C", str(clone), "log", "-1", "--format=%H", head], timeout=10) == head
|