mirror of
https://github.com/brunopostle/ifcurl.git
synced 2026-07-12 10:18:14 +00:00
Implements ifcurl-5oo. get_checkout() ensures the bare cache is current via fetch_ifc(), then clones it locally to <cache_dir>/checkout/ (no network), and places it at the resolved commit as a detached HEAD — forcing branch creation before any commit. Viewer artefact patterns (*.ifcview, *.rdbview, etc.) are appended to .git/info/exclude so they are locally ignored without touching the repo's own .gitignore. When a newer commit is requested, the checkout fetches from origin (the local bare cache) using the default refspec to avoid git's "refusing to fetch into checked-out branch" error. Unblocks the JSON-RPC connector (ifcurl-242). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
250 lines
9.8 KiB
Python
250 lines
9.8 KiB
Python
"""Tests for ifcurl.checkout — working-tree checkout management."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from ifcurl.checkout import _ensure_checkout, _ensure_viewer_excludes, _VIEWER_EXCLUDES, get_checkout
|
|
from ifcurl.url import IfcUrl
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fixture helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _make_bare_cache(tmp_path: Path, remote_url: str, ifc_bytes: bytes = b"IFC4\n") -> dict:
|
|
"""Create a source repo + bare cache clone; return metadata dict."""
|
|
import git as gitpkg
|
|
|
|
src = tmp_path / "source"
|
|
src.mkdir()
|
|
(src / "model.ifc").write_bytes(ifc_bytes)
|
|
repo = gitpkg.Repo.init(str(src))
|
|
with repo.config_writer() as cw:
|
|
cw.set_value("user", "name", "Test")
|
|
cw.set_value("user", "email", "t@t.com")
|
|
repo.index.add(["model.ifc"])
|
|
commit = repo.index.commit("init")
|
|
|
|
url_hash = hashlib.sha256(remote_url.encode()).hexdigest()[:24]
|
|
cache_dir = tmp_path / url_hash
|
|
bare_dir = cache_dir / "repo.git"
|
|
gitpkg.Repo.clone_from(str(src), str(bare_dir), bare=True)
|
|
(cache_dir / "remote_url").write_text(remote_url)
|
|
|
|
return {
|
|
"hexsha": commit.hexsha,
|
|
"bytes": ifc_bytes,
|
|
"cache_dir": cache_dir,
|
|
"bare_dir": bare_dir,
|
|
"src": src,
|
|
"remote_url": remote_url,
|
|
}
|
|
|
|
|
|
def _ifc_url(hexsha: str, remote_url: str, path: str = "model.ifc") -> IfcUrl:
|
|
"""Build an IfcUrl for a pre-populated cache (https transport, commit ref)."""
|
|
host_path = remote_url.removeprefix("https://")
|
|
return IfcUrl.parse(f"ifc://{host_path}@{hexsha}?path={path}")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _ensure_viewer_excludes
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestEnsureViewerExcludes:
|
|
def test_creates_exclude_file(self, tmp_path):
|
|
fake_checkout = tmp_path / "checkout"
|
|
(fake_checkout / ".git" / "info").mkdir(parents=True)
|
|
_ensure_viewer_excludes(fake_checkout)
|
|
assert (fake_checkout / ".git" / "info" / "exclude").exists()
|
|
|
|
def test_patterns_present(self, tmp_path):
|
|
fake_checkout = tmp_path / "checkout"
|
|
(fake_checkout / ".git" / "info").mkdir(parents=True)
|
|
_ensure_viewer_excludes(fake_checkout)
|
|
content = (fake_checkout / ".git" / "info" / "exclude").read_text()
|
|
assert "*.ifcview" in content
|
|
assert "*.rdbview" in content
|
|
|
|
def test_idempotent(self, tmp_path):
|
|
fake_checkout = tmp_path / "checkout"
|
|
(fake_checkout / ".git" / "info").mkdir(parents=True)
|
|
_ensure_viewer_excludes(fake_checkout)
|
|
_ensure_viewer_excludes(fake_checkout)
|
|
content = (fake_checkout / ".git" / "info" / "exclude").read_text()
|
|
assert content.count("*.ifcview") == 1
|
|
|
|
def test_appends_to_existing_file(self, tmp_path):
|
|
fake_checkout = tmp_path / "checkout"
|
|
(fake_checkout / ".git" / "info").mkdir(parents=True)
|
|
exclude = fake_checkout / ".git" / "info" / "exclude"
|
|
exclude.write_text("# existing\n*.log\n")
|
|
_ensure_viewer_excludes(fake_checkout)
|
|
content = exclude.read_text()
|
|
assert "*.log" in content
|
|
assert "*.ifcview" in content
|
|
|
|
def test_does_not_duplicate_existing_pattern(self, tmp_path):
|
|
fake_checkout = tmp_path / "checkout"
|
|
(fake_checkout / ".git" / "info").mkdir(parents=True)
|
|
exclude = fake_checkout / ".git" / "info" / "exclude"
|
|
exclude.write_text("*.ifcview\n")
|
|
_ensure_viewer_excludes(fake_checkout)
|
|
assert exclude.read_text().count("*.ifcview") == 1
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _ensure_checkout
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestEnsureCheckout:
|
|
def test_creates_checkout_dir(self, tmp_path):
|
|
meta = _make_bare_cache(tmp_path, "https://example.com/org/repo")
|
|
checkout_dir = _ensure_checkout(meta["cache_dir"], meta["hexsha"])
|
|
assert checkout_dir.is_dir()
|
|
|
|
def test_checkout_is_detached_head(self, tmp_path):
|
|
import git as gitpkg
|
|
|
|
meta = _make_bare_cache(tmp_path, "https://example.com/org/repo")
|
|
checkout_dir = _ensure_checkout(meta["cache_dir"], meta["hexsha"])
|
|
repo = gitpkg.Repo(str(checkout_dir))
|
|
assert repo.head.is_detached
|
|
|
|
def test_checkout_at_correct_commit(self, tmp_path):
|
|
import git as gitpkg
|
|
|
|
meta = _make_bare_cache(tmp_path, "https://example.com/org/repo")
|
|
checkout_dir = _ensure_checkout(meta["cache_dir"], meta["hexsha"])
|
|
repo = gitpkg.Repo(str(checkout_dir))
|
|
assert repo.head.commit.hexsha == meta["hexsha"]
|
|
|
|
def test_file_present_in_checkout(self, tmp_path):
|
|
meta = _make_bare_cache(tmp_path, "https://example.com/org/repo")
|
|
checkout_dir = _ensure_checkout(meta["cache_dir"], meta["hexsha"])
|
|
assert (checkout_dir / "model.ifc").exists()
|
|
|
|
def test_idempotent_same_commit(self, tmp_path):
|
|
meta = _make_bare_cache(tmp_path, "https://example.com/org/repo")
|
|
d1 = _ensure_checkout(meta["cache_dir"], meta["hexsha"])
|
|
d2 = _ensure_checkout(meta["cache_dir"], meta["hexsha"])
|
|
assert d1 == d2
|
|
|
|
def test_updates_to_new_commit(self, tmp_path):
|
|
import git as gitpkg
|
|
|
|
meta = _make_bare_cache(tmp_path, "https://example.com/org/repo")
|
|
_ensure_checkout(meta["cache_dir"], meta["hexsha"])
|
|
|
|
# Add a new commit to the bare cache
|
|
src_repo = gitpkg.Repo(str(meta["src"]))
|
|
(meta["src"] / "model.ifc").write_bytes(b"IFC4\nV2\n")
|
|
src_repo.index.add(["model.ifc"])
|
|
new_commit = src_repo.index.commit("update")
|
|
bare = gitpkg.Repo(str(meta["bare_dir"]))
|
|
bare.git.fetch(str(meta["src"]), "+refs/*:refs/*")
|
|
|
|
checkout_dir = _ensure_checkout(meta["cache_dir"], new_commit.hexsha)
|
|
repo = gitpkg.Repo(str(checkout_dir))
|
|
assert repo.head.commit.hexsha == new_commit.hexsha
|
|
assert (checkout_dir / "model.ifc").read_bytes() == b"IFC4\nV2\n"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# get_checkout
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestGetCheckout:
|
|
def _url_and_cache(self, tmp_path, monkeypatch, remote_url="https://example.com/org/repo"):
|
|
monkeypatch.setattr("ifcurl.git.user_cache_dir", lambda *a, **kw: str(tmp_path))
|
|
meta = _make_bare_cache(tmp_path, remote_url)
|
|
url = _ifc_url(meta["hexsha"], remote_url)
|
|
return url, meta
|
|
|
|
def test_returns_path(self, tmp_path, monkeypatch):
|
|
url, _ = self._url_and_cache(tmp_path, monkeypatch)
|
|
result = get_checkout(url)
|
|
assert isinstance(result, Path)
|
|
|
|
def test_file_exists(self, tmp_path, monkeypatch):
|
|
url, _ = self._url_and_cache(tmp_path, monkeypatch)
|
|
result = get_checkout(url)
|
|
assert result.exists()
|
|
|
|
def test_file_content_correct(self, tmp_path, monkeypatch):
|
|
url, meta = self._url_and_cache(tmp_path, monkeypatch)
|
|
result = get_checkout(url)
|
|
assert result.read_bytes() == meta["bytes"]
|
|
|
|
def test_path_ends_with_ifc_filename(self, tmp_path, monkeypatch):
|
|
url, _ = self._url_and_cache(tmp_path, monkeypatch)
|
|
result = get_checkout(url)
|
|
assert result.name == "model.ifc"
|
|
|
|
def test_checkout_is_detached_head(self, tmp_path, monkeypatch):
|
|
import git as gitpkg
|
|
|
|
url, meta = self._url_and_cache(tmp_path, monkeypatch)
|
|
get_checkout(url)
|
|
checkout_dir = meta["cache_dir"] / "checkout"
|
|
repo = gitpkg.Repo(str(checkout_dir))
|
|
assert repo.head.is_detached
|
|
|
|
def test_viewer_excludes_written(self, tmp_path, monkeypatch):
|
|
url, meta = self._url_and_cache(tmp_path, monkeypatch)
|
|
get_checkout(url)
|
|
exclude = meta["cache_dir"] / "checkout" / ".git" / "info" / "exclude"
|
|
content = exclude.read_text()
|
|
assert "*.ifcview" in content
|
|
assert "*.rdbview" in content
|
|
|
|
def test_idempotent(self, tmp_path, monkeypatch):
|
|
url, _ = self._url_and_cache(tmp_path, monkeypatch)
|
|
p1 = get_checkout(url)
|
|
p2 = get_checkout(url)
|
|
assert p1 == p2
|
|
|
|
def test_nested_ifc_path(self, tmp_path, monkeypatch):
|
|
import git as gitpkg
|
|
|
|
remote_url = "https://example.com/org/repo2"
|
|
monkeypatch.setattr("ifcurl.git.user_cache_dir", lambda *a, **kw: str(tmp_path))
|
|
|
|
src = tmp_path / "source2"
|
|
(src / "models").mkdir(parents=True)
|
|
(src / "models" / "structure.ifc").write_bytes(b"IFC4\n")
|
|
repo = gitpkg.Repo.init(str(src))
|
|
with repo.config_writer() as cw:
|
|
cw.set_value("user", "name", "Test")
|
|
cw.set_value("user", "email", "t@t.com")
|
|
repo.index.add(["models/structure.ifc"])
|
|
commit = repo.index.commit("init")
|
|
|
|
url_hash = hashlib.sha256(remote_url.encode()).hexdigest()[:24]
|
|
cache_dir = tmp_path / url_hash
|
|
gitpkg.Repo.clone_from(str(src), str(cache_dir / "repo.git"), bare=True)
|
|
(cache_dir / "remote_url").write_text(remote_url)
|
|
|
|
url = IfcUrl.parse(f"ifc://example.com/org/repo2@{commit.hexsha}?path=models/structure.ifc")
|
|
result = get_checkout(url)
|
|
assert result.name == "structure.ifc"
|
|
assert result.exists()
|
|
|
|
def test_local_transport_raises(self, tmp_path):
|
|
url = IfcUrl.parse(f"ifc://{tmp_path}@HEAD?path=model.ifc")
|
|
with pytest.raises(ValueError, match="local"):
|
|
get_checkout(url)
|
|
|
|
def test_no_path_raises(self, tmp_path, monkeypatch):
|
|
monkeypatch.setattr("ifcurl.git.user_cache_dir", lambda *a, **kw: str(tmp_path))
|
|
url = IfcUrl.parse("ifc://example.com/org/repo@abc123def")
|
|
with pytest.raises(ValueError, match="'path'"):
|
|
get_checkout(url)
|