Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Install dependencies
run: |
pip install --upgrade pip
pip install -r requirements-dev.txt

- name: Run tests
run: |
pytest

# Run pre-commit only on Python 3.13 + ubuntu.
- name: Run pre-commit hooks
if: ${{ matrix.python-version == '3.13' && matrix.os == 'ubuntu-latest' }}
run: |
pre-commit run --all-files
41 changes: 41 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Publish to PyPI

on:
release:
types: [created] # Trigger only when a release is created
workflow_dispatch: # Allows manual triggering of the workflow

jobs:
publish:
runs-on: ubuntu-latest

steps:
# Step 1: Check out the code
- name: Checkout code
uses: actions/checkout@v4

# Step 2: Set up Python
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.13

# Step 3: Install dependencies for building and publishing
- name: Install build tools
run: |
pip install --upgrade pip
pip install build twine

# Step 4: Build the package
- name: Build the package
run: |
python -m build

# Step 5: Publish to PyPI
- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: |
python -m twine check dist/*
python -m twine upload --skip-existing dist/*
33 changes: 0 additions & 33 deletions .github/workflows/unitest.yml

This file was deleted.

36 changes: 18 additions & 18 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,39 @@ repos:
hooks:
# Files
- id: check-added-large-files
description: 'Prevent large files from being committed.'
args: ['--maxkb=10000']
description: "Prevent large files from being committed."
args: ["--maxkb=10000"]
- id: check-case-conflict
description: 'Check for files that would conflict in case-insensitive filesystems.'
description: "Check for files that would conflict in case-insensitive filesystems."
- id: fix-byte-order-marker
description: 'Remove utf-8 byte order marker.'
description: "Remove utf-8 byte order marker."
- id: mixed-line-ending
description: 'Replace mixed line ending.'
description: "Replace mixed line ending."

# Links
- id: destroyed-symlinks
description: 'Detect symlinks which are changed to regular files with a content of a path which that symlink was pointing to.'
description: "Detect symlinks which are changed to regular files with a content of a path which that symlink was pointing to."

# File files for parseable syntax: python
- id: check-ast

# File and line endings
- id: end-of-file-fixer
description: 'Ensure that a file is either empty, or ends with one newline.'
description: "Ensure that a file is either empty, or ends with one newline."
- id: trailing-whitespace
description: 'Trim trailing whitespace.'
description: "Trim trailing whitespace."

# Python
- id: check-docstring-first
description: 'Check a common error of defining a docstring after code.'
description: "Check a common error of defining a docstring after code."
- id: requirements-txt-fixer
description: 'Sort entries in requirements.txt.'
description: "Sort entries in requirements.txt."

- repo: https://github.com/MarcoGorelli/absolufy-imports
rev: v0.3.1
hooks:
- id: absolufy-imports
description: 'Automatically convert relative imports to absolute. (Use `args: [--never]` to revert.)'
description: "Automatically convert relative imports to absolute. (Use `args: [--never]` to revert.)"

- repo: https://github.com/psf/black
rev: 24.10.0
Expand All @@ -47,30 +47,30 @@ repos:
rev: v3.19.1
hooks:
- id: pyupgrade
description: 'Automatically upgrade syntax for newer versions.'
args: [--py3-plus, --py36-plus, --py38-plus]
description: "Automatically upgrade syntax for newer versions."
args: [--py3-plus, --py36-plus, --py38-plus, --py39-plus, --py310-plus]

- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- id: python-check-blanket-noqa
description: 'Enforce that `noqa` annotations always occur with specific codes. Sample annotations: `# noqa: F401`, `# noqa: F401,W203`.'
description: "Enforce that `noqa` annotations always occur with specific codes. Sample annotations: `# noqa: F401`, `# noqa: F401,W203`."
- id: python-check-blanket-type-ignore
description: 'Enforce that `# type: ignore` annotations always occur with specific codes. Sample annotations: `# type: ignore[attr-defined]`, `# type: ignore[attr-defined, name-defined]`.'
description: "Enforce that `# type: ignore` annotations always occur with specific codes. Sample annotations: `# type: ignore[attr-defined]`, `# type: ignore[attr-defined, name-defined]`."
- id: python-use-type-annotations
description: 'Enforce that python3.6+ type annotations are used instead of type comments.'
description: "Enforce that python3.6+ type annotations are used instead of type comments."

- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
description: 'Sort imports alphabetically, and automatically separated into sections and by type.'
description: "Sort imports alphabetically, and automatically separated into sections and by type."

- repo: https://github.com/hadialqattan/pycln
rev: v2.4.0
hooks:
- id: pycln
description: 'Remove unused import statements.'
description: "Remove unused import statements."

- repo: https://github.com/djlint/djLint
rev: v1.36.4
Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,3 @@ filter_files = true

[tool.black]
line-length = 119
skip-string-normalization = true
7 changes: 7 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-r requirements.txt
black
djlint
pre-commit
pylint
pytest
pytest-asyncio
5 changes: 0 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
black
click>=8.0.0
djlint
fastapi-analytics
fastapi[standard]
pre-commit
pytest
pytest-asyncio
python-dotenv
slowapi
starlette
Expand Down
20 changes: 9 additions & 11 deletions src/gitingest/cli.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import os
from typing import Optional, Tuple

import click

from gitingest.ignore_patterns import DEFAULT_IGNORE_PATTERNS
from gitingest.ingest import ingest
from gitingest.ingest_from_query import MAX_FILE_SIZE

Expand All @@ -17,17 +15,17 @@ def normalize_pattern(pattern: str) -> str:


@click.command()
@click.argument('source', type=str, required=True)
@click.option('--output', '-o', default=None, help='Output file path (default: <repo_name>.txt in current directory)')
@click.option('--max-size', '-s', default=MAX_FILE_SIZE, help='Maximum file size to process in bytes')
@click.option('--exclude-pattern', '-e', multiple=True, help='Patterns to exclude')
@click.option('--include-pattern', '-i', multiple=True, help='Patterns to include')
@click.argument("source", type=str, required=True)
@click.option("--output", "-o", default=None, help="Output file path (default: <repo_name>.txt in current directory)")
@click.option("--max-size", "-s", default=MAX_FILE_SIZE, help="Maximum file size to process in bytes")
@click.option("--exclude-pattern", "-e", multiple=True, help="Patterns to exclude")
@click.option("--include-pattern", "-i", multiple=True, help="Patterns to include")
def main(
source: str,
output: Optional[str],
output: str | None,
max_size: int,
exclude_pattern: Tuple[str, ...],
include_pattern: Tuple[str, ...],
exclude_pattern: tuple[str, ...],
include_pattern: tuple[str, ...],
) -> None:
"""Analyze a directory and create a text dump of its contents."""
try:
Expand All @@ -48,5 +46,5 @@ def main(
raise click.Abort()


if __name__ == '__main__':
if __name__ == "__main__":
main()
19 changes: 11 additions & 8 deletions src/gitingest/clone.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import asyncio
from dataclasses import dataclass
from typing import Optional, Tuple

from gitingest.utils import AsyncTimeoutError, async_timeout

Expand All @@ -11,8 +10,9 @@
class CloneConfig:
url: str
local_path: str
commit: Optional[str] = None
branch: Optional[str] = None
commit: str | None = None
branch: str | None = None



async def check_repo_exists(url: str) -> bool:
Expand Down Expand Up @@ -44,7 +44,8 @@ async def check_repo_exists(url: str) -> bool:
return "HTTP/1.1 404" not in stdout_str and "HTTP/2 404" not in stdout_str


async def run_git_command(*args: str) -> Tuple[bytes, bytes]:
async def run_git_command(*args: str) -> tuple[bytes, bytes]:

"""
Executes a git command asynchronously and captures its output.

Expand Down Expand Up @@ -77,7 +78,7 @@ async def run_git_command(*args: str) -> Tuple[bytes, bytes]:


@async_timeout(CLONE_TIMEOUT)
async def clone_repo(config: CloneConfig) -> Tuple[bytes, bytes]:
async def clone_repo(config: CloneConfig) -> tuple[bytes, bytes]:
"""
Clones a repository to a local path based on the provided query parameters.

Expand Down Expand Up @@ -107,8 +108,9 @@ async def clone_repo(config: CloneConfig) -> Tuple[bytes, bytes]:
# Extract and validate query parameters
url: str = config.url
local_path: str = config.local_path
commit: Optional[str] = config.commit
branch: Optional[str] = config.branch
commit: str | None = config.commit
branch: str | None = config.branch


if not url:
raise ValueError("The 'url' parameter is required.")
Expand All @@ -131,7 +133,8 @@ async def clone_repo(config: CloneConfig) -> Tuple[bytes, bytes]:
checkout_cmd = ["git", "-C", local_path, "checkout", commit]
return await run_git_command(*checkout_cmd)

if branch and branch.lower() not in ('main', 'master'):
if branch and branch.lower() not in ("main", "master"):

# Scenario 2: Clone a specific branch with shallow depth
clone_cmd = ["git", "clone", "--depth=1", "--single-branch", "--branch", branch, url, local_path]
return await run_git_command(*clone_cmd)
Expand Down
Loading
Loading