Metadata-Version: 2.4
Name: rusty-git
Version: 0.3.0
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Version Control :: Git
Summary: High-speed pure Rust Git engine (gix + git2) for Python
Keywords: git,gix,pyo3,gitoxide,forge,libgit2
Author-email: Framework M <dev@frameworkm.dev>
License: Apache-2.0
Requires-Python: >=3.12
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://gitlab.com/framework-m/rusty-git
Project-URL: Issues, https://gitlab.com/framework-m/rusty-git/-/issues
Project-URL: Repository, https://gitlab.com/framework-m/rusty-git

# rusty-git

[![GitLab CI](https://gitlab.com/framework-m/rusty-git/badges/main/pipeline.svg)](https://gitlab.com/framework-m/rusty-git/-/pipelines)
[![PyPI version](https://img.shields.io/pypi/v/rusty-git.svg)](https://pypi.org/project/rusty-git/)
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

**High-speed pure Rust Git engine (`gix` + `git2`) compiled via PyO3 with both native `async` (Tokio) and GIL-released `sync` APIs.**

`rusty-git` provides in-process Git operations with **zero OS subprocess forks** and **statically-compiled native wheels** (no dynamic `libgit2.so` dependencies required).

---

## Installation

```bash
pip install rusty-git
# or with uv:
uv add rusty-git
```

---

## Quickstart: 100% In-Process Demo (Zero Subprocesses)

Copy-paste this into `example.py` to test immediately in any environment:

```python
import asyncio
import os
import tempfile
import rusty_git


async def main():
    with tempfile.TemporaryDirectory() as tmp_dir:
        repo_path = os.path.join(tmp_dir, "demo.git")

        # 1. Initialize a bare repository in-process (ZERO subprocesses)
        await rusty_git.init_bare_repository(repo_path)

        # 2. Create the initial root commit in-process
        res = await rusty_git.create_commit(
            repo_path,
            branch="main",
            files={
                "README.md": b"# Hello from rusty-git!\n",
                "src/main.py": b"print('Hello World')\n",
            },
            message="Initial commit",
            author_name="Alice",
            author_email="alice@example.com",
        )
        print("Root Commit SHA:", res["commit_sha"])

        # 3. Inspect commit in-process
        commit = await rusty_git.get_commit(repo_path, "HEAD")
        print(f"Author:  {commit['author_name']} <{commit['author_email']}>")
        print(f"Message: {commit['message']}")

        # 4. Read tree blob without disk checkout
        blob = await rusty_git.get_tree_blob(repo_path, "HEAD", "README.md")
        print("Blob Content:", blob.decode("utf-8").strip())

        # 5. List branches
        branches = await rusty_git.list_branches(repo_path)
        print("Branches:", branches)


asyncio.run(main())
```

---

## Dual-Color Usage: Async & Sync

### 1. Asynchronous API (Default - for Litestar, FastAPI, Granian, Uvicorn)

All blocking Git I/O is offloaded to a Tokio multi-threaded worker pool, releasing the Python GIL:

```python
import asyncio
import rusty_git


async def main():
    repo_path = "/path/to/your/repo"

    # 1. Inspect commits
    commit = await rusty_git.get_commit(repo_path, "HEAD")
    print(commit["sha"], commit["message"], commit["author_name"])

    # 2. Read tree blobs without disk checkout
    readme = await rusty_git.get_tree_blob(repo_path, "main", "README.md")
    print(readme.decode("utf-8"))

    # 3. Fast unified diffs
    hunks = await rusty_git.compare_refs(repo_path, "main~1", "main")
    for hunk in hunks:
        print(hunk["new_path"], hunk["content"])

    # 4. 3-Way Merge
    result = await rusty_git.execute_merge(
        repo_path,
        base_ref="main",
        head_ref="feature",
        author_name="Automated Merge",
        author_email="bot@example.com",
    )
    if result["status"] == "merged":
        print("Merged Commit SHA:", result["commit_sha"])


asyncio.run(main())
```

---

### 2. Synchronous API (`rusty_git.sync` - for CLI tools, Celery, scripts)

Direct execution on the calling thread with the Python GIL cleanly released (`py.detach`):

```python
import rusty_git

# Read commit synchronously
commit = rusty_git.sync.get_commit("/path/to/repo.git", "HEAD")
print(commit["sha"], commit["author_name"])

# List branches synchronously
branches = rusty_git.sync.list_branches("/path/to/repo.git")
print("Branches:", branches)

# Read file directly from commit tree
blob = rusty_git.sync.get_tree_blob("/path/to/repo.git", "HEAD", "pyproject.toml")

# Create a commit directly on any branch (new or existing)
res = rusty_git.sync.create_commit(
    "/path/to/repo.git",
    branch="feature-x",
    files={"hello.txt": b"Hello world!\n"},
    message="feat: add hello.txt",
)

# 3-Way Merge synchronously
result = rusty_git.sync.execute_merge(
    "/path/to/repo.git",
    "main",
    "feature",
    author_name="Merge Bot",
    author_email="bot@example.com",
)
```

---

## Architectural Note: Forges & Branch Management

`rusty-git` is designed for high-performance server-side forges and tools operating on **bare repositories**:

* **Creating branches with commits**: Passing any branch name to `create_commit(...)` will create or advance that branch atomically.
* **Creating a branch without a commit**: Git branches are simply ref pointers. To create a branch pointing to an existing commit without a new commit, your application or CLI can write the commit SHA directly to `refs/heads/<branch_name>`.
* **Switching active branch (`HEAD`)**: In a bare repo or CLI, switching the symbolic reference is done by writing `ref: refs/heads/<branch_name>\n` to the `HEAD` file.

### Engine Architecture: Pure `gix` vs. `git2` Wire Streaming

`rusty-git` intentionally separates user-facing operations from network pack streaming:

* **100% Pure `gix` Core (WebAssembly / Pyodide Ready)**: `commit` (atomic in-memory trees & commits), `inspect` (refs, tree blobs, histogram unified diffs), and `merge` (3-way ORT tree merge) run entirely on pure-Rust `gix`. They require zero C headers and run in browsers and Pyodide.
* **Wire Protocol Pack Streaming (`git2`)**: Ingesting thin packs (`git push`) and streaming sliding-window delta packs (`git clone`/`git fetch`) use `libgit2`'s battle-tested pack engine. Once `gix-pack` high-level streaming pack generation reaches full maturity, these remaining wire endpoints will transition to `gix`, completing the journey to 100% pure Rust.

This keeps `rusty-git` focused entirely on performance-critical operations without unnecessary file-I/O wrapper overhead.

---

## Smart-HTTP Protocol Wire Support

`rusty-git` can drive Git HTTP and SSH protocol negotiation completely in-process:

> **Note on Transport & Auth (SSH / TLS):** `rusty-git` operates directly on request/response byte streams rather than managing sockets. Transport encryption and authentication (SSH keys via `asyncssh`/OpenSSH, or TLS/mTLS client certs via Nginx/Caddy/HTTPX) happen cleanly at the transport layer before passing payloads to `rusty-git`.

```python
import rusty_git

# Ref advertisement for git-upload-pack (git clone / fetch)
advert = await rusty_git.advertise("/srv/repos/myorg/backend.git", "git-upload-pack")

# Handle pack negotiation and streaming
upload_res = await rusty_git.upload_pack("/srv/repos/myorg/backend.git", request_body)

# Ingest inbound git push packfile atomically
push_res = await rusty_git.receive_pack("/srv/repos/myorg/backend.git", push_body)
print("Updated Refs:", push_res["updated_refs"])
```

---

## Development & Testing

### Rust Tests
```bash
cargo test
```

### Python Integration Tests
```bash
uv run pytest
```

---

## License

Apache-2.0 © Framework M Contributors

