Skip to content

Commit e8abe9e

Browse files
authored
Merge branch 'main' into VsCode-Extension
2 parents 8cea188 + 8be6f56 commit e8abe9e

36 files changed

+350
-363
lines changed

.pre-commit-config.yaml

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ repos:
3939
description: "Automatically convert relative imports to absolute. (Use `args: [--never]` to revert.)"
4040

4141
- repo: https://github.com/psf/black
42-
rev: 24.10.0
42+
rev: 25.1.0
4343
hooks:
4444
- id: black
4545

@@ -61,7 +61,7 @@ repos:
6161
description: "Enforce that python3.6+ type annotations are used instead of type comments."
6262

6363
- repo: https://github.com/PyCQA/isort
64-
rev: 5.13.2
64+
rev: 6.0.1
6565
hooks:
6666
- id: isort
6767
description: "Sort imports alphabetically, and automatically separated into sections and by type."
@@ -73,7 +73,7 @@ repos:
7373
- id: djlint-reformat-jinja
7474

7575
- repo: https://github.com/igorshubovych/markdownlint-cli
76-
rev: v0.43.0
76+
rev: v0.44.0
7777
hooks:
7878
- id: markdownlint
7979
description: "Lint markdown files."
@@ -88,23 +88,23 @@ repos:
8888
files: ^src/
8989

9090
- repo: https://github.com/pycqa/pylint
91-
rev: v3.3.3
91+
rev: v3.3.6
9292
hooks:
9393
- id: pylint
9494
name: pylint for source
9595
files: ^src/
9696
additional_dependencies:
9797
[
98-
chardet,
99-
click,
100-
fastapi-analytics,
98+
click>=8.0.0,
99+
"fastapi[standard]>=0.109.1",
100+
pydantic,
101101
pytest-asyncio,
102102
python-dotenv,
103103
slowapi,
104-
starlette,
104+
starlette>=0.40.0,
105105
tiktoken,
106106
tomli,
107-
uvicorn,
107+
uvicorn>=0.11.7,
108108
]
109109
- id: pylint
110110
name: pylint for tests
@@ -113,17 +113,16 @@ repos:
113113
- --rcfile=tests/.pylintrc
114114
additional_dependencies:
115115
[
116-
chardet,
117-
click,
118-
fastapi-analytics,
119-
pytest,
116+
click>=8.0.0,
117+
"fastapi[standard]>=0.109.1",
118+
pydantic,
120119
pytest-asyncio,
121120
python-dotenv,
122121
slowapi,
123-
starlette,
124-
tomli,
122+
starlette>=0.40.0,
125123
tiktoken,
126-
uvicorn,
124+
tomli,
125+
uvicorn>=0.11.7,
127126
]
128127

129128
- repo: meta

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,15 @@ readme = {file = "README.md", content-type = "text/markdown" }
66
requires-python = ">= 3.8"
77
dependencies = [
88
"click>=8.0.0",
9+
"fastapi[standard]>=0.109.1", # Vulnerable to https://osv.dev/vulnerability/PYSEC-2024-38
10+
"pydantic",
11+
"python-dotenv",
12+
"slowapi",
13+
"starlette>=0.40.0", # Vulnerable to https://osv.dev/vulnerability/GHSA-f96h-pmfr-66vw
914
"tiktoken",
1015
"tomli",
1116
"typing_extensions; python_version < '3.10'",
17+
"uvicorn>=0.11.7", # Vulnerable to https://osv.dev/vulnerability/PYSEC-2020-150
1218
]
1319

1420
license = {file = "LICENSE"}

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
chardet
21
click>=8.0.0
32
fastapi[standard]>=0.109.1 # Vulnerable to https://osv.dev/vulnerability/PYSEC-2024-38
3+
pydantic
44
python-dotenv
55
slowapi
66
starlette>=0.40.0 # Vulnerable to https://osv.dev/vulnerability/GHSA-f96h-pmfr-66vw

src/gitingest/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
""" Gitingest: A package for ingesting data from Git repositories. """
1+
"""Gitingest: A package for ingesting data from Git repositories."""
22

3-
from gitingest.cloning import clone
3+
from gitingest.cloning import clone_repo
44
from gitingest.entrypoint import ingest, ingest_async
55
from gitingest.ingestion import ingest_query
66
from gitingest.query_parsing import parse_query
77

8-
__all__ = ["ingest_query", "clone", "parse_query", "ingest", "ingest_async"]
8+
__all__ = ["ingest_query", "clone_repo", "parse_query", "ingest", "ingest_async"]

src/gitingest/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
""" Command-line interface for the Gitingest package. """
1+
"""Command-line interface for the Gitingest package."""
22

33
# pylint: disable=no-value-for-parameter
44

src/gitingest/cloning.py

Lines changed: 9 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
""" This module contains functions for cloning a Git repository to a local path. """
1+
"""This module contains functions for cloning a Git repository to a local path."""
22

3-
import asyncio
43
import os
54
from pathlib import Path
6-
from typing import List, Optional, Tuple
5+
from typing import Optional
76

8-
from gitingest.ingestion_schema import CloneConfig
7+
from gitingest.schemas import CloneConfig
8+
from gitingest.utils.git_utils import check_repo_exists, ensure_git_installed, run_command
99
from gitingest.utils.timeout_wrapper import async_timeout
1010

1111
TIMEOUT: int = 60
1212

1313

1414
@async_timeout(TIMEOUT)
15-
async def clone(config: CloneConfig) -> None:
15+
async def clone_repo(config: CloneConfig) -> None:
1616
"""
1717
Clone a repository to a local path based on the provided configuration.
1818
@@ -47,7 +47,7 @@ async def clone(config: CloneConfig) -> None:
4747
raise OSError(f"Failed to create parent directory {parent_dir}: {exc}") from exc
4848

4949
# Check if the repository exists
50-
if not await _check_repo_exists(url):
50+
if not await check_repo_exists(url):
5151
raise ValueError("Repository not found, make sure it is public")
5252

5353
clone_cmd = ["git", "clone", "--single-branch"]
@@ -64,7 +64,8 @@ async def clone(config: CloneConfig) -> None:
6464
clone_cmd += [url, local_path]
6565

6666
# Clone the repository
67-
await _run_command(*clone_cmd)
67+
await ensure_git_installed()
68+
await run_command(*clone_cmd)
6869

6970
if commit or partial_clone:
7071
checkout_cmd = ["git", "-C", local_path]
@@ -81,148 +82,4 @@ async def clone(config: CloneConfig) -> None:
8182
checkout_cmd += ["checkout", commit]
8283

8384
# Check out the specific commit and/or subpath
84-
await _run_command(*checkout_cmd)
85-
86-
87-
async def _check_repo_exists(url: str) -> bool:
88-
"""
89-
Check if a Git repository exists at the provided URL.
90-
91-
Parameters
92-
----------
93-
url : str
94-
The URL of the Git repository to check.
95-
Returns
96-
-------
97-
bool
98-
True if the repository exists, False otherwise.
99-
100-
Raises
101-
------
102-
RuntimeError
103-
If the curl command returns an unexpected status code.
104-
"""
105-
proc = await asyncio.create_subprocess_exec(
106-
"curl",
107-
"-I",
108-
url,
109-
stdout=asyncio.subprocess.PIPE,
110-
stderr=asyncio.subprocess.PIPE,
111-
)
112-
stdout, _ = await proc.communicate()
113-
114-
if proc.returncode != 0:
115-
return False
116-
117-
response = stdout.decode()
118-
status_code = _get_status_code(response)
119-
120-
if status_code in (200, 301):
121-
return True
122-
123-
if status_code in (404, 302):
124-
return False
125-
126-
raise RuntimeError(f"Unexpected status code: {status_code}")
127-
128-
129-
async def fetch_remote_branch_list(url: str) -> List[str]:
130-
"""
131-
Fetch the list of branches from a remote Git repository.
132-
Parameters
133-
----------
134-
url : str
135-
The URL of the Git repository to fetch branches from.
136-
Returns
137-
-------
138-
List[str]
139-
A list of branch names available in the remote repository.
140-
"""
141-
fetch_branches_command = ["git", "ls-remote", "--heads", url]
142-
stdout, _ = await _run_command(*fetch_branches_command)
143-
stdout_decoded = stdout.decode()
144-
145-
return [
146-
line.split("refs/heads/", 1)[1]
147-
for line in stdout_decoded.splitlines()
148-
if line.strip() and "refs/heads/" in line
149-
]
150-
151-
152-
async def _run_command(*args: str) -> Tuple[bytes, bytes]:
153-
"""
154-
Execute a command asynchronously and captures its output.
155-
156-
Parameters
157-
----------
158-
*args : str
159-
The command and its arguments to execute.
160-
161-
Returns
162-
-------
163-
Tuple[bytes, bytes]
164-
A tuple containing the stdout and stderr of the command.
165-
166-
Raises
167-
------
168-
RuntimeError
169-
If command exits with a non-zero status.
170-
"""
171-
await check_git_installed()
172-
173-
# Execute the requested command
174-
proc = await asyncio.create_subprocess_exec(
175-
*args,
176-
stdout=asyncio.subprocess.PIPE,
177-
stderr=asyncio.subprocess.PIPE,
178-
)
179-
stdout, stderr = await proc.communicate()
180-
if proc.returncode != 0:
181-
error_message = stderr.decode().strip()
182-
raise RuntimeError(f"Command failed: {' '.join(args)}\nError: {error_message}")
183-
184-
return stdout, stderr
185-
186-
187-
async def check_git_installed() -> None:
188-
"""
189-
Check if Git is installed and accessible on the system.
190-
191-
Raises
192-
------
193-
RuntimeError
194-
If Git is not installed or if the Git command exits with a non-zero status.
195-
"""
196-
try:
197-
proc = await asyncio.create_subprocess_exec(
198-
"git",
199-
"--version",
200-
stdout=asyncio.subprocess.PIPE,
201-
stderr=asyncio.subprocess.PIPE,
202-
)
203-
_, stderr = await proc.communicate()
204-
if proc.returncode != 0:
205-
error_message = stderr.decode().strip() if stderr else "Git command not found"
206-
raise RuntimeError(f"Git is not installed or not accessible: {error_message}")
207-
208-
except FileNotFoundError as exc:
209-
raise RuntimeError("Git is not installed. Please install Git before proceeding.") from exc
210-
211-
212-
def _get_status_code(response: str) -> int:
213-
"""
214-
Extract the status code from an HTTP response.
215-
216-
Parameters
217-
----------
218-
response : str
219-
The HTTP response string.
220-
221-
Returns
222-
-------
223-
int
224-
The status code of the response
225-
"""
226-
status_line = response.splitlines()[0].strip()
227-
status_code = int(status_line.split(" ", 2)[1])
228-
return status_code
85+
await run_command(*checkout_cmd)

src/gitingest/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
""" Configuration file for the project. """
1+
"""Configuration file for the project."""
22

33
import tempfile
44
from pathlib import Path

src/gitingest/entrypoint.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
""" Main entry point for ingesting a source and processing its contents. """
1+
"""Main entry point for ingesting a source and processing its contents."""
22

33
import asyncio
44
import inspect
55
import shutil
66
from typing import Optional, Set, Tuple, Union
77

8-
from gitingest.cloning import clone
8+
from gitingest.cloning import clone_repo
99
from gitingest.config import TMP_BASE_PATH
1010
from gitingest.ingestion import ingest_query
1111
from gitingest.query_parsing import IngestionQuery, parse_query
@@ -53,7 +53,7 @@ async def ingest_async(
5353
Raises
5454
------
5555
TypeError
56-
If `clone` does not return a coroutine, or if the `source` is of an unsupported type.
56+
If `clone_repo` does not return a coroutine, or if the `source` is of an unsupported type.
5757
"""
5858
repo_cloned = False
5959

@@ -71,15 +71,15 @@ async def ingest_async(
7171
query.branch = selected_branch
7272

7373
clone_config = query.extract_clone_config()
74-
clone_coroutine = clone(clone_config)
74+
clone_coroutine = clone_repo(clone_config)
7575

7676
if inspect.iscoroutine(clone_coroutine):
7777
if asyncio.get_event_loop().is_running():
7878
await clone_coroutine
7979
else:
8080
asyncio.run(clone_coroutine)
8181
else:
82-
raise TypeError("clone did not return a coroutine as expected.")
82+
raise TypeError("clone_repo did not return a coroutine as expected.")
8383

8484
repo_cloned = True
8585

src/gitingest/ingestion.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
""" Functions to ingest and analyze a codebase directory or single file. """
1+
"""Functions to ingest and analyze a codebase directory or single file."""
22

33
import warnings
44
from pathlib import Path
55
from typing import Tuple
66

77
from gitingest.config import MAX_DIRECTORY_DEPTH, MAX_FILES, MAX_TOTAL_SIZE_BYTES
8-
from gitingest.filesystem_schema import FileSystemNode, FileSystemNodeType, FileSystemStats
98
from gitingest.output_formatters import format_node
109
from gitingest.query_parsing import IngestionQuery
10+
from gitingest.schemas import FileSystemNode, FileSystemNodeType, FileSystemStats
1111
from gitingest.utils.ingestion_utils import _should_exclude, _should_include
1212
from gitingest.utils.path_utils import _is_safe_symlink
1313

src/gitingest/output_formatters.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
import tiktoken
66

7-
from gitingest.filesystem_schema import FileSystemNode, FileSystemNodeType
87
from gitingest.query_parsing import IngestionQuery
8+
from gitingest.schemas import FileSystemNode, FileSystemNodeType
99

1010

1111
def format_node(node: FileSystemNode, query: IngestionQuery) -> Tuple[str, str, str]:

0 commit comments

Comments
 (0)