"""Pushed-branch tier of the worktree pruners (#A, Aug 2026). Behavior contract: a clean worktree whose branch head EXACTLY matches what origin holds (open-PR lane on a single-branch-refspec install) gets its TREE reaped while its BRANCH ref survives — including surviving the orphaned-branch pass. Anything not provably on origin stays preserved. Uses a real local bare repo as `origin` so `git ls-remote`/push are genuine, no network and no mocks. """ import os import subprocess import time import pytest def _run(args, cwd): return subprocess.run( args, cwd=cwd, capture_output=True, text=True, encoding="utf-8" ) @pytest.fixture def repo_with_bare_origin(tmp_path): """A working clone whose origin is a real local bare repo. Mirrors the managed-install fetch config: single-branch refspec for main only, so pushed side branches never gain refs/remotes/origin/ entries — the exact condition that made the old pruner misread pushed PR lanes as 'unpushed'. """ bare = tmp_path / "origin.git" bare.mkdir() _run(["git", "init", "--bare", "--initial-branch=main"], bare) repo = tmp_path / "clone" repo.mkdir() _run(["git", "init", "--initial-branch=main"], repo) _run(["git", "config", "user.email", "test@test.com"], repo) _run(["git", "config", "user.name", "Test"], repo) (repo / "README.md").write_text("# repo\n") _run(["git", "add", "."], repo) _run(["git", "commit", "-m", "init"], repo) _run(["git", "remote", "add", "origin", str(bare)], repo) # Managed-install shape: fetch refspec covers main ONLY. _run( ["git", "config", "remote.origin.fetch", "+refs/heads/main:refs/remotes/origin/main"], repo, ) _run(["git", "push", "-u", "origin", "main"], repo) _run(["git", "fetch", "origin"], repo) return repo def _age(path, hours=100): t = time.time() - hours * 3600 os.utime(path, (t, t)) def _mk_worktree(repo, name, branch, commit=True, push=False, extra_commit_after_push=False): """Create .worktrees/ on with an optional pushed commit.""" (repo / ".worktrees").mkdir(exist_ok=True) p = repo / ".worktrees" / name _run(["git", "worktree", "add", str(p), "-b", branch, "HEAD"], repo) if commit: (p / f"{name}.txt").write_text("payload\n") _run(["git", "add", f"{name}.txt"], p) _run(["git", "commit", "-m", f"work on {name}"], p) if push: # Plain push — deliberately NO refs/remotes/* tracking ref appears, # because the fetch refspec only covers main. result = _run(["git", "push", "origin", branch], p) assert result.returncode == 0, result.stderr if extra_commit_after_push: (p / "later.txt").write_text("post-push work\n") _run(["git", "add", "later.txt"], p) _run(["git", "commit", "-m", "post-push commit"], p) _age(p) return p def _branch_exists(repo, branch): return _run( ["git", "rev-parse", "--verify", "--quiet", f"refs/heads/{branch}"], repo ).returncode == 0 class TestFetchRemoteBranchHeads: def test_lists_pushed_branches(self, repo_with_bare_origin): import cli repo = repo_with_bare_origin _mk_worktree(repo, "hermes-a", "hermes/hermes-a", push=True) heads = cli._fetch_remote_branch_heads(str(repo)) assert heads is not None assert "main" in heads assert "hermes/hermes-a" in heads def test_unreachable_remote_returns_none(self, tmp_path): import cli repo = tmp_path / "r" repo.mkdir() _run(["git", "init"], repo) _run(["git", "remote", "add", "origin", str(tmp_path / "missing.git")], repo) assert cli._fetch_remote_branch_heads(str(repo)) is None class TestBranchPushedExact: def test_exact_match_true(self, repo_with_bare_origin): import cli repo = repo_with_bare_origin wt = _mk_worktree(repo, "hermes-x", "hermes/hermes-x", push=True) heads = cli._fetch_remote_branch_heads(str(repo)) assert cli._worktree_branch_pushed_exact(str(wt), heads) is True def test_local_ahead_of_push_false(self, repo_with_bare_origin): import cli repo = repo_with_bare_origin wt = _mk_worktree( repo, "hermes-y", "hermes/hermes-y", push=True, extra_commit_after_push=True, ) heads = cli._fetch_remote_branch_heads(str(repo)) assert cli._worktree_branch_pushed_exact(str(wt), heads) is False def test_never_pushed_false(self, repo_with_bare_origin): import cli repo = repo_with_bare_origin wt = _mk_worktree(repo, "hermes-z", "hermes/hermes-z", push=False) heads = cli._fetch_remote_branch_heads(str(repo)) assert cli._worktree_branch_pushed_exact(str(wt), heads) is False def test_none_heads_false(self, repo_with_bare_origin): import cli repo = repo_with_bare_origin wt = _mk_worktree(repo, "hermes-n", "hermes/hermes-n", push=True) assert cli._worktree_branch_pushed_exact(str(wt), None) is False assert cli._worktree_branch_pushed_exact(str(wt), {}) is False class TestStartupPrunerPushedTier: """cli._prune_stale_worktrees against the real repo fixture.""" def test_pushed_lane_tree_reaped_branch_kept(self, repo_with_bare_origin): import cli repo = repo_with_bare_origin wt = _mk_worktree(repo, "hermes-pushed", "hermes/hermes-pushed", push=True) cli._prune_stale_worktrees(str(repo)) assert not wt.exists(), ( "pushed-as-is open-PR lane must be reaped (checkout is redundant)" ) assert _branch_exists(repo, "hermes/hermes-pushed"), ( "branch ref must survive the reap AND the orphaned-branch pass" ) def test_unpushed_lane_still_preserved(self, repo_with_bare_origin): import cli repo = repo_with_bare_origin wt = _mk_worktree(repo, "hermes-unpushed", "hermes/hermes-unp", push=False) cli._prune_stale_worktrees(str(repo)) assert wt.exists(), "never-pushed unique work must survive, any age" def test_ahead_of_pushed_lane_preserved(self, repo_with_bare_origin): import cli repo = repo_with_bare_origin wt = _mk_worktree( repo, "hermes-ahead", "hermes/hermes-ahead", push=True, extra_commit_after_push=True, ) cli._prune_stale_worktrees(str(repo)) assert wt.exists(), ( "local commits beyond the pushed head are unpushed work — preserve" ) def test_pushed_but_dirty_preserved(self, repo_with_bare_origin): import cli repo = repo_with_bare_origin wt = _mk_worktree(repo, "hermes-pdirty", "hermes/hermes-pdirty", push=True) (wt / "uncommitted.txt").write_text("in-flight\n") _age(wt) cli._prune_stale_worktrees(str(repo)) assert wt.exists(), "dirty guard outranks the pushed tier" def test_offline_remote_preserves(self, repo_with_bare_origin, tmp_path): import cli repo = repo_with_bare_origin wt = _mk_worktree(repo, "hermes-off", "hermes/hermes-off", push=True) # Sever the remote AFTER pushing: ls-remote now fails -> None -> # pushed tier must degrade to preserve. _run(["git", "remote", "set-url", "origin", str(tmp_path / "gone.git")], repo) cli._prune_stale_worktrees(str(repo)) assert wt.exists(), "unverifiable push state must fail toward preserve" class TestAttendedGcPushedTier: """worktree_gc audit/reclaim behavior for the pushed tier.""" def test_audit_verdict_and_reclaim_keeps_branch(self, repo_with_bare_origin): import cli # noqa: F401 (worktree_gc lazily imports cli) from hermes_cli import worktree_gc repo = repo_with_bare_origin wt = _mk_worktree(repo, "salv-lane", "salv/pushed-lane", push=True) records = worktree_gc.audit_worktrees(str(repo), with_sizes=False) by_name = {r.name: r for r in records} assert by_name["salv-lane"].verdict == "reap-keep-branch" assert "pushed to origin" in by_name["salv-lane"].reason actions = worktree_gc.reclaim_worktrees(str(repo), records=records) assert any("salv-lane" in a and "kept" in a for a in actions) assert not wt.exists() assert _branch_exists(repo, "salv/pushed-lane") def test_audit_never_pushed_keeps(self, repo_with_bare_origin): import cli # noqa: F401 from hermes_cli import worktree_gc repo = repo_with_bare_origin wt = _mk_worktree(repo, "salv-keep", "salv/never-pushed", push=False) records = worktree_gc.audit_worktrees(str(repo), with_sizes=False) by_name = {r.name: r for r in records} assert by_name["salv-keep"].verdict == "keep" worktree_gc.reclaim_worktrees(str(repo), records=records) assert wt.exists() class TestCronWorktreeMaintenance: def test_throttle_dispatches_once_per_interval(self, monkeypatch): import cron.scheduler as sched calls = [] monkeypatch.setattr(sched, "_last_worktree_maintenance_at", None) monkeypatch.setattr( sched.threading, "Thread", lambda *a, **k: type("T", (), {"start": lambda self: calls.append(k.get("name"))})(), ) sched._maybe_run_worktree_maintenance() sched._maybe_run_worktree_maintenance() # inside interval — no-op assert len(calls) == 1 def test_repo_discovery_requires_worktrees_dir(self, monkeypatch, tmp_path): import cron.scheduler as sched # A job workdir inside a git repo WITHOUT .worktrees/ is filtered out. repo = tmp_path / "jobrepo" repo.mkdir() _run(["git", "init"], repo) monkeypatch.setattr( "cron.jobs.load_jobs", lambda: [{"workdir": str(repo)}], ) repos = sched._worktree_maintenance_repos() assert str(repo) not in repos # Adding .worktrees/ makes it eligible. (repo / ".worktrees").mkdir() repos = sched._worktree_maintenance_repos() assert str(repo) in repos def test_maintenance_prunes_via_real_pruner(self, repo_with_bare_origin, monkeypatch): import cron.scheduler as sched repo = repo_with_bare_origin wt = _mk_worktree(repo, "hermes-cronreap", "hermes/hermes-cronreap", push=True) monkeypatch.setattr(sched, "_last_worktree_maintenance_at", None) monkeypatch.setattr( sched, "_worktree_maintenance_repos", lambda: [str(repo)] ) sched._maybe_run_worktree_maintenance() # The sweep runs on a daemon thread — wait for it (bounded). deadline = time.time() + 30 while wt.exists() and time.time() < deadline: time.sleep(0.2) assert not wt.exists(), "cron-tick maintenance must run the real pruner" assert _branch_exists(repo, "hermes/hermes-cronreap")