diff --git a/src/gitingest/clone.py b/src/gitingest/clone.py index 9999fcd7..85756de0 100644 --- a/src/gitingest/clone.py +++ b/src/gitingest/clone.py @@ -96,18 +96,32 @@ async def clone_repo(config: CloneConfig, *, token: str | None = None) -> None: } with git_auth_context(url, token) as (git_cmd, auth_url): + # Determine branch/tag flag for --single-branch to fetch the correct ref + branch_args: list[str] = [] + if config.branch: + branch_args = ["-b", config.branch] + elif config.tag: + branch_args = ["-b", config.tag] + if partial_clone: # For partial clones, use git.Git() with filter and sparse options cmd_args = ["--single-branch", "--no-checkout", "--depth=1"] + cmd_args.extend(branch_args) cmd_args.extend(["--filter=blob:none", "--sparse"]) cmd_args.extend([auth_url, local_path]) git_cmd.clone(*cmd_args) elif token and is_github_host(url): # For authenticated GitHub repos, use git_cmd with auth URL - cmd_args = ["--single-branch", "--no-checkout", "--depth=1", auth_url, local_path] + cmd_args = ["--single-branch", "--no-checkout", "--depth=1"] + cmd_args.extend(branch_args) + cmd_args.extend([auth_url, local_path]) git_cmd.clone(*cmd_args) else: # For non-authenticated repos, use the standard GitPython method + if config.branch: + clone_kwargs["branch"] = config.branch + elif config.tag: + clone_kwargs["branch"] = config.tag git.Repo.clone_from(url, local_path, **clone_kwargs) logger.info("Git clone completed successfully") diff --git a/tests/test_clone.py b/tests/test_clone.py index 6abbd87c..5dae8512 100644 --- a/tests/test_clone.py +++ b/tests/test_clone.py @@ -205,6 +205,52 @@ async def test_clone_with_include_submodules(gitpython_mocks: dict) -> None: mock_repo.git.submodule.assert_called_with("update", "--init", "--recursive", "--depth=1") +@pytest.mark.asyncio +async def test_clone_with_branch_passes_branch_to_git(repo_exists_true: AsyncMock, gitpython_mocks: dict) -> None: + """Test that cloning with a branch passes -b to the git clone command. + + Given a valid URL and a specific branch: + When ``clone_repo`` is called, + Then the clone command should include the branch so --single-branch fetches the correct ref. + """ + clone_config = CloneConfig(url=DEMO_URL, local_path=LOCAL_REPO_PATH, commit=None, branch="feature-branch") + + await clone_repo(clone_config) + + mock_clone_from = gitpython_mocks["clone_from"] + mock_clone_from.assert_called_once() + + _, kwargs = mock_clone_from.call_args + assert kwargs.get("branch") == "feature-branch", ( + "clone_from should receive branch='feature-branch' so --single-branch fetches the correct ref" + ) + + +@pytest.mark.asyncio +async def test_clone_with_branch_and_token_passes_branch_flag( + repo_exists_true: AsyncMock, gitpython_mocks: dict +) -> None: + """Test that cloning with a branch and token passes -b to git.Git().clone(). + + Given a GitHub URL, a token, and a specific branch: + When ``clone_repo`` is called, + Then the raw git clone args should include -b . + """ + clone_config = CloneConfig( + url=DEMO_URL, local_path=LOCAL_REPO_PATH, commit=None, branch="feature-branch" + ) + + await clone_repo(clone_config, token="ghp_testtoken123") + + mock_git_cmd = gitpython_mocks["git_cmd"] + mock_git_cmd.clone.assert_called_once() + + clone_args = mock_git_cmd.clone.call_args[0] + assert "-b" in clone_args, "clone args should include -b flag" + b_index = list(clone_args).index("-b") + assert clone_args[b_index + 1] == "feature-branch", "branch name should follow -b flag" + + @pytest.mark.asyncio async def test_check_repo_exists_with_auth_token(mocker: MockerFixture) -> None: """Test ``check_repo_exists`` with authentication token.