"""muse#54 item 3 -- `muse reflog compact [--branch ] [--dry-run] [--json]`. Merges consecutive same-category reflog entries into a single summary entry, similar in spirit to `muse rebase --squash` at the commit level. """ from __future__ import annotations import json import pathlib from tests.cli_test_helper import CliRunner from muse.core.types import fake_id, Manifest, NULL_COMMIT_ID from muse.core.paths import muse_dir, logs_dir from muse.core.reflog import read_reflog, compact_reflog, categorize_operation cli = None runner = CliRunner() def _env(root: pathlib.Path) -> Manifest: return {"MUSE_REPO_ROOT": str(root)} def _run(root: pathlib.Path, *args: str): return runner.invoke(cli, list(args), env=_env(root)) def _init_repo(tmp_path: pathlib.Path) -> tuple[pathlib.Path, str]: dot_muse = muse_dir(tmp_path) dot_muse.mkdir() repo_id = fake_id("repo") (dot_muse / "repo.json").write_text(json.dumps({ "repo_id": repo_id, "domain": "code", "default_branch": "main", "created_at": "2025-01-01T00:00:00+00:00", }), encoding="utf-8") (dot_muse / "HEAD").write_text("ref: refs/heads/main\n", encoding="utf-8") (dot_muse / "refs" / "heads").mkdir(parents=True) (dot_muse / "snapshots").mkdir() (dot_muse / "commits").mkdir() (dot_muse / "objects").mkdir() return tmp_path, repo_id def _commit_file(root: pathlib.Path, content: str, message: str) -> str: (root / "f.txt").write_text(content) add = _run(root, "code", "add", "f.txt") assert add.exit_code == 0, add.output commit = _run(root, "commit", "-m", message, "--json") assert commit.exit_code == 0, commit.output return json.loads(commit.output)["commit_id"] def _write_raw_entry(path: pathlib.Path, old_id: str, new_id: str, ts: int, author: str, operation: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) with path.open("a", encoding="utf-8") as f: f.write(f"{old_id} {new_id} {author} {ts} +0000\t{operation}\n") def _fake_commit_id(n: int) -> str: return f"{n:064x}" class TestCategorizeOperation: def test_simple_prefix(self) -> None: assert categorize_operation("commit: add verse") == "commit" def test_prefix_with_parenthetical(self) -> None: assert categorize_operation("reset (hard): moving to sha256:abc") == "reset" class TestCompactReflogCore: def test_merges_consecutive_same_category_run(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) head_log = logs_dir(root) / "HEAD" c0, c1, c2, c3 = (_fake_commit_id(n) for n in range(4)) _write_raw_entry(head_log, NULL_COMMIT_ID, c1, 1000, "gabriel", "commit: c1") _write_raw_entry(head_log, c1, c2, 1001, "gabriel", "commit: c2") _write_raw_entry(head_log, c2, c3, 1002, "gabriel", "commit: c3") removed, remaining = compact_reflog(root, branch=None) assert removed == 2 assert remaining == 1 entries = read_reflog(root, branch=None, limit=100) assert len(entries) == 1 assert entries[0].old_id == NULL_COMMIT_ID assert entries[0].new_id == c3 assert "commit (compacted 3 entries)" in entries[0].operation def test_different_categories_not_merged(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) head_log = logs_dir(root) / "HEAD" c1, c2, c3 = (_fake_commit_id(n) for n in range(1, 4)) _write_raw_entry(head_log, NULL_COMMIT_ID, c1, 1000, "gabriel", "commit: c1") _write_raw_entry(head_log, c1, c2, 1001, "gabriel", "reset (hard): moving to sha256:" + c2) _write_raw_entry(head_log, c2, c3, 1002, "gabriel", "commit: c3") removed, remaining = compact_reflog(root, branch=None) assert removed == 0 assert remaining == 3 def test_lone_entry_never_compacted(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) head_log = logs_dir(root) / "HEAD" c1 = _fake_commit_id(1) _write_raw_entry(head_log, NULL_COMMIT_ID, c1, 1000, "gabriel", "commit: c1") removed, remaining = compact_reflog(root, branch=None) assert removed == 0 assert remaining == 1 def test_different_authors_become_various(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) head_log = logs_dir(root) / "HEAD" c1, c2 = (_fake_commit_id(n) for n in range(1, 3)) _write_raw_entry(head_log, NULL_COMMIT_ID, c1, 1000, "alice", "commit: c1") _write_raw_entry(head_log, c1, c2, 1001, "bob", "commit: c2") compact_reflog(root, branch=None) entries = read_reflog(root, branch=None, limit=100) assert entries[0].author == "various" def test_dry_run_does_not_write(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) head_log = logs_dir(root) / "HEAD" c1, c2 = (_fake_commit_id(n) for n in range(1, 3)) _write_raw_entry(head_log, NULL_COMMIT_ID, c1, 1000, "gabriel", "commit: c1") _write_raw_entry(head_log, c1, c2, 1001, "gabriel", "commit: c2") removed, remaining = compact_reflog(root, branch=None, dry_run=True) assert removed == 1 assert remaining == 1 entries = read_reflog(root, branch=None, limit=100) assert len(entries) == 2 # unchanged on disk def test_second_run_also_merged(self, tmp_path: pathlib.Path) -> None: """Two separate qualifying runs, split by a differing category, both compact.""" root, repo_id = _init_repo(tmp_path) head_log = logs_dir(root) / "HEAD" c1, c2, c3, c4, c5 = (_fake_commit_id(n) for n in range(1, 6)) _write_raw_entry(head_log, NULL_COMMIT_ID, c1, 1000, "gabriel", "commit: c1") _write_raw_entry(head_log, c1, c2, 1001, "gabriel", "commit: c2") _write_raw_entry(head_log, c2, c3, 1002, "gabriel", "reset (hard): to c3") _write_raw_entry(head_log, c3, c4, 1003, "gabriel", "commit: c4") _write_raw_entry(head_log, c4, c5, 1004, "gabriel", "commit: c5") removed, remaining = compact_reflog(root, branch=None) assert removed == 2 assert remaining == 3 entries = read_reflog(root, branch=None, limit=100) # newest-first: [commit(c4->c5 compacted), reset, commit(c1->c2 compacted)] assert entries[0].new_id == c5 assert entries[1].new_id == c3 assert entries[2].new_id == c2 class TestReflogCompactCli: def test_cli_compact_json(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) _commit_file(root, "v1\n", "c1") _commit_file(root, "v2\n", "c2") _commit_file(root, "v3\n", "c3") result = _run(root, "reflog", "compact", "--json") assert result.exit_code == 0, result.output data = json.loads(result.output) assert data["compacted"] == 2 assert data["remaining"] == 1 assert data["dry_run"] is False assert data["ref"] == "HEAD" def test_cli_compact_dry_run_json(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) _commit_file(root, "v1\n", "c1") _commit_file(root, "v2\n", "c2") result = _run(root, "reflog", "compact", "--dry-run", "--json") assert result.exit_code == 0, result.output data = json.loads(result.output) assert data["compacted"] == 1 assert data["dry_run"] is True # Unchanged after dry-run. follow_up = _run(root, "reflog", "--json") assert json.loads(follow_up.output)["total"] == 2 def test_cli_invalid_branch_errors(self, tmp_path: pathlib.Path) -> None: root, repo_id = _init_repo(tmp_path) result = _run(root, "reflog", "compact", "--branch", "../escape", "--json") assert result.exit_code == 1