rusty-git 0.2.1__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rusty_git-0.2.1 → rusty_git-0.3.0}/Cargo.lock +1 -1
- {rusty_git-0.2.1 → rusty_git-0.3.0}/Cargo.toml +1 -1
- {rusty_git-0.2.1 → rusty_git-0.3.0}/PKG-INFO +87 -20
- rusty_git-0.3.0/README.md +213 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/CHANGELOG.md +12 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/Cargo.toml +2 -2
- rusty_git-0.3.0/crates/rusty-git/README.md +213 -0
- rusty_git-0.3.0/crates/rusty-git/src/commit.rs +280 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/inspect.rs +129 -112
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/lib.rs +22 -19
- rusty_git-0.3.0/crates/rusty-git/src/merge.rs +215 -0
- rusty_git-0.3.0/crates/rusty-git/tests/commit_test.rs +183 -0
- rusty_git-0.3.0/crates/rusty-git/tests/python_commit_test.py +122 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/pyproject.toml +2 -2
- {rusty_git-0.2.1 → rusty_git-0.3.0}/python/rusty_git/__init__.py +2 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/python/rusty_git/_rusty_git.pyi +34 -0
- rusty_git-0.2.1/README.md +0 -146
- rusty_git-0.2.1/crates/rusty-git/README.md +0 -146
- rusty_git-0.2.1/crates/rusty-git/src/merge.rs +0 -211
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/error.rs +0 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/graph.rs +0 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/pack.rs +0 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/pktline.rs +0 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/protocol.rs +0 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/receive.rs +0 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/upload.rs +0 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/common/mod.rs +0 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/inspect_test.rs +0 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/merge_test.rs +0 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/protocol_test.rs +0 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/python_protocol_test.py +0 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/python_smoke_test.py +0 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/python_sync_test.py +0 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/roundtrip_test.rs +0 -0
- {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rusty-git
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -10,7 +10,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
10
10
|
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
11
11
|
Summary: High-speed pure Rust Git engine (gix + git2) for Python
|
|
12
12
|
Keywords: git,gix,pyo3,gitoxide,forge,libgit2
|
|
13
|
-
Author-email: Framework M <dev@
|
|
13
|
+
Author-email: Framework M <dev@frameworkm.dev>
|
|
14
14
|
License: Apache-2.0
|
|
15
15
|
Requires-Python: >=3.12
|
|
16
16
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
@@ -30,18 +30,6 @@ Project-URL: Repository, https://gitlab.com/framework-m/rusty-git
|
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
33
|
-
## Why rusty-git?
|
|
34
|
-
|
|
35
|
-
| Feature | `GitPython` | `pygit2` | `rusty-git` |
|
|
36
|
-
|---|---|---|---|
|
|
37
|
-
| **Execution Model** | Subprocess fork (`/usr/bin/git`) | C library bindings | **In-process pure Rust + PyO3** |
|
|
38
|
-
| **GIL Behavior** | Stalls Python thread during fork | Often holds GIL | **Releases GIL on both sync & async** |
|
|
39
|
-
| **Async / Tokio** | ❌ No | ❌ No | **✅ Native `asyncio.Future` via Tokio** |
|
|
40
|
-
| **Installation** | Pure Python (requires OS `git`) | Requires `libgit2.so` & C compiler | **Self-contained precompiled wheel** |
|
|
41
|
-
| **Smart-HTTP Wire Protocol** | ❌ No | ❌ Partial | **✅ In-process Smart-HTTP negotiation** |
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
33
|
## Installation
|
|
46
34
|
|
|
47
35
|
```bash
|
|
@@ -52,6 +40,57 @@ uv add rusty-git
|
|
|
52
40
|
|
|
53
41
|
---
|
|
54
42
|
|
|
43
|
+
## Quickstart: 100% In-Process Demo (Zero Subprocesses)
|
|
44
|
+
|
|
45
|
+
Copy-paste this into `example.py` to test immediately in any environment:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import asyncio
|
|
49
|
+
import os
|
|
50
|
+
import tempfile
|
|
51
|
+
import rusty_git
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def main():
|
|
55
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
56
|
+
repo_path = os.path.join(tmp_dir, "demo.git")
|
|
57
|
+
|
|
58
|
+
# 1. Initialize a bare repository in-process (ZERO subprocesses)
|
|
59
|
+
await rusty_git.init_bare_repository(repo_path)
|
|
60
|
+
|
|
61
|
+
# 2. Create the initial root commit in-process
|
|
62
|
+
res = await rusty_git.create_commit(
|
|
63
|
+
repo_path,
|
|
64
|
+
branch="main",
|
|
65
|
+
files={
|
|
66
|
+
"README.md": b"# Hello from rusty-git!\n",
|
|
67
|
+
"src/main.py": b"print('Hello World')\n",
|
|
68
|
+
},
|
|
69
|
+
message="Initial commit",
|
|
70
|
+
author_name="Alice",
|
|
71
|
+
author_email="alice@example.com",
|
|
72
|
+
)
|
|
73
|
+
print("Root Commit SHA:", res["commit_sha"])
|
|
74
|
+
|
|
75
|
+
# 3. Inspect commit in-process
|
|
76
|
+
commit = await rusty_git.get_commit(repo_path, "HEAD")
|
|
77
|
+
print(f"Author: {commit['author_name']} <{commit['author_email']}>")
|
|
78
|
+
print(f"Message: {commit['message']}")
|
|
79
|
+
|
|
80
|
+
# 4. Read tree blob without disk checkout
|
|
81
|
+
blob = await rusty_git.get_tree_blob(repo_path, "HEAD", "README.md")
|
|
82
|
+
print("Blob Content:", blob.decode("utf-8").strip())
|
|
83
|
+
|
|
84
|
+
# 5. List branches
|
|
85
|
+
branches = await rusty_git.list_branches(repo_path)
|
|
86
|
+
print("Branches:", branches)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
asyncio.run(main())
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
55
94
|
## Dual-Color Usage: Async & Sync
|
|
56
95
|
|
|
57
96
|
### 1. Asynchronous API (Default - for Litestar, FastAPI, Granian, Uvicorn)
|
|
@@ -64,23 +103,22 @@ import rusty_git
|
|
|
64
103
|
|
|
65
104
|
|
|
66
105
|
async def main():
|
|
67
|
-
|
|
68
|
-
repo_path = await rusty_git.init_bare_repository("/srv/repos/myorg/backend.git")
|
|
106
|
+
repo_path = "/path/to/your/repo"
|
|
69
107
|
|
|
70
|
-
#
|
|
108
|
+
# 1. Inspect commits
|
|
71
109
|
commit = await rusty_git.get_commit(repo_path, "HEAD")
|
|
72
110
|
print(commit["sha"], commit["message"], commit["author_name"])
|
|
73
111
|
|
|
74
|
-
#
|
|
112
|
+
# 2. Read tree blobs without disk checkout
|
|
75
113
|
readme = await rusty_git.get_tree_blob(repo_path, "main", "README.md")
|
|
76
114
|
print(readme.decode("utf-8"))
|
|
77
115
|
|
|
78
|
-
#
|
|
116
|
+
# 3. Fast unified diffs
|
|
79
117
|
hunks = await rusty_git.compare_refs(repo_path, "main~1", "main")
|
|
80
118
|
for hunk in hunks:
|
|
81
119
|
print(hunk["new_path"], hunk["content"])
|
|
82
120
|
|
|
83
|
-
#
|
|
121
|
+
# 4. 3-Way Merge
|
|
84
122
|
result = await rusty_git.execute_merge(
|
|
85
123
|
repo_path,
|
|
86
124
|
base_ref="main",
|
|
@@ -115,6 +153,14 @@ print("Branches:", branches)
|
|
|
115
153
|
# Read file directly from commit tree
|
|
116
154
|
blob = rusty_git.sync.get_tree_blob("/path/to/repo.git", "HEAD", "pyproject.toml")
|
|
117
155
|
|
|
156
|
+
# Create a commit directly on any branch (new or existing)
|
|
157
|
+
res = rusty_git.sync.create_commit(
|
|
158
|
+
"/path/to/repo.git",
|
|
159
|
+
branch="feature-x",
|
|
160
|
+
files={"hello.txt": b"Hello world!\n"},
|
|
161
|
+
message="feat: add hello.txt",
|
|
162
|
+
)
|
|
163
|
+
|
|
118
164
|
# 3-Way Merge synchronously
|
|
119
165
|
result = rusty_git.sync.execute_merge(
|
|
120
166
|
"/path/to/repo.git",
|
|
@@ -127,10 +173,31 @@ result = rusty_git.sync.execute_merge(
|
|
|
127
173
|
|
|
128
174
|
---
|
|
129
175
|
|
|
176
|
+
## Architectural Note: Forges & Branch Management
|
|
177
|
+
|
|
178
|
+
`rusty-git` is designed for high-performance server-side forges and tools operating on **bare repositories**:
|
|
179
|
+
|
|
180
|
+
* **Creating branches with commits**: Passing any branch name to `create_commit(...)` will create or advance that branch atomically.
|
|
181
|
+
* **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>`.
|
|
182
|
+
* **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.
|
|
183
|
+
|
|
184
|
+
### Engine Architecture: Pure `gix` vs. `git2` Wire Streaming
|
|
185
|
+
|
|
186
|
+
`rusty-git` intentionally separates user-facing operations from network pack streaming:
|
|
187
|
+
|
|
188
|
+
* **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.
|
|
189
|
+
* **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.
|
|
190
|
+
|
|
191
|
+
This keeps `rusty-git` focused entirely on performance-critical operations without unnecessary file-I/O wrapper overhead.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
130
195
|
## Smart-HTTP Protocol Wire Support
|
|
131
196
|
|
|
132
197
|
`rusty-git` can drive Git HTTP and SSH protocol negotiation completely in-process:
|
|
133
198
|
|
|
199
|
+
> **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`.
|
|
200
|
+
|
|
134
201
|
```python
|
|
135
202
|
import rusty_git
|
|
136
203
|
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# rusty-git
|
|
2
|
+
|
|
3
|
+
[](https://gitlab.com/framework-m/rusty-git/-/pipelines)
|
|
4
|
+
[](https://pypi.org/project/rusty-git/)
|
|
5
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
6
|
+
|
|
7
|
+
**High-speed pure Rust Git engine (`gix` + `git2`) compiled via PyO3 with both native `async` (Tokio) and GIL-released `sync` APIs.**
|
|
8
|
+
|
|
9
|
+
`rusty-git` provides in-process Git operations with **zero OS subprocess forks** and **statically-compiled native wheels** (no dynamic `libgit2.so` dependencies required).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install rusty-git
|
|
17
|
+
# or with uv:
|
|
18
|
+
uv add rusty-git
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Quickstart: 100% In-Process Demo (Zero Subprocesses)
|
|
24
|
+
|
|
25
|
+
Copy-paste this into `example.py` to test immediately in any environment:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import asyncio
|
|
29
|
+
import os
|
|
30
|
+
import tempfile
|
|
31
|
+
import rusty_git
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def main():
|
|
35
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
36
|
+
repo_path = os.path.join(tmp_dir, "demo.git")
|
|
37
|
+
|
|
38
|
+
# 1. Initialize a bare repository in-process (ZERO subprocesses)
|
|
39
|
+
await rusty_git.init_bare_repository(repo_path)
|
|
40
|
+
|
|
41
|
+
# 2. Create the initial root commit in-process
|
|
42
|
+
res = await rusty_git.create_commit(
|
|
43
|
+
repo_path,
|
|
44
|
+
branch="main",
|
|
45
|
+
files={
|
|
46
|
+
"README.md": b"# Hello from rusty-git!\n",
|
|
47
|
+
"src/main.py": b"print('Hello World')\n",
|
|
48
|
+
},
|
|
49
|
+
message="Initial commit",
|
|
50
|
+
author_name="Alice",
|
|
51
|
+
author_email="alice@example.com",
|
|
52
|
+
)
|
|
53
|
+
print("Root Commit SHA:", res["commit_sha"])
|
|
54
|
+
|
|
55
|
+
# 3. Inspect commit in-process
|
|
56
|
+
commit = await rusty_git.get_commit(repo_path, "HEAD")
|
|
57
|
+
print(f"Author: {commit['author_name']} <{commit['author_email']}>")
|
|
58
|
+
print(f"Message: {commit['message']}")
|
|
59
|
+
|
|
60
|
+
# 4. Read tree blob without disk checkout
|
|
61
|
+
blob = await rusty_git.get_tree_blob(repo_path, "HEAD", "README.md")
|
|
62
|
+
print("Blob Content:", blob.decode("utf-8").strip())
|
|
63
|
+
|
|
64
|
+
# 5. List branches
|
|
65
|
+
branches = await rusty_git.list_branches(repo_path)
|
|
66
|
+
print("Branches:", branches)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
asyncio.run(main())
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Dual-Color Usage: Async & Sync
|
|
75
|
+
|
|
76
|
+
### 1. Asynchronous API (Default - for Litestar, FastAPI, Granian, Uvicorn)
|
|
77
|
+
|
|
78
|
+
All blocking Git I/O is offloaded to a Tokio multi-threaded worker pool, releasing the Python GIL:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import asyncio
|
|
82
|
+
import rusty_git
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def main():
|
|
86
|
+
repo_path = "/path/to/your/repo"
|
|
87
|
+
|
|
88
|
+
# 1. Inspect commits
|
|
89
|
+
commit = await rusty_git.get_commit(repo_path, "HEAD")
|
|
90
|
+
print(commit["sha"], commit["message"], commit["author_name"])
|
|
91
|
+
|
|
92
|
+
# 2. Read tree blobs without disk checkout
|
|
93
|
+
readme = await rusty_git.get_tree_blob(repo_path, "main", "README.md")
|
|
94
|
+
print(readme.decode("utf-8"))
|
|
95
|
+
|
|
96
|
+
# 3. Fast unified diffs
|
|
97
|
+
hunks = await rusty_git.compare_refs(repo_path, "main~1", "main")
|
|
98
|
+
for hunk in hunks:
|
|
99
|
+
print(hunk["new_path"], hunk["content"])
|
|
100
|
+
|
|
101
|
+
# 4. 3-Way Merge
|
|
102
|
+
result = await rusty_git.execute_merge(
|
|
103
|
+
repo_path,
|
|
104
|
+
base_ref="main",
|
|
105
|
+
head_ref="feature",
|
|
106
|
+
author_name="Automated Merge",
|
|
107
|
+
author_email="bot@example.com",
|
|
108
|
+
)
|
|
109
|
+
if result["status"] == "merged":
|
|
110
|
+
print("Merged Commit SHA:", result["commit_sha"])
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
asyncio.run(main())
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### 2. Synchronous API (`rusty_git.sync` - for CLI tools, Celery, scripts)
|
|
119
|
+
|
|
120
|
+
Direct execution on the calling thread with the Python GIL cleanly released (`py.detach`):
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
import rusty_git
|
|
124
|
+
|
|
125
|
+
# Read commit synchronously
|
|
126
|
+
commit = rusty_git.sync.get_commit("/path/to/repo.git", "HEAD")
|
|
127
|
+
print(commit["sha"], commit["author_name"])
|
|
128
|
+
|
|
129
|
+
# List branches synchronously
|
|
130
|
+
branches = rusty_git.sync.list_branches("/path/to/repo.git")
|
|
131
|
+
print("Branches:", branches)
|
|
132
|
+
|
|
133
|
+
# Read file directly from commit tree
|
|
134
|
+
blob = rusty_git.sync.get_tree_blob("/path/to/repo.git", "HEAD", "pyproject.toml")
|
|
135
|
+
|
|
136
|
+
# Create a commit directly on any branch (new or existing)
|
|
137
|
+
res = rusty_git.sync.create_commit(
|
|
138
|
+
"/path/to/repo.git",
|
|
139
|
+
branch="feature-x",
|
|
140
|
+
files={"hello.txt": b"Hello world!\n"},
|
|
141
|
+
message="feat: add hello.txt",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# 3-Way Merge synchronously
|
|
145
|
+
result = rusty_git.sync.execute_merge(
|
|
146
|
+
"/path/to/repo.git",
|
|
147
|
+
"main",
|
|
148
|
+
"feature",
|
|
149
|
+
author_name="Merge Bot",
|
|
150
|
+
author_email="bot@example.com",
|
|
151
|
+
)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Architectural Note: Forges & Branch Management
|
|
157
|
+
|
|
158
|
+
`rusty-git` is designed for high-performance server-side forges and tools operating on **bare repositories**:
|
|
159
|
+
|
|
160
|
+
* **Creating branches with commits**: Passing any branch name to `create_commit(...)` will create or advance that branch atomically.
|
|
161
|
+
* **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>`.
|
|
162
|
+
* **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.
|
|
163
|
+
|
|
164
|
+
### Engine Architecture: Pure `gix` vs. `git2` Wire Streaming
|
|
165
|
+
|
|
166
|
+
`rusty-git` intentionally separates user-facing operations from network pack streaming:
|
|
167
|
+
|
|
168
|
+
* **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.
|
|
169
|
+
* **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.
|
|
170
|
+
|
|
171
|
+
This keeps `rusty-git` focused entirely on performance-critical operations without unnecessary file-I/O wrapper overhead.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Smart-HTTP Protocol Wire Support
|
|
176
|
+
|
|
177
|
+
`rusty-git` can drive Git HTTP and SSH protocol negotiation completely in-process:
|
|
178
|
+
|
|
179
|
+
> **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`.
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
import rusty_git
|
|
183
|
+
|
|
184
|
+
# Ref advertisement for git-upload-pack (git clone / fetch)
|
|
185
|
+
advert = await rusty_git.advertise("/srv/repos/myorg/backend.git", "git-upload-pack")
|
|
186
|
+
|
|
187
|
+
# Handle pack negotiation and streaming
|
|
188
|
+
upload_res = await rusty_git.upload_pack("/srv/repos/myorg/backend.git", request_body)
|
|
189
|
+
|
|
190
|
+
# Ingest inbound git push packfile atomically
|
|
191
|
+
push_res = await rusty_git.receive_pack("/srv/repos/myorg/backend.git", push_body)
|
|
192
|
+
print("Updated Refs:", push_res["updated_refs"])
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Development & Testing
|
|
198
|
+
|
|
199
|
+
### Rust Tests
|
|
200
|
+
```bash
|
|
201
|
+
cargo test
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Python Integration Tests
|
|
205
|
+
```bash
|
|
206
|
+
uv run pytest
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
Apache-2.0 © Framework M Contributors
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## rusty-git v0.3.0
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- replace git2 with pure gix across engine and unlock pyodide support (50d97c7)
|
|
6
|
+
- implement create_commit with optimistic concurrency and update quickstart (2f963c9)
|
|
7
|
+
- enable windows amd64 wheel build via cargo-zigbuild (0485858)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- pyodide build (0b26a3e)
|
|
12
|
+
|
|
1
13
|
## rusty-git v0.2.1
|
|
2
14
|
|
|
3
15
|
### Bug Fixes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "rusty-git"
|
|
3
3
|
description = "High-speed pure Rust Git engine (gix + git2) exposed to Python via PyO3"
|
|
4
|
-
version = "0.
|
|
4
|
+
version = "0.3.0"
|
|
5
5
|
edition = "2021"
|
|
6
6
|
rust-version = "1.80"
|
|
7
7
|
license = "Apache-2.0"
|
|
@@ -18,7 +18,7 @@ crate-type = ["cdylib", "rlib"]
|
|
|
18
18
|
|
|
19
19
|
[dependencies]
|
|
20
20
|
pyo3 = { version = "0.30", features = ["extension-module", "abi3-py312"] }
|
|
21
|
-
gix = "0.90"
|
|
21
|
+
gix = { version = "0.90", features = ["merge", "blob-diff", "revision"] }
|
|
22
22
|
thiserror = "2"
|
|
23
23
|
sha1_smol = "1"
|
|
24
24
|
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# rusty-git
|
|
2
|
+
|
|
3
|
+
[](https://gitlab.com/framework-m/rusty-git/-/pipelines)
|
|
4
|
+
[](https://pypi.org/project/rusty-git/)
|
|
5
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
6
|
+
|
|
7
|
+
**High-speed pure Rust Git engine (`gix` + `git2`) compiled via PyO3 with both native `async` (Tokio) and GIL-released `sync` APIs.**
|
|
8
|
+
|
|
9
|
+
`rusty-git` provides in-process Git operations with **zero OS subprocess forks** and **statically-compiled native wheels** (no dynamic `libgit2.so` dependencies required).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install rusty-git
|
|
17
|
+
# or with uv:
|
|
18
|
+
uv add rusty-git
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Quickstart: 100% In-Process Demo (Zero Subprocesses)
|
|
24
|
+
|
|
25
|
+
Copy-paste this into `example.py` to test immediately in any environment:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import asyncio
|
|
29
|
+
import os
|
|
30
|
+
import tempfile
|
|
31
|
+
import rusty_git
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def main():
|
|
35
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
36
|
+
repo_path = os.path.join(tmp_dir, "demo.git")
|
|
37
|
+
|
|
38
|
+
# 1. Initialize a bare repository in-process (ZERO subprocesses)
|
|
39
|
+
await rusty_git.init_bare_repository(repo_path)
|
|
40
|
+
|
|
41
|
+
# 2. Create the initial root commit in-process
|
|
42
|
+
res = await rusty_git.create_commit(
|
|
43
|
+
repo_path,
|
|
44
|
+
branch="main",
|
|
45
|
+
files={
|
|
46
|
+
"README.md": b"# Hello from rusty-git!\n",
|
|
47
|
+
"src/main.py": b"print('Hello World')\n",
|
|
48
|
+
},
|
|
49
|
+
message="Initial commit",
|
|
50
|
+
author_name="Alice",
|
|
51
|
+
author_email="alice@example.com",
|
|
52
|
+
)
|
|
53
|
+
print("Root Commit SHA:", res["commit_sha"])
|
|
54
|
+
|
|
55
|
+
# 3. Inspect commit in-process
|
|
56
|
+
commit = await rusty_git.get_commit(repo_path, "HEAD")
|
|
57
|
+
print(f"Author: {commit['author_name']} <{commit['author_email']}>")
|
|
58
|
+
print(f"Message: {commit['message']}")
|
|
59
|
+
|
|
60
|
+
# 4. Read tree blob without disk checkout
|
|
61
|
+
blob = await rusty_git.get_tree_blob(repo_path, "HEAD", "README.md")
|
|
62
|
+
print("Blob Content:", blob.decode("utf-8").strip())
|
|
63
|
+
|
|
64
|
+
# 5. List branches
|
|
65
|
+
branches = await rusty_git.list_branches(repo_path)
|
|
66
|
+
print("Branches:", branches)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
asyncio.run(main())
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Dual-Color Usage: Async & Sync
|
|
75
|
+
|
|
76
|
+
### 1. Asynchronous API (Default - for Litestar, FastAPI, Granian, Uvicorn)
|
|
77
|
+
|
|
78
|
+
All blocking Git I/O is offloaded to a Tokio multi-threaded worker pool, releasing the Python GIL:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import asyncio
|
|
82
|
+
import rusty_git
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def main():
|
|
86
|
+
repo_path = "/path/to/your/repo"
|
|
87
|
+
|
|
88
|
+
# 1. Inspect commits
|
|
89
|
+
commit = await rusty_git.get_commit(repo_path, "HEAD")
|
|
90
|
+
print(commit["sha"], commit["message"], commit["author_name"])
|
|
91
|
+
|
|
92
|
+
# 2. Read tree blobs without disk checkout
|
|
93
|
+
readme = await rusty_git.get_tree_blob(repo_path, "main", "README.md")
|
|
94
|
+
print(readme.decode("utf-8"))
|
|
95
|
+
|
|
96
|
+
# 3. Fast unified diffs
|
|
97
|
+
hunks = await rusty_git.compare_refs(repo_path, "main~1", "main")
|
|
98
|
+
for hunk in hunks:
|
|
99
|
+
print(hunk["new_path"], hunk["content"])
|
|
100
|
+
|
|
101
|
+
# 4. 3-Way Merge
|
|
102
|
+
result = await rusty_git.execute_merge(
|
|
103
|
+
repo_path,
|
|
104
|
+
base_ref="main",
|
|
105
|
+
head_ref="feature",
|
|
106
|
+
author_name="Automated Merge",
|
|
107
|
+
author_email="bot@example.com",
|
|
108
|
+
)
|
|
109
|
+
if result["status"] == "merged":
|
|
110
|
+
print("Merged Commit SHA:", result["commit_sha"])
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
asyncio.run(main())
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### 2. Synchronous API (`rusty_git.sync` - for CLI tools, Celery, scripts)
|
|
119
|
+
|
|
120
|
+
Direct execution on the calling thread with the Python GIL cleanly released (`py.detach`):
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
import rusty_git
|
|
124
|
+
|
|
125
|
+
# Read commit synchronously
|
|
126
|
+
commit = rusty_git.sync.get_commit("/path/to/repo.git", "HEAD")
|
|
127
|
+
print(commit["sha"], commit["author_name"])
|
|
128
|
+
|
|
129
|
+
# List branches synchronously
|
|
130
|
+
branches = rusty_git.sync.list_branches("/path/to/repo.git")
|
|
131
|
+
print("Branches:", branches)
|
|
132
|
+
|
|
133
|
+
# Read file directly from commit tree
|
|
134
|
+
blob = rusty_git.sync.get_tree_blob("/path/to/repo.git", "HEAD", "pyproject.toml")
|
|
135
|
+
|
|
136
|
+
# Create a commit directly on any branch (new or existing)
|
|
137
|
+
res = rusty_git.sync.create_commit(
|
|
138
|
+
"/path/to/repo.git",
|
|
139
|
+
branch="feature-x",
|
|
140
|
+
files={"hello.txt": b"Hello world!\n"},
|
|
141
|
+
message="feat: add hello.txt",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# 3-Way Merge synchronously
|
|
145
|
+
result = rusty_git.sync.execute_merge(
|
|
146
|
+
"/path/to/repo.git",
|
|
147
|
+
"main",
|
|
148
|
+
"feature",
|
|
149
|
+
author_name="Merge Bot",
|
|
150
|
+
author_email="bot@example.com",
|
|
151
|
+
)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Architectural Note: Forges & Branch Management
|
|
157
|
+
|
|
158
|
+
`rusty-git` is designed for high-performance server-side forges and tools operating on **bare repositories**:
|
|
159
|
+
|
|
160
|
+
* **Creating branches with commits**: Passing any branch name to `create_commit(...)` will create or advance that branch atomically.
|
|
161
|
+
* **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>`.
|
|
162
|
+
* **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.
|
|
163
|
+
|
|
164
|
+
### Engine Architecture: Pure `gix` vs. `git2` Wire Streaming
|
|
165
|
+
|
|
166
|
+
`rusty-git` intentionally separates user-facing operations from network pack streaming:
|
|
167
|
+
|
|
168
|
+
* **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.
|
|
169
|
+
* **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.
|
|
170
|
+
|
|
171
|
+
This keeps `rusty-git` focused entirely on performance-critical operations without unnecessary file-I/O wrapper overhead.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Smart-HTTP Protocol Wire Support
|
|
176
|
+
|
|
177
|
+
`rusty-git` can drive Git HTTP and SSH protocol negotiation completely in-process:
|
|
178
|
+
|
|
179
|
+
> **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`.
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
import rusty_git
|
|
183
|
+
|
|
184
|
+
# Ref advertisement for git-upload-pack (git clone / fetch)
|
|
185
|
+
advert = await rusty_git.advertise("/srv/repos/myorg/backend.git", "git-upload-pack")
|
|
186
|
+
|
|
187
|
+
# Handle pack negotiation and streaming
|
|
188
|
+
upload_res = await rusty_git.upload_pack("/srv/repos/myorg/backend.git", request_body)
|
|
189
|
+
|
|
190
|
+
# Ingest inbound git push packfile atomically
|
|
191
|
+
push_res = await rusty_git.receive_pack("/srv/repos/myorg/backend.git", push_body)
|
|
192
|
+
print("Updated Refs:", push_res["updated_refs"])
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Development & Testing
|
|
198
|
+
|
|
199
|
+
### Rust Tests
|
|
200
|
+
```bash
|
|
201
|
+
cargo test
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Python Integration Tests
|
|
205
|
+
```bash
|
|
206
|
+
uv run pytest
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
Apache-2.0 © Framework M Contributors
|