From 068aae12a12118c7f5603501c2a18384da48d89d Mon Sep 17 00:00:00 2001 From: Srikanth Patchava Date: Tue, 6 Oct 2026 16:08:01 -0700 Subject: [PATCH] fix(setup): say when a cached clone was reused and not updated DepsManager.setup() leaves an existing ~/.ebuild/repos/ alone on purpose (it must not move a pinned branch or tag), but the CLI printed the same 'Repos are ready' as for a fresh clone. A month-old eos then fails later in 'ebuild sim' with sim.yaml not found. Warn with the clone's commit and date and name 'ebuild repos update'. Clone/update logic is unchanged; --eos-path/--eboot-path links are not reported as cached. Fixes #180 Signed-off-by: Srikanth Patchava --- ebuild/cli/commands.py | 27 +++++++++ tests/unit/test_setup_stale_cache.py | 87 ++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 tests/unit/test_setup_stale_cache.py diff --git a/ebuild/cli/commands.py b/ebuild/cli/commands.py index 5f250a8..9f1d052 100644 --- a/ebuild/cli/commands.py +++ b/ebuild/cli/commands.py @@ -2349,6 +2349,27 @@ def generate_boot(log: Logger, boot_yaml: str, output_dir: str) -> None: # Dependency management commands # ═══════════════════════════════════════════════════════════════ +def _warn_cached_repo(log: Logger, name: str, repo_dir: Path) -> None: + """Say plainly that ``setup`` reused an existing clone without pulling it. + + ``DepsManager.setup`` deliberately leaves an existing clone alone (it + must not move a pinned branch or tag), so a clone made weeks ago would + otherwise be reported exactly like a fresh one (issue #180). + """ + try: + result = subprocess.run( + ["git", "-C", str(repo_dir), "log", "-1", "--format=%h %cs"], + capture_output=True, text=True, check=True, + ) + where = f"at {result.stdout.strip()}" + except (subprocess.CalledProcessError, FileNotFoundError): + where = "at an unknown commit" + log.warning( + f" {name} was already cloned ({where}); setup does not pull. " + f"Run `ebuild repos update` to get the latest." + ) + + @cli.command() @click.option("--eos-url", default=None, help="Git URL for eos repo (overrides default).") @click.option("--eboot-url", default=None, help="Git URL for eboot repo (overrides default).") @@ -2388,12 +2409,18 @@ def setup( try: log.step("Setting up eos...") + eos_cached = eos_path is None and (mgr.cache_dir / "eos").is_dir() eos_dir = mgr.setup("eos", url=eos_url, branch=eos_branch, path=eos_path) log.success(f" eos: {eos_dir}") + if eos_cached: + _warn_cached_repo(log, "eos", eos_dir) log.step("Setting up eboot...") + eboot_cached = eboot_path is None and (mgr.cache_dir / "eboot").is_dir() eboot_dir = mgr.setup("eboot", url=eboot_url, branch=eboot_branch, path=eboot_path) log.success(f" eboot: {eboot_dir}") + if eboot_cached: + _warn_cached_repo(log, "eboot", eboot_dir) log.success("Setup complete. Repos are ready.") except Exception as e: diff --git a/tests/unit/test_setup_stale_cache.py b/tests/unit/test_setup_stale_cache.py new file mode 100644 index 0000000..527b1e3 --- /dev/null +++ b/tests/unit/test_setup_stale_cache.py @@ -0,0 +1,87 @@ +"""`ebuild setup` must say when it reused an existing clone (issue #180). + +DepsManager.setup() leaves an existing clone alone on purpose, so the CLI +is the only place that can tell the user the checkout was not updated. +""" + +import subprocess +from pathlib import Path + +import pytest +from click.testing import CliRunner + +from ebuild.cli.commands import cli + + +def _git(repo: Path, *args: str) -> str: + return subprocess.run( + ["git", "-C", str(repo), *args], + capture_output=True, text=True, check=True, + ).stdout.strip() + + +def _make_clone(path: Path) -> str: + path.mkdir(parents=True) + _git(path, "init", "-q") + (path / "README").write_text("x\n", encoding="utf-8") + _git(path, "add", "README") + _git(path, "-c", "user.name=t", "-c", "user.email=t@t", "commit", "-q", "-m", "init") + return _git(path, "log", "-1", "--format=%h %cs") + + +class _FakeManager: + """Stands in for DepsManager: records calls, never touches the network.""" + + cache_root: Path = Path() + + def __init__(self) -> None: + self.cache_dir = self.cache_root + + def setup(self, name, url=None, branch=None, path=None): + if path: + return Path(path) + dest = self.cache_dir / name + dest.mkdir(parents=True, exist_ok=True) # a "fresh clone" + return dest + + +@pytest.fixture +def fake_manager(tmp_path, monkeypatch): + import ebuild.deps.manager as manager + + _FakeManager.cache_root = tmp_path / "repos" + monkeypatch.setattr(manager, "DepsManager", _FakeManager) + return _FakeManager.cache_root + + +def test_fresh_clone_has_no_stale_warning(fake_manager): + result = CliRunner().invoke(cli, ["setup"]) + assert result.exit_code == 0, result.output + assert "already cloned" not in result.output + assert "Setup complete" in result.output + + +def test_existing_clone_names_commit_and_update_command(fake_manager): + eos_head = _make_clone(fake_manager / "eos") + result = CliRunner().invoke(cli, ["setup"]) + assert result.exit_code == 0, result.output + assert f"eos was already cloned (at {eos_head}); setup does not pull." in result.output + assert "ebuild repos update" in result.output + # eboot was freshly "cloned" in this run, so it gets no warning. + assert "eboot was already cloned" not in result.output + + +def test_existing_non_git_dir_still_warns(fake_manager): + (fake_manager / "eboot").mkdir(parents=True) + result = CliRunner().invoke(cli, ["setup"]) + assert result.exit_code == 0, result.output + assert "eboot was already cloned (at an unknown commit)" in result.output + + +def test_linked_path_is_not_reported_as_cached(fake_manager, tmp_path): + _make_clone(fake_manager / "eos") + local = tmp_path / "my-eos" + local.mkdir() + result = CliRunner().invoke(cli, ["setup", "--eos-path", str(local)]) + assert result.exit_code == 0, result.output + assert "eos was already cloned" not in result.output