Skip to content

Commit 361147a

Browse files
authored
feat: add branch option to CLI and ingest function for cloning specific branches (#155)
1 parent b34b7f4 commit 361147a

File tree

3 files changed

+76
-4
lines changed

3 files changed

+76
-4
lines changed

src/gitingest/cli.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
@click.option("--max-size", "-s", default=MAX_FILE_SIZE, help="Maximum file size to process in bytes")
1717
@click.option("--exclude-pattern", "-e", multiple=True, help="Patterns to exclude")
1818
@click.option("--include-pattern", "-i", multiple=True, help="Patterns to include")
19+
@click.option("--branch", "-b", default=None, help="Branch to clone and ingest")
1920
def main(
2021
source: str,
2122
output: str | None,
2223
max_size: int,
2324
exclude_pattern: tuple[str, ...],
2425
include_pattern: tuple[str, ...],
26+
branch: str | None,
2527
):
2628
"""
2729
Main entry point for the CLI. This function is called when the CLI is run as a script.
@@ -41,9 +43,11 @@ def main(
4143
A tuple of patterns to exclude during the analysis. Files matching these patterns will be ignored.
4244
include_pattern : tuple[str, ...]
4345
A tuple of patterns to include during the analysis. Only files matching these patterns will be processed.
46+
branch : str | None
47+
The branch to clone (optional).
4448
"""
4549
# Main entry point for the CLI. This function is called when the CLI is run as a script.
46-
asyncio.run(_async_main(source, output, max_size, exclude_pattern, include_pattern))
50+
asyncio.run(_async_main(source, output, max_size, exclude_pattern, include_pattern, branch))
4751

4852

4953
async def _async_main(
@@ -52,6 +56,7 @@ async def _async_main(
5256
max_size: int,
5357
exclude_pattern: tuple[str, ...],
5458
include_pattern: tuple[str, ...],
59+
branch: str | None,
5560
) -> None:
5661
"""
5762
Analyze a directory or repository and create a text dump of its contents.
@@ -72,6 +77,8 @@ async def _async_main(
7277
A tuple of patterns to exclude during the analysis. Files matching these patterns will be ignored.
7378
include_pattern : tuple[str, ...]
7479
A tuple of patterns to include during the analysis. Only files matching these patterns will be processed.
80+
branch : str | None
81+
The branch to clone (optional).
7582
7683
Raises
7784
------
@@ -85,7 +92,7 @@ async def _async_main(
8592

8693
if not output:
8794
output = OUTPUT_FILE_PATH
88-
summary, _, _ = await ingest(source, max_size, include_patterns, exclude_patterns, output=output)
95+
summary, _, _ = await ingest(source, max_size, include_patterns, exclude_patterns, branch, output=output)
8996

9097
click.echo(f"Analysis complete! Output written to: {output}")
9198
click.echo("\nSummary:")

src/gitingest/repository_ingest.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ async def ingest(
1515
max_file_size: int = 10 * 1024 * 1024, # 10 MB
1616
include_patterns: set[str] | str | None = None,
1717
exclude_patterns: set[str] | str | None = None,
18+
branch: str | None = None,
1819
output: str | None = None,
1920
) -> tuple[str, str, str]:
2021
"""
@@ -35,6 +36,8 @@ async def ingest(
3536
Pattern or set of patterns specifying which files to include. If `None`, all files are included.
3637
exclude_patterns : set[str] | str | None, optional
3738
Pattern or set of patterns specifying which files to exclude. If `None`, no files are excluded.
39+
branch : str | None, optional
40+
The branch to clone and ingest. If `None`, the default branch is used.
3841
output : str | None, optional
3942
File path where the summary and content should be written. If `None`, the results are not written to a file.
4043
@@ -61,17 +64,23 @@ async def ingest(
6164
)
6265

6366
if parsed_query.url:
67+
selected_branch = branch if branch else parsed_query.branch # prioritize branch argument
68+
parsed_query.branch = selected_branch
69+
6470
# Extract relevant fields for CloneConfig
6571
clone_config = CloneConfig(
6672
url=parsed_query.url,
6773
local_path=str(parsed_query.local_path),
6874
commit=parsed_query.commit,
69-
branch=parsed_query.branch,
75+
branch=selected_branch,
7076
)
7177
clone_result = clone_repo(clone_config)
7278

7379
if inspect.iscoroutine(clone_result):
74-
asyncio.run(clone_result)
80+
if asyncio.get_event_loop().is_running():
81+
await clone_result
82+
else:
83+
asyncio.run(clone_result)
7584
else:
7685
raise TypeError("clone_repo did not return a coroutine as expected.")
7786

tests/test_repository_clone.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77

88
import asyncio
9+
import os
910
from unittest.mock import AsyncMock, patch
1011

1112
import pytest
@@ -306,3 +307,58 @@ async def test_clone_repo_with_timeout() -> None:
306307
mock_exec.side_effect = asyncio.TimeoutError
307308
with pytest.raises(AsyncTimeoutError, match="Operation timed out after"):
308309
await clone_repo(clone_config)
310+
311+
312+
@pytest.mark.asyncio
313+
async def test_clone_specific_branch(tmp_path):
314+
"""
315+
Test cloning a specific branch of a repository.
316+
317+
Given a valid repository URL and a branch name:
318+
When `clone_repo` is called,
319+
Then the repository should be cloned and checked out at that branch.
320+
"""
321+
repo_url = "https://github.com/cyclotruc/gitingest.git"
322+
branch_name = "main"
323+
local_path = tmp_path / "gitingest"
324+
325+
config = CloneConfig(url=repo_url, local_path=str(local_path), branch=branch_name)
326+
await clone_repo(config)
327+
328+
# Assertions
329+
assert local_path.exists(), "The repository was not cloned successfully."
330+
assert local_path.is_dir(), "The cloned repository path is not a directory."
331+
332+
# Check the current branch
333+
current_branch = os.popen(f"git -C {local_path} branch --show-current").read().strip()
334+
assert current_branch == branch_name, f"Expected branch '{branch_name}', got '{current_branch}'."
335+
336+
337+
@pytest.mark.asyncio
338+
async def test_clone_branch_with_slashes(tmp_path):
339+
"""
340+
Test cloning a branch with slashes in the name.
341+
342+
Given a valid repository URL and a branch name with slashes:
343+
When `clone_repo` is called,
344+
Then the repository should be cloned and checked out at that branch.
345+
"""
346+
repo_url = "https://github.com/user/repo"
347+
branch_name = "fix/in-operator"
348+
local_path = tmp_path / "gitingest"
349+
350+
clone_config = CloneConfig(url=repo_url, local_path=str(local_path), branch=branch_name)
351+
with patch("gitingest.repository_clone._check_repo_exists", return_value=True):
352+
with patch("gitingest.repository_clone._run_git_command", new_callable=AsyncMock) as mock_exec:
353+
await clone_repo(clone_config)
354+
355+
mock_exec.assert_called_once_with(
356+
"git",
357+
"clone",
358+
"--depth=1",
359+
"--single-branch",
360+
"--branch",
361+
"fix/in-operator",
362+
clone_config.url,
363+
clone_config.local_path,
364+
)

0 commit comments

Comments
 (0)