"""GitShardAdapter history adopt + cross-substrate integration (SHARD-WP-0012 T3).""" import os import subprocess import pytest from shard_wiki.adapters import FolderAdapter, GitShardAdapter from shard_wiki.coordination import ApplyStatus from shard_wiki.space import InformationSpace _ENV = { "GIT_AUTHOR_NAME": "t", "GIT_AUTHOR_EMAIL": "t@t", "GIT_COMMITTER_NAME": "t", "GIT_COMMITTER_EMAIL": "t@t", "PATH": os.environ.get("PATH", ""), } def _git(repo, *args): return subprocess.run( ["git", "-C", str(repo), *args], check=True, capture_output=True, text=True, env=_ENV ).stdout.strip() def _git_repo(tmp_path, files, name="git"): repo = tmp_path / name repo.mkdir() _git(repo, "init", "--quiet") for rel, text in files.items(): (repo / rel).parent.mkdir(parents=True, exist_ok=True) (repo / rel).write_text(text, encoding="utf-8") _git(repo, "add", rel) _git(repo, "commit", "-m", "seed") return repo def _folder(tmp_path, name, files, writable=False): root = tmp_path / name for rel, text in files.items(): p = root / rel p.parent.mkdir(parents=True, exist_ok=True) p.write_text(text, encoding="utf-8") return FolderAdapter(name, root, writable=writable) # -- history adopt ------------------------------------------------------------- def test_history_lists_commits_newest_first(tmp_path): repo = _git_repo(tmp_path, {"Home.md": "v1"}) adapter = GitShardAdapter("git", repo, writable=True) adapter.write("Home", "v2") history = adapter.history("Home") assert len(history) == 2 assert history[0].message == "write Home.md" # newest first assert history[-1].message == "seed" assert all(rev.sha for rev in history) def test_history_unknown_key_raises(tmp_path): adapter = GitShardAdapter("git", _git_repo(tmp_path, {"Home.md": "h"})) with pytest.raises(KeyError): adapter.history("Nope") # -- cross-substrate integration ---------------------------------------------- def test_resolve_across_git_and_folder(tmp_path): space = InformationSpace("space") space.attach(GitShardAdapter("git", _git_repo(tmp_path, {"Home.md": "git home"}))) space.attach(_folder(tmp_path, "notes", {"Daily.md": "folder daily"})) assert space.read("Home").body == "git home" # resolved from the git shard assert space.read("Daily").body == "folder daily" # resolved from the folder shard def test_chorus_spans_substrates_with_divergence(tmp_path): space = InformationSpace("space") space.attach(GitShardAdapter("git", _git_repo(tmp_path, {"Shared.md": "from git"}))) space.attach(_folder(tmp_path, "notes", {"Shared.md": "from folder"})) res = space.resolve("Shared") assert {p.body for p in res.pages} == {"from git", "from folder"} # chorus across substrates git_page = next(p for p in res.pages if p.identity.shard == "git") assert git_page.envelope.divergence # divergence recorded, not erased def test_edit_through_git_shard_commits(tmp_path): repo = _git_repo(tmp_path, {"Home.md": "original"}) space = InformationSpace("space") space.attach(GitShardAdapter("git", repo, writable=True)) result = space.edit("Home", "edited via overlay") assert result.status is ApplyStatus.APPLIED # write-through fast-forward on a git shard assert space.read("Home").body == "edited via overlay" assert int(_git(repo, "rev-list", "--count", "HEAD")) == 2 # the edit became a commit def test_apply_under_drift_refuses_on_external_commit(tmp_path): repo = _git_repo(tmp_path, {"Home.md": "original"}) space = InformationSpace("space") space.attach(GitShardAdapter("git", repo, writable=True)) overlay = space.overlay("Home", "my draft") # base_rev = current git sha # Another writer commits to the same path → the sha moves underneath the draft. (repo / "Home.md").write_text("someone else", encoding="utf-8") _git(repo, "add", "Home.md") _git(repo, "commit", "-m", "external") result = space.apply_overlay(overlay.overlay_id) assert result.status is ApplyStatus.REFUSED_DRIFT # never clobber (sha drift detected) # The shard itself is untouched — the external commit stands; the draft remains a draft. assert space.union.shard("git").read("Home").body == "someone else" def test_overlay_on_read_only_git_shard_kept_as_draft(tmp_path): space = InformationSpace("space") space.attach(GitShardAdapter("git", _git_repo(tmp_path, {"Home.md": "ro"}), writable=False)) result = space.edit("Home", "wanted change") assert result.status is ApplyStatus.KEPT_DRAFT # read-only target → overlay retained