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.
Files changed (35) hide show
  1. {rusty_git-0.2.1 → rusty_git-0.3.0}/Cargo.lock +1 -1
  2. {rusty_git-0.2.1 → rusty_git-0.3.0}/Cargo.toml +1 -1
  3. {rusty_git-0.2.1 → rusty_git-0.3.0}/PKG-INFO +87 -20
  4. rusty_git-0.3.0/README.md +213 -0
  5. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/CHANGELOG.md +12 -0
  6. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/Cargo.toml +2 -2
  7. rusty_git-0.3.0/crates/rusty-git/README.md +213 -0
  8. rusty_git-0.3.0/crates/rusty-git/src/commit.rs +280 -0
  9. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/inspect.rs +129 -112
  10. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/lib.rs +22 -19
  11. rusty_git-0.3.0/crates/rusty-git/src/merge.rs +215 -0
  12. rusty_git-0.3.0/crates/rusty-git/tests/commit_test.rs +183 -0
  13. rusty_git-0.3.0/crates/rusty-git/tests/python_commit_test.py +122 -0
  14. {rusty_git-0.2.1 → rusty_git-0.3.0}/pyproject.toml +2 -2
  15. {rusty_git-0.2.1 → rusty_git-0.3.0}/python/rusty_git/__init__.py +2 -0
  16. {rusty_git-0.2.1 → rusty_git-0.3.0}/python/rusty_git/_rusty_git.pyi +34 -0
  17. rusty_git-0.2.1/README.md +0 -146
  18. rusty_git-0.2.1/crates/rusty-git/README.md +0 -146
  19. rusty_git-0.2.1/crates/rusty-git/src/merge.rs +0 -211
  20. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/error.rs +0 -0
  21. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/graph.rs +0 -0
  22. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/pack.rs +0 -0
  23. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/pktline.rs +0 -0
  24. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/protocol.rs +0 -0
  25. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/receive.rs +0 -0
  26. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/src/upload.rs +0 -0
  27. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/common/mod.rs +0 -0
  28. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/inspect_test.rs +0 -0
  29. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/merge_test.rs +0 -0
  30. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/protocol_test.rs +0 -0
  31. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/python_protocol_test.py +0 -0
  32. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/python_smoke_test.py +0 -0
  33. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/python_sync_test.py +0 -0
  34. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/tests/roundtrip_test.rs +0 -0
  35. {rusty_git-0.2.1 → rusty_git-0.3.0}/crates/rusty-git/uv.lock +0 -0
@@ -1851,7 +1851,7 @@ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
1851
1851
 
1852
1852
  [[package]]
1853
1853
  name = "rusty-git"
1854
- version = "0.2.1"
1854
+ version = "0.3.0"
1855
1855
  dependencies = [
1856
1856
  "git2",
1857
1857
  "gix",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/rusty-git"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.2.1"
6
+ version = "0.3.0"
7
7
  edition = "2021"
8
8
  rust-version = "1.80"
9
9
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rusty-git
3
- Version: 0.2.1
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@framework-m.com>
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
- # 1. Initialize a bare repository
68
- repo_path = await rusty_git.init_bare_repository("/srv/repos/myorg/backend.git")
106
+ repo_path = "/path/to/your/repo"
69
107
 
70
- # 2. Inspect commits
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
- # 3. Read tree blobs without disk checkout
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
- # 4. Fast unified diffs
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
- # 5. 3-Way Merge
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
+ [![GitLab CI](https://gitlab.com/framework-m/rusty-git/badges/main/pipeline.svg)](https://gitlab.com/framework-m/rusty-git/-/pipelines)
4
+ [![PyPI version](https://img.shields.io/pypi/v/rusty-git.svg)](https://pypi.org/project/rusty-git/)
5
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](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.2.1"
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
+ [![GitLab CI](https://gitlab.com/framework-m/rusty-git/badges/main/pipeline.svg)](https://gitlab.com/framework-m/rusty-git/-/pipelines)
4
+ [![PyPI version](https://img.shields.io/pypi/v/rusty-git.svg)](https://pypi.org/project/rusty-git/)
5
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](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