| 
9 | 9 | import pytest  | 
10 | 10 | 
 
  | 
11 | 11 | from libvcs._internal.run import run  | 
 | 12 | +from libvcs.cmd.git import Git  | 
 | 13 | +from libvcs.exc import CommandError  | 
 | 14 | +from libvcs.pytest_plugin import (  | 
 | 15 | +    DEFAULT_GIT_INITIAL_BRANCH,  | 
 | 16 | +    _create_git_remote_repo,  | 
 | 17 | +)  | 
12 | 18 | 
 
  | 
13 | 19 | if t.TYPE_CHECKING:  | 
14 | 20 |     import pathlib  | 
@@ -176,3 +182,221 @@ def test_git_bare_repo_sync_and_commit(  | 
176 | 182 |     # Test  | 
177 | 183 |     result = pytester.runpytest(str(first_test_filename))  | 
178 | 184 |     result.assert_outcomes(passed=2)  | 
 | 185 | + | 
 | 186 | + | 
 | 187 | +def test_create_git_remote_repo_basic(tmp_path: pathlib.Path) -> None:  | 
 | 188 | +    """Test basic git repository creation."""  | 
 | 189 | +    repo_path = tmp_path / "test-repo"  | 
 | 190 | + | 
 | 191 | +    result = _create_git_remote_repo(repo_path, init_cmd_args=[])  | 
 | 192 | + | 
 | 193 | +    assert result == repo_path  | 
 | 194 | +    assert repo_path.exists()  | 
 | 195 | +    assert (repo_path / ".git").exists()  | 
 | 196 | + | 
 | 197 | + | 
 | 198 | +def test_create_git_remote_repo_bare(tmp_path: pathlib.Path) -> None:  | 
 | 199 | +    """Test bare git repository creation."""  | 
 | 200 | +    repo_path = tmp_path / "test-repo.git"  | 
 | 201 | + | 
 | 202 | +    result = _create_git_remote_repo(repo_path, init_cmd_args=["--bare"])  | 
 | 203 | + | 
 | 204 | +    assert result == repo_path  | 
 | 205 | +    assert repo_path.exists()  | 
 | 206 | +    assert (repo_path / "HEAD").exists()  | 
 | 207 | +    assert not (repo_path / ".git").exists()  | 
 | 208 | + | 
 | 209 | + | 
 | 210 | +def test_create_git_remote_repo_with_initial_branch(  | 
 | 211 | +    tmp_path: pathlib.Path,  | 
 | 212 | +    monkeypatch: pytest.MonkeyPatch,  | 
 | 213 | +) -> None:  | 
 | 214 | +    """Test repository creation with custom initial branch.  | 
 | 215 | +
  | 
 | 216 | +    This test checks both modern Git (2.30.0+) and fallback behavior.  | 
 | 217 | +    """  | 
 | 218 | +    repo_path = tmp_path / "test-repo"  | 
 | 219 | + | 
 | 220 | +    # Track Git.init calls  | 
 | 221 | +    init_calls: list[dict[str, t.Any]] = []  | 
 | 222 | + | 
 | 223 | +    def mock_init(self: Git, *args: t.Any, **kwargs: t.Any) -> str:  | 
 | 224 | +        init_calls.append({"args": args, "kwargs": kwargs})  | 
 | 225 | + | 
 | 226 | +        # Simulate old Git that doesn't support --initial-branch  | 
 | 227 | +        if kwargs.get("initial_branch"):  | 
 | 228 | +            msg = "error: unknown option `initial-branch'"  | 
 | 229 | +            raise CommandError(  | 
 | 230 | +                msg,  | 
 | 231 | +                returncode=1,  | 
 | 232 | +                cmd=["git", "init", "--initial-branch=main"],  | 
 | 233 | +            )  | 
 | 234 | + | 
 | 235 | +        # Create the repo directory to simulate successful init  | 
 | 236 | +        self.path.mkdir(exist_ok=True)  | 
 | 237 | +        (self.path / ".git").mkdir(exist_ok=True)  | 
 | 238 | +        return "Initialized empty Git repository"  | 
 | 239 | + | 
 | 240 | +    monkeypatch.setattr(Git, "init", mock_init)  | 
 | 241 | + | 
 | 242 | +    result = _create_git_remote_repo(repo_path, initial_branch="develop")  | 
 | 243 | + | 
 | 244 | +    # Should have tried twice: once with initial_branch, once without  | 
 | 245 | +    assert len(init_calls) == 2  | 
 | 246 | +    assert init_calls[0]["kwargs"].get("initial_branch") == "develop"  | 
 | 247 | +    assert "initial_branch" not in init_calls[1]["kwargs"]  | 
 | 248 | +    assert result == repo_path  | 
 | 249 | + | 
 | 250 | + | 
 | 251 | +def test_create_git_remote_repo_modern_git(  | 
 | 252 | +    tmp_path: pathlib.Path,  | 
 | 253 | +    monkeypatch: pytest.MonkeyPatch,  | 
 | 254 | +) -> None:  | 
 | 255 | +    """Test repository creation with Git 2.30.0+ that supports --initial-branch."""  | 
 | 256 | +    repo_path = tmp_path / "test-repo"  | 
 | 257 | + | 
 | 258 | +    init_calls: list[dict[str, t.Any]] = []  | 
 | 259 | + | 
 | 260 | +    def mock_init(self: Git, *args: t.Any, **kwargs: t.Any) -> str:  | 
 | 261 | +        init_calls.append({"args": args, "kwargs": kwargs})  | 
 | 262 | +        # Simulate successful init with --initial-branch support  | 
 | 263 | +        self.path.mkdir(exist_ok=True)  | 
 | 264 | +        (self.path / ".git").mkdir(exist_ok=True)  | 
 | 265 | +        branch = kwargs.get("initial_branch", "master")  | 
 | 266 | +        return f"Initialized empty Git repository with initial branch '{branch}'"  | 
 | 267 | + | 
 | 268 | +    monkeypatch.setattr(Git, "init", mock_init)  | 
 | 269 | + | 
 | 270 | +    result = _create_git_remote_repo(repo_path, initial_branch="main")  | 
 | 271 | + | 
 | 272 | +    # Should only call init once since it succeeded  | 
 | 273 | +    assert len(init_calls) == 1  | 
 | 274 | +    assert init_calls[0]["kwargs"].get("initial_branch") == "main"  | 
 | 275 | +    assert result == repo_path  | 
 | 276 | + | 
 | 277 | + | 
 | 278 | +@pytest.mark.parametrize(  | 
 | 279 | +    ("env_var", "param", "expected_branch"),  | 
 | 280 | +    [  | 
 | 281 | +        ("custom-env", None, "custom-env"),  # Use env var  | 
 | 282 | +        ("custom-env", "param-override", "param-override"),  # Param overrides env  | 
 | 283 | +        (None, "explicit-param", "explicit-param"),  # Use param  | 
 | 284 | +        (None, None, DEFAULT_GIT_INITIAL_BRANCH),  # Use default  | 
 | 285 | +    ],  | 
 | 286 | +)  | 
 | 287 | +def test_create_git_remote_repo_branch_configuration(  | 
 | 288 | +    tmp_path: pathlib.Path,  | 
 | 289 | +    monkeypatch: pytest.MonkeyPatch,  | 
 | 290 | +    env_var: str | None,  | 
 | 291 | +    param: str | None,  | 
 | 292 | +    expected_branch: str,  | 
 | 293 | +) -> None:  | 
 | 294 | +    """Test initial branch configuration hierarchy."""  | 
 | 295 | +    # Always reload the module to ensure fresh state  | 
 | 296 | +    import sys  | 
 | 297 | + | 
 | 298 | +    if "libvcs.pytest_plugin" in sys.modules:  | 
 | 299 | +        del sys.modules["libvcs.pytest_plugin"]  | 
 | 300 | + | 
 | 301 | +    if env_var:  | 
 | 302 | +        monkeypatch.setenv("LIBVCS_GIT_DEFAULT_INITIAL_BRANCH", env_var)  | 
 | 303 | + | 
 | 304 | +    # Import after setting env var  | 
 | 305 | +    from libvcs.pytest_plugin import _create_git_remote_repo  | 
 | 306 | + | 
 | 307 | +    repo_path = tmp_path / "test-repo"  | 
 | 308 | + | 
 | 309 | +    # Track what branch was used  | 
 | 310 | +    used_branch = None  | 
 | 311 | + | 
 | 312 | +    def mock_init(self: Git, *args: t.Any, **kwargs: t.Any) -> str:  | 
 | 313 | +        nonlocal used_branch  | 
 | 314 | +        used_branch = kwargs.get("initial_branch")  | 
 | 315 | +        self.path.mkdir(exist_ok=True)  | 
 | 316 | +        (self.path / ".git").mkdir(exist_ok=True)  | 
 | 317 | +        return "Initialized"  | 
 | 318 | + | 
 | 319 | +    monkeypatch.setattr(Git, "init", mock_init)  | 
 | 320 | + | 
 | 321 | +    _create_git_remote_repo(repo_path, initial_branch=param)  | 
 | 322 | + | 
 | 323 | +    assert used_branch == expected_branch  | 
 | 324 | + | 
 | 325 | + | 
 | 326 | +def test_create_git_remote_repo_post_init_callback(tmp_path: pathlib.Path) -> None:  | 
 | 327 | +    """Test that post-init callback is executed."""  | 
 | 328 | +    repo_path = tmp_path / "test-repo"  | 
 | 329 | +    callback_executed = False  | 
 | 330 | +    callback_path = None  | 
 | 331 | + | 
 | 332 | +    def post_init_callback(  | 
 | 333 | +        remote_repo_path: pathlib.Path,  | 
 | 334 | +        env: t.Any = None,  | 
 | 335 | +    ) -> None:  | 
 | 336 | +        nonlocal callback_executed, callback_path  | 
 | 337 | +        callback_executed = True  | 
 | 338 | +        callback_path = remote_repo_path  | 
 | 339 | +        (remote_repo_path / "callback-marker.txt").write_text("executed")  | 
 | 340 | + | 
 | 341 | +    _create_git_remote_repo(  | 
 | 342 | +        repo_path,  | 
 | 343 | +        remote_repo_post_init=post_init_callback,  | 
 | 344 | +        init_cmd_args=[],  # Create non-bare repo for easier testing  | 
 | 345 | +    )  | 
 | 346 | + | 
 | 347 | +    assert callback_executed  | 
 | 348 | +    assert callback_path == repo_path  | 
 | 349 | +    assert (repo_path / "callback-marker.txt").exists()  | 
 | 350 | +    assert (repo_path / "callback-marker.txt").read_text() == "executed"  | 
 | 351 | + | 
 | 352 | + | 
 | 353 | +def test_create_git_remote_repo_permission_error(  | 
 | 354 | +    tmp_path: pathlib.Path,  | 
 | 355 | +    monkeypatch: pytest.MonkeyPatch,  | 
 | 356 | +) -> None:  | 
 | 357 | +    """Test handling of permission errors."""  | 
 | 358 | +    repo_path = tmp_path / "test-repo"  | 
 | 359 | + | 
 | 360 | +    def mock_init(self: Git, *args: t.Any, **kwargs: t.Any) -> str:  | 
 | 361 | +        msg = "fatal: cannot mkdir .git: Permission denied"  | 
 | 362 | +        raise CommandError(  | 
 | 363 | +            msg,  | 
 | 364 | +            returncode=128,  | 
 | 365 | +            cmd=["git", "init"],  | 
 | 366 | +        )  | 
 | 367 | + | 
 | 368 | +    monkeypatch.setattr(Git, "init", mock_init)  | 
 | 369 | + | 
 | 370 | +    with pytest.raises(CommandError) as exc_info:  | 
 | 371 | +        _create_git_remote_repo(repo_path)  | 
 | 372 | + | 
 | 373 | +    assert "Permission denied" in str(exc_info.value)  | 
 | 374 | + | 
 | 375 | + | 
 | 376 | +@pytest.mark.skipif(  | 
 | 377 | +    not shutil.which("git"),  | 
 | 378 | +    reason="git is not available",  | 
 | 379 | +)  | 
 | 380 | +def test_create_git_remote_repo_integration(tmp_path: pathlib.Path) -> None:  | 
 | 381 | +    """Integration test with real git command."""  | 
 | 382 | +    repo_path = tmp_path / "integration-repo"  | 
 | 383 | + | 
 | 384 | +    result = _create_git_remote_repo(repo_path, initial_branch="development")  | 
 | 385 | + | 
 | 386 | +    assert result == repo_path  | 
 | 387 | +    assert repo_path.exists()  | 
 | 388 | + | 
 | 389 | +    # Check actual git status  | 
 | 390 | +    git = Git(path=repo_path)  | 
 | 391 | + | 
 | 392 | +    # Get git version to determine what to check  | 
 | 393 | +    try:  | 
 | 394 | +        version = git.version()  | 
 | 395 | +        if version.major > 2 or (version.major == 2 and version.minor >= 30):  | 
 | 396 | +            # Can check branch name on modern Git  | 
 | 397 | +            branch_output = git.run(["symbolic-ref", "HEAD"])  | 
 | 398 | +            assert "refs/heads/development" in branch_output  | 
 | 399 | +    except Exception:  | 
 | 400 | +        # Just verify it's a valid repo  | 
 | 401 | +        status = git.run(["status", "--porcelain"])  | 
 | 402 | +        assert isinstance(status, str)  | 
0 commit comments