"""Tests for tools/checkpoint_manager.py — CheckpointManager.""" import logging import subprocess import pytest from pathlib import Path from unittest.mock import patch from tools.checkpoint_manager import ( CheckpointManager, _shadow_repo_path, _init_shadow_repo, _run_git, _git_env, _dir_file_count, format_checkpoint_list, DEFAULT_EXCLUDES, CHECKPOINT_BASE, ) # ========================================================================= # Fixtures # ========================================================================= @pytest.fixture() def work_dir(tmp_path): """Temporary working directory.""" d = tmp_path / "project" d.mkdir() (d / "main.py").write_text("print('hello')\\n") (d / "README.md").write_text("# Project\\n") return d @pytest.fixture() def checkpoint_base(tmp_path): """Isolated checkpoint base — never writes to ~/.hermes/.""" return tmp_path / "checkpoints" @pytest.fixture() def fake_home(tmp_path, monkeypatch): """Set a deterministic fake home for expanduser/path-home behavior.""" home = tmp_path / "home" home.mkdir() monkeypatch.setenv("HOME", str(home)) monkeypatch.setenv("USERPROFILE", str(home)) monkeypatch.delenv("HOMEDRIVE", raising=False) monkeypatch.delenv("HOMEPATH", raising=False) monkeypatch.setattr(Path, "home", classmethod(lambda cls: home)) return home @pytest.fixture() def mgr(work_dir, checkpoint_base, monkeypatch): """CheckpointManager with redirected checkpoint base.""" monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) return CheckpointManager(enabled=True, max_snapshots=50) @pytest.fixture() def disabled_mgr(checkpoint_base, monkeypatch): """Disabled CheckpointManager.""" monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) return CheckpointManager(enabled=False) # ========================================================================= # Shadow repo path # ========================================================================= class TestShadowRepoPath: def test_deterministic(self, work_dir, checkpoint_base, monkeypatch): monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) p1 = _shadow_repo_path(str(work_dir)) p2 = _shadow_repo_path(str(work_dir)) assert p1 == p2 def test_different_dirs_different_paths(self, tmp_path, checkpoint_base, monkeypatch): monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) p1 = _shadow_repo_path(str(tmp_path / "a")) p2 = _shadow_repo_path(str(tmp_path / "b")) assert p1 != p2 def test_under_checkpoint_base(self, work_dir, checkpoint_base, monkeypatch): monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) p = _shadow_repo_path(str(work_dir)) assert str(p).startswith(str(checkpoint_base)) def test_tilde_and_expanded_home_share_shadow_repo(self, fake_home, checkpoint_base, monkeypatch): monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) project = fake_home / "project" project.mkdir() tilde_path = f"~/{project.name}" expanded_path = str(project) assert _shadow_repo_path(tilde_path) == _shadow_repo_path(expanded_path) # ========================================================================= # Shadow repo init # ========================================================================= class TestShadowRepoInit: def test_creates_git_repo(self, work_dir, checkpoint_base, monkeypatch): monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) shadow = _shadow_repo_path(str(work_dir)) err = _init_shadow_repo(shadow, str(work_dir)) assert err is None assert (shadow / "HEAD").exists() def test_no_git_in_project_dir(self, work_dir, checkpoint_base, monkeypatch): monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) shadow = _shadow_repo_path(str(work_dir)) _init_shadow_repo(shadow, str(work_dir)) assert not (work_dir / ".git").exists() def test_has_exclude_file(self, work_dir, checkpoint_base, monkeypatch): monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) shadow = _shadow_repo_path(str(work_dir)) _init_shadow_repo(shadow, str(work_dir)) exclude = shadow / "info" / "exclude" assert exclude.exists() content = exclude.read_text() assert "node_modules/" in content assert ".env" in content def test_has_workdir_file(self, work_dir, checkpoint_base, monkeypatch): monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) shadow = _shadow_repo_path(str(work_dir)) _init_shadow_repo(shadow, str(work_dir)) workdir_file = shadow / "HERMES_WORKDIR" assert workdir_file.exists() assert str(work_dir.resolve()) in workdir_file.read_text() def test_idempotent(self, work_dir, checkpoint_base, monkeypatch): monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) shadow = _shadow_repo_path(str(work_dir)) err1 = _init_shadow_repo(shadow, str(work_dir)) err2 = _init_shadow_repo(shadow, str(work_dir)) assert err1 is None assert err2 is None # ========================================================================= # CheckpointManager — disabled # ========================================================================= class TestDisabledManager: def test_ensure_checkpoint_returns_false(self, disabled_mgr, work_dir): assert disabled_mgr.ensure_checkpoint(str(work_dir)) is False def test_new_turn_works(self, disabled_mgr): disabled_mgr.new_turn() # should not raise # ========================================================================= # CheckpointManager — taking checkpoints # ========================================================================= class TestTakeCheckpoint: def test_first_checkpoint(self, mgr, work_dir): result = mgr.ensure_checkpoint(str(work_dir), "initial") assert result is True def test_successful_checkpoint_does_not_log_expected_diff_exit(self, mgr, work_dir, caplog): with caplog.at_level(logging.ERROR, logger="tools.checkpoint_manager"): result = mgr.ensure_checkpoint(str(work_dir), "initial") assert result is True assert not any("diff --cached --quiet" in r.getMessage() for r in caplog.records) def test_dedup_same_turn(self, mgr, work_dir): r1 = mgr.ensure_checkpoint(str(work_dir), "first") r2 = mgr.ensure_checkpoint(str(work_dir), "second") assert r1 is True assert r2 is False # dedup'd def test_new_turn_resets_dedup(self, mgr, work_dir): r1 = mgr.ensure_checkpoint(str(work_dir), "turn 1") assert r1 is True mgr.new_turn() # Modify a file so there's something to commit (work_dir / "main.py").write_text("print('modified')\\n") r2 = mgr.ensure_checkpoint(str(work_dir), "turn 2") assert r2 is True def test_no_changes_skips_commit(self, mgr, work_dir): # First checkpoint mgr.ensure_checkpoint(str(work_dir), "initial") mgr.new_turn() # No file changes — should return False (nothing to commit) r = mgr.ensure_checkpoint(str(work_dir), "no changes") assert r is False def test_skip_root_dir(self, mgr): r = mgr.ensure_checkpoint("/", "root") assert r is False def test_skip_home_dir(self, mgr): r = mgr.ensure_checkpoint(str(Path.home()), "home") assert r is False # ========================================================================= # CheckpointManager — listing checkpoints # ========================================================================= class TestListCheckpoints: def test_empty_when_no_checkpoints(self, mgr, work_dir): result = mgr.list_checkpoints(str(work_dir)) assert result == [] def test_list_after_take(self, mgr, work_dir): mgr.ensure_checkpoint(str(work_dir), "test checkpoint") result = mgr.list_checkpoints(str(work_dir)) assert len(result) == 1 assert result[0]["reason"] == "test checkpoint" assert "hash" in result[0] assert "short_hash" in result[0] assert "timestamp" in result[0] def test_multiple_checkpoints_ordered(self, mgr, work_dir): mgr.ensure_checkpoint(str(work_dir), "first") mgr.new_turn() (work_dir / "main.py").write_text("v2\\n") mgr.ensure_checkpoint(str(work_dir), "second") mgr.new_turn() (work_dir / "main.py").write_text("v3\\n") mgr.ensure_checkpoint(str(work_dir), "third") result = mgr.list_checkpoints(str(work_dir)) assert len(result) == 3 # Most recent first assert result[0]["reason"] == "third" assert result[2]["reason"] == "first" def test_tilde_path_lists_same_checkpoints_as_expanded_path(self, checkpoint_base, fake_home, monkeypatch): monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) mgr = CheckpointManager(enabled=True, max_snapshots=50) project = fake_home / "project" project.mkdir() (project / "main.py").write_text("v1\n") tilde_path = f"~/{project.name}" assert mgr.ensure_checkpoint(tilde_path, "initial") is True listed = mgr.list_checkpoints(str(project)) assert len(listed) == 1 assert listed[0]["reason"] == "initial" # ========================================================================= # CheckpointManager — restoring # ========================================================================= class TestRestore: def test_restore_to_previous(self, mgr, work_dir): # Write original content (work_dir / "main.py").write_text("original\\n") mgr.ensure_checkpoint(str(work_dir), "original state") mgr.new_turn() # Modify the file (work_dir / "main.py").write_text("modified\\n") # Get the checkpoint hash checkpoints = mgr.list_checkpoints(str(work_dir)) assert len(checkpoints) == 1 # Restore result = mgr.restore(str(work_dir), checkpoints[0]["hash"]) assert result["success"] is True # File should be back to original assert (work_dir / "main.py").read_text() == "original\\n" def test_restore_invalid_hash(self, mgr, work_dir): mgr.ensure_checkpoint(str(work_dir), "initial") result = mgr.restore(str(work_dir), "deadbeef1234") assert result["success"] is False def test_restore_no_checkpoints(self, mgr, work_dir): result = mgr.restore(str(work_dir), "abc123") assert result["success"] is False def test_restore_creates_pre_rollback_snapshot(self, mgr, work_dir): (work_dir / "main.py").write_text("v1\\n") mgr.ensure_checkpoint(str(work_dir), "v1") mgr.new_turn() (work_dir / "main.py").write_text("v2\\n") checkpoints = mgr.list_checkpoints(str(work_dir)) mgr.restore(str(work_dir), checkpoints[0]["hash"]) # Should now have 2 checkpoints: original + pre-rollback all_cps = mgr.list_checkpoints(str(work_dir)) assert len(all_cps) >= 2 assert "pre-rollback" in all_cps[0]["reason"] def test_tilde_path_supports_diff_and_restore_flow(self, checkpoint_base, fake_home, monkeypatch): monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) mgr = CheckpointManager(enabled=True, max_snapshots=50) project = fake_home / "project" project.mkdir() file_path = project / "main.py" file_path.write_text("original\n") tilde_path = f"~/{project.name}" assert mgr.ensure_checkpoint(tilde_path, "initial") is True mgr.new_turn() file_path.write_text("changed\n") checkpoints = mgr.list_checkpoints(str(project)) diff_result = mgr.diff(tilde_path, checkpoints[0]["hash"]) assert diff_result["success"] is True assert "main.py" in diff_result["diff"] restore_result = mgr.restore(tilde_path, checkpoints[0]["hash"]) assert restore_result["success"] is True assert file_path.read_text() == "original\n" # ========================================================================= # CheckpointManager — working dir resolution # ========================================================================= class TestWorkingDirResolution: def test_resolves_git_project_root(self, tmp_path): mgr = CheckpointManager(enabled=True) project = tmp_path / "myproject" project.mkdir() (project / ".git").mkdir() subdir = project / "src" subdir.mkdir() filepath = subdir / "main.py" filepath.write_text("x\\n") result = mgr.get_working_dir_for_path(str(filepath)) assert result == str(project) def test_resolves_pyproject_root(self, tmp_path): mgr = CheckpointManager(enabled=True) project = tmp_path / "pyproj" project.mkdir() (project / "pyproject.toml").write_text("[project]\\n") subdir = project / "src" subdir.mkdir() result = mgr.get_working_dir_for_path(str(subdir / "file.py")) assert result == str(project) def test_falls_back_to_parent(self, tmp_path, monkeypatch): mgr = CheckpointManager(enabled=True) filepath = tmp_path / "random" / "file.py" filepath.parent.mkdir(parents=True) filepath.write_text("x\\n") # The walk-up scan for project markers (.git, pyproject.toml, etc.) # stops at tmp_path — otherwise stray markers in ``/tmp`` (e.g. # ``/tmp/pyproject.toml`` left by other tools on the host) get # picked up as the project root and this test flakes on shared CI. import pathlib as _pl _real_exists = _pl.Path.exists def _guarded_exists(self): s = str(self) stop = str(tmp_path) if not s.startswith(stop) and any( s.endswith("/" + m) or s == "/" + m for m in (".git", "pyproject.toml", "package.json", "Cargo.toml", "go.mod", "Makefile", "pom.xml", ".hg", "Gemfile") ): return False return _real_exists(self) monkeypatch.setattr(_pl.Path, "exists", _guarded_exists) result = mgr.get_working_dir_for_path(str(filepath)) assert result == str(filepath.parent) def test_resolves_tilde_path_to_project_root(self, fake_home): mgr = CheckpointManager(enabled=True) project = fake_home / "myproject" project.mkdir() (project / "pyproject.toml").write_text("[project]\n") subdir = project / "src" subdir.mkdir() filepath = subdir / "main.py" filepath.write_text("x\n") result = mgr.get_working_dir_for_path(f"~/{project.name}/src/main.py") assert result == str(project) # ========================================================================= # Git env isolation # ========================================================================= class TestGitEnvIsolation: def test_sets_git_dir(self, tmp_path): shadow = tmp_path / "shadow" env = _git_env(shadow, str(tmp_path / "work")) assert env["GIT_DIR"] == str(shadow) def test_sets_work_tree(self, tmp_path): shadow = tmp_path / "shadow" work = tmp_path / "work" env = _git_env(shadow, str(work)) assert env["GIT_WORK_TREE"] == str(work.resolve()) def test_clears_index_file(self, tmp_path, monkeypatch): monkeypatch.setenv("GIT_INDEX_FILE", "/some/index") shadow = tmp_path / "shadow" env = _git_env(shadow, str(tmp_path)) assert "GIT_INDEX_FILE" not in env def test_expands_tilde_in_work_tree(self, fake_home, tmp_path): shadow = tmp_path / "shadow" work = fake_home / "work" work.mkdir() env = _git_env(shadow, f"~/{work.name}") assert env["GIT_WORK_TREE"] == str(work.resolve()) # ========================================================================= # format_checkpoint_list # ========================================================================= class TestFormatCheckpointList: def test_empty_list(self): result = format_checkpoint_list([], "/some/dir") assert "No checkpoints" in result def test_formats_entries(self): cps = [ {"hash": "abc123", "short_hash": "abc1", "timestamp": "2026-03-09T21:15:00-07:00", "reason": "before write_file"}, {"hash": "def456", "short_hash": "def4", "timestamp": "2026-03-09T21:10:00-07:00", "reason": "before patch"}, ] result = format_checkpoint_list(cps, "/home/user/project") assert "abc1" in result assert "def4" in result assert "before write_file" in result assert "/rollback" in result # ========================================================================= # File count guard # ========================================================================= class TestDirFileCount: def test_counts_files(self, work_dir): count = _dir_file_count(str(work_dir)) assert count >= 2 # main.py + README.md def test_nonexistent_dir(self, tmp_path): count = _dir_file_count(str(tmp_path / "nonexistent")) assert count == 0 # ========================================================================= # Error resilience # ========================================================================= class TestErrorResilience: def test_no_git_installed(self, work_dir, checkpoint_base, monkeypatch): monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) mgr = CheckpointManager(enabled=True) # Mock git not found monkeypatch.setattr("shutil.which", lambda x: None) mgr._git_available = None # reset lazy probe result = mgr.ensure_checkpoint(str(work_dir), "test") assert result is False def test_run_git_allows_expected_nonzero_without_error_log(self, tmp_path, caplog): work = tmp_path / "work" work.mkdir() completed = subprocess.CompletedProcess( args=["git", "diff", "--cached", "--quiet"], returncode=1, stdout="", stderr="", ) with patch("tools.checkpoint_manager.subprocess.run", return_value=completed): with caplog.at_level(logging.ERROR, logger="tools.checkpoint_manager"): ok, stdout, stderr = _run_git( ["diff", "--cached", "--quiet"], tmp_path / "shadow", str(work), allowed_returncodes={1}, ) assert ok is False assert stdout == "" assert stderr == "" assert not caplog.records def test_run_git_invalid_working_dir_reports_path_error(self, tmp_path, caplog): missing = tmp_path / "missing" with caplog.at_level(logging.ERROR, logger="tools.checkpoint_manager"): ok, stdout, stderr = _run_git( ["status"], tmp_path / "shadow", str(missing), ) assert ok is False assert stdout == "" assert "working directory not found" in stderr assert not any("Git executable not found" in r.getMessage() for r in caplog.records) def test_run_git_missing_git_reports_git_not_found(self, tmp_path, monkeypatch, caplog): work = tmp_path / "work" work.mkdir() def raise_missing_git(*args, **kwargs): raise FileNotFoundError(2, "No such file or directory", "git") monkeypatch.setattr("tools.checkpoint_manager.subprocess.run", raise_missing_git) with caplog.at_level(logging.ERROR, logger="tools.checkpoint_manager"): ok, stdout, stderr = _run_git( ["status"], tmp_path / "shadow", str(work), ) assert ok is False assert stdout == "" assert stderr == "git not found" assert any("Git executable not found" in r.getMessage() for r in caplog.records) def test_checkpoint_failure_does_not_raise(self, mgr, work_dir, monkeypatch): """Checkpoint failures should never raise — they're silently logged.""" def broken_run_git(*args, **kwargs): raise OSError("git exploded") monkeypatch.setattr("tools.checkpoint_manager._run_git", broken_run_git) # Should not raise result = mgr.ensure_checkpoint(str(work_dir), "test") assert result is False # ========================================================================= # Security / Input validation # ========================================================================= class TestSecurity: def test_restore_rejects_argument_injection(self, mgr, work_dir): mgr.ensure_checkpoint(str(work_dir), "initial") # Try to pass a git flag as a commit hash result = mgr.restore(str(work_dir), "--patch") assert result["success"] is False assert "Invalid commit hash" in result["error"] assert "must not start with '-'" in result["error"] result = mgr.restore(str(work_dir), "-p") assert result["success"] is False assert "Invalid commit hash" in result["error"] def test_restore_rejects_invalid_hex_chars(self, mgr, work_dir): mgr.ensure_checkpoint(str(work_dir), "initial") # Git hashes should not contain characters like ;, &, | result = mgr.restore(str(work_dir), "abc; rm -rf /") assert result["success"] is False assert "expected 4-64 hex characters" in result["error"] result = mgr.diff(str(work_dir), "abc&def") assert result["success"] is False assert "expected 4-64 hex characters" in result["error"] def test_restore_rejects_path_traversal(self, mgr, work_dir): mgr.ensure_checkpoint(str(work_dir), "initial") # Real commit hash but malicious path checkpoints = mgr.list_checkpoints(str(work_dir)) target_hash = checkpoints[0]["hash"] # Absolute path outside result = mgr.restore(str(work_dir), target_hash, file_path="/etc/passwd") assert result["success"] is False assert "got absolute path" in result["error"] # Relative traversal outside path result = mgr.restore(str(work_dir), target_hash, file_path="../outside_file.txt") assert result["success"] is False assert "escapes the working directory" in result["error"] def test_restore_accepts_valid_file_path(self, mgr, work_dir): mgr.ensure_checkpoint(str(work_dir), "initial") checkpoints = mgr.list_checkpoints(str(work_dir)) target_hash = checkpoints[0]["hash"] # Valid path inside directory result = mgr.restore(str(work_dir), target_hash, file_path="main.py") assert result["success"] is True # Another valid path with subdirectories (work_dir / "subdir").mkdir() (work_dir / "subdir" / "test.txt").write_text("hello") mgr.new_turn() mgr.ensure_checkpoint(str(work_dir), "second") checkpoints = mgr.list_checkpoints(str(work_dir)) target_hash = checkpoints[0]["hash"] result = mgr.restore(str(work_dir), target_hash, file_path="subdir/test.txt") assert result["success"] is True # ========================================================================= # GPG / global git config isolation # ========================================================================= # Regression tests for the bug where users with ``commit.gpgsign = true`` # in their global git config got a pinentry popup (or a failed commit) # every time the agent took a background snapshot. import os as _os class TestGpgAndGlobalConfigIsolation: def test_git_env_isolates_global_and_system_config(self, tmp_path): """_git_env must null out GIT_CONFIG_GLOBAL / GIT_CONFIG_SYSTEM so the shadow repo does not inherit user-level gpgsign, hooks, aliases, etc.""" env = _git_env(tmp_path / "shadow", str(tmp_path)) assert env["GIT_CONFIG_GLOBAL"] == _os.devnull assert env["GIT_CONFIG_SYSTEM"] == _os.devnull assert env["GIT_CONFIG_NOSYSTEM"] == "1" def test_init_sets_commit_gpgsign_false(self, work_dir, checkpoint_base, monkeypatch): monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) shadow = _shadow_repo_path(str(work_dir)) _init_shadow_repo(shadow, str(work_dir)) # Inspect the shadow's own config directly — the settings must be # written into the repo, not just inherited via env vars. result = subprocess.run( ["git", "config", "--file", str(shadow / "config"), "--get", "commit.gpgsign"], capture_output=True, text=True, ) assert result.stdout.strip() == "false" def test_init_sets_tag_gpgsign_false(self, work_dir, checkpoint_base, monkeypatch): monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) shadow = _shadow_repo_path(str(work_dir)) _init_shadow_repo(shadow, str(work_dir)) result = subprocess.run( ["git", "config", "--file", str(shadow / "config"), "--get", "tag.gpgSign"], capture_output=True, text=True, ) assert result.stdout.strip() == "false" def test_checkpoint_works_with_global_gpgsign_and_broken_gpg( self, work_dir, checkpoint_base, monkeypatch, tmp_path ): """The real bug scenario: user has global commit.gpgsign=true but GPG is broken or pinentry is unavailable. Before the fix, every snapshot either failed or spawned a pinentry window. After the fix, snapshots succeed without ever invoking GPG.""" monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) # Fake HOME with global gpgsign=true and a deliberately broken GPG # binary. If isolation fails, the commit will try to exec this # nonexistent path and the checkpoint will fail. fake_home = tmp_path / "fake_home" fake_home.mkdir() (fake_home / ".gitconfig").write_text( "[user]\n email = real@user.com\n name = Real User\n" "[commit]\n gpgsign = true\n" "[tag]\n gpgSign = true\n" "[gpg]\n program = /nonexistent/fake-gpg-binary\n" ) monkeypatch.setenv("HOME", str(fake_home)) monkeypatch.delenv("GPG_TTY", raising=False) monkeypatch.delenv("DISPLAY", raising=False) # block GUI pinentry mgr = CheckpointManager(enabled=True) assert mgr.ensure_checkpoint(str(work_dir), reason="with-global-gpgsign") is True assert len(mgr.list_checkpoints(str(work_dir))) == 1 def test_checkpoint_works_on_prefix_shadow_without_local_gpgsign( self, work_dir, checkpoint_base, monkeypatch, tmp_path ): """Users with shadow repos created before the fix will not have commit.gpgsign=false in their shadow's own config. The inline ``--no-gpg-sign`` flag on the commit call must cover them.""" monkeypatch.setattr("tools.checkpoint_manager.CHECKPOINT_BASE", checkpoint_base) # Simulate a pre-fix shadow repo: init without commit.gpgsign=false # in its own config. _init_shadow_repo now writes it, so we must # manually remove it to mimic the pre-fix state. shadow = _shadow_repo_path(str(work_dir)) _init_shadow_repo(shadow, str(work_dir)) subprocess.run( ["git", "config", "--file", str(shadow / "config"), "--unset", "commit.gpgsign"], capture_output=True, text=True, check=False, ) subprocess.run( ["git", "config", "--file", str(shadow / "config"), "--unset", "tag.gpgSign"], capture_output=True, text=True, check=False, ) # And simulate hostile global config fake_home = tmp_path / "fake_home" fake_home.mkdir() (fake_home / ".gitconfig").write_text( "[commit]\n gpgsign = true\n" "[gpg]\n program = /nonexistent/fake-gpg-binary\n" ) monkeypatch.setenv("HOME", str(fake_home)) monkeypatch.delenv("GPG_TTY", raising=False) monkeypatch.delenv("DISPLAY", raising=False) mgr = CheckpointManager(enabled=True) assert mgr.ensure_checkpoint(str(work_dir), reason="prefix-shadow") is True assert len(mgr.list_checkpoints(str(work_dir))) == 1