diffron 0.1.0__py3-none-any.whl
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.
- diffron/__init__.py +26 -0
- diffron/__init__.pyi +28 -0
- diffron/cli.py +347 -0
- diffron/cli.pyi +30 -0
- diffron/client.py +175 -0
- diffron/client.pyi +80 -0
- diffron/commit_gen.py +151 -0
- diffron/commit_gen.pyi +41 -0
- diffron/git_hooks.py +292 -0
- diffron/git_hooks.pyi +52 -0
- diffron/lemonade.py +192 -0
- diffron/lemonade.pyi +66 -0
- diffron/pr_gen.py +224 -0
- diffron/pr_gen.pyi +56 -0
- diffron/py.typed +0 -0
- diffron/utils.py +236 -0
- diffron/utils.pyi +58 -0
- diffron-0.1.0.dist-info/METADATA +373 -0
- diffron-0.1.0.dist-info/RECORD +28 -0
- diffron-0.1.0.dist-info/WHEEL +5 -0
- diffron-0.1.0.dist-info/entry_points.txt +5 -0
- diffron-0.1.0.dist-info/licenses/LICENSE +21 -0
- diffron-0.1.0.dist-info/top_level.txt +2 -0
- docs/HOOKS.md +442 -0
- docs/PLAN.md +378 -0
- docs/PYPI_RELEASE.md +487 -0
- docs/SETUP.md +524 -0
- docs/USAGE.md +515 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Diffron Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
docs/HOOKS.md
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
# Diffron Git Hooks Architecture
|
|
2
|
+
|
|
3
|
+
Detailed technical documentation on how Diffron Git hooks work on Windows.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [Overview](#overview)
|
|
10
|
+
2. [Hook Types](#hook-types)
|
|
11
|
+
3. [Installation Methods](#installation-methods)
|
|
12
|
+
4. [Hook Execution Flow](#hook-execution-flow)
|
|
13
|
+
5. [File Structure](#file-structure)
|
|
14
|
+
6. [Windows-Specific Implementation](#windows-specific-implementation)
|
|
15
|
+
7. [Security Considerations](#security-considerations)
|
|
16
|
+
8. [Customization](#customization)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Overview
|
|
21
|
+
|
|
22
|
+
Diffron uses Git's `prepare-commit-msg` hook to automatically generate commit messages using AI. The hook runs **before** the commit message editor opens, allowing it to pre-populate the message.
|
|
23
|
+
|
|
24
|
+
### Key Features
|
|
25
|
+
|
|
26
|
+
- **Automatic:** Runs on every `git commit`
|
|
27
|
+
- **Non-blocking:** Commits still work if Lemonade is unavailable
|
|
28
|
+
- **Smart:** Skips merges, rebases, and amends
|
|
29
|
+
- **Windows-native:** Designed for GitHub Desktop 3.5.5+
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Hook Types
|
|
34
|
+
|
|
35
|
+
### prepare-commit-msg Hook
|
|
36
|
+
|
|
37
|
+
**When it runs:** After Git prepares the default commit message, but before the editor starts.
|
|
38
|
+
|
|
39
|
+
**Purpose:** Modify or replace the default commit message.
|
|
40
|
+
|
|
41
|
+
**Arguments:**
|
|
42
|
+
1. `$1` - Path to file containing the commit message
|
|
43
|
+
2. `$2` - Source of the commit message (`message`, `template`, `merge`, `squash`, `commit`)
|
|
44
|
+
3. `$3` - (Optional) Commit object name (for `commit` source)
|
|
45
|
+
|
|
46
|
+
**Diffron behavior:**
|
|
47
|
+
- Reads staged diff
|
|
48
|
+
- Calls Lemonade API
|
|
49
|
+
- Writes generated message to file
|
|
50
|
+
- Git opens editor with generated message
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Installation Methods
|
|
55
|
+
|
|
56
|
+
### Global Installation
|
|
57
|
+
|
|
58
|
+
**Location:** `C:\Users\YourName\.diffron-hooks\`
|
|
59
|
+
|
|
60
|
+
**Git config:**
|
|
61
|
+
```bash
|
|
62
|
+
git config --global core.hooksPath "C:/Users/YourName/.diffron-hooks"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Applies to:** All repositories on the system.
|
|
66
|
+
|
|
67
|
+
**Pros:**
|
|
68
|
+
- Install once, works everywhere
|
|
69
|
+
- Consistent behavior across projects
|
|
70
|
+
|
|
71
|
+
**Cons:**
|
|
72
|
+
- Affects all repositories
|
|
73
|
+
- Requires global Git configuration
|
|
74
|
+
|
|
75
|
+
### Per-Repository Installation
|
|
76
|
+
|
|
77
|
+
**Location:** `.git\hooks\`
|
|
78
|
+
|
|
79
|
+
**Git config:** None required (Git checks `.git/hooks` by default)
|
|
80
|
+
|
|
81
|
+
**Applies to:** Single repository only.
|
|
82
|
+
|
|
83
|
+
**Pros:**
|
|
84
|
+
- Repository-specific configuration
|
|
85
|
+
- No global changes
|
|
86
|
+
|
|
87
|
+
**Cons:**
|
|
88
|
+
- Must install for each repository
|
|
89
|
+
- Easy to forget when cloning
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Hook Execution Flow
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
97
|
+
│ USER ACTION │
|
|
98
|
+
│ git commit -m "anything" (or GitHub Desktop) │
|
|
99
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
100
|
+
│
|
|
101
|
+
▼
|
|
102
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
103
|
+
│ GIT PREPARES COMMIT │
|
|
104
|
+
│ - Validates staged changes │
|
|
105
|
+
│ - Creates .git/COMMIT_EDITMSG with default message │
|
|
106
|
+
│ - Determines commit source (message/template/merge) │
|
|
107
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
108
|
+
│
|
|
109
|
+
▼
|
|
110
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
111
|
+
│ GIT EXECUTES prepare-commit-msg HOOK │
|
|
112
|
+
│ - Calls: .diffron-hooks/prepare-commit-msg │
|
|
113
|
+
│ - Passes: COMMIT_EDITMSG path, commit source │
|
|
114
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
115
|
+
│
|
|
116
|
+
▼
|
|
117
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
118
|
+
│ SHELL WRAPPER (prepare-commit-msg) │
|
|
119
|
+
│ #!/C:/Program Files/Git/usr/bin/sh.exe │
|
|
120
|
+
│ HOOK_DIR="$(cd "$(dirname "$0")" && pwd)" │
|
|
121
|
+
│ python "$HOOK_DIR/prepare-commit-msg.py" "$1" "$2" │
|
|
122
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
123
|
+
│
|
|
124
|
+
▼
|
|
125
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
126
|
+
│ PYTHON HOOK (prepare-commit-msg.py) │
|
|
127
|
+
│ 1. Parse arguments (msg_file, commit_source) │
|
|
128
|
+
│ 2. Check if should skip (merge/squash/amend) │
|
|
129
|
+
│ 3. Check if Lemonade is running │
|
|
130
|
+
│ 4. Get staged diff (git diff --cached) │
|
|
131
|
+
│ 5. Call Lemonade API for message generation │
|
|
132
|
+
│ 6. Write generated message to msg_file │
|
|
133
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
134
|
+
│
|
|
135
|
+
▼
|
|
136
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
137
|
+
│ GIT OPENS EDITOR │
|
|
138
|
+
│ - Opens COMMIT_EDITMSG with generated content │
|
|
139
|
+
│ - User can review/modify before saving │
|
|
140
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
141
|
+
│
|
|
142
|
+
▼
|
|
143
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
144
|
+
│ COMMIT COMPLETED │
|
|
145
|
+
│ - Git creates commit with final message │
|
|
146
|
+
│ - Hook execution complete │
|
|
147
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## File Structure
|
|
153
|
+
|
|
154
|
+
### Global Hooks Directory
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
C:\Users\YourName\.diffron-hooks\
|
|
158
|
+
├── prepare-commit-msg # Shell wrapper (no extension)
|
|
159
|
+
└── prepare-commit-msg.py # Python hook script
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### File Contents
|
|
163
|
+
|
|
164
|
+
#### prepare-commit-msg (Shell Wrapper)
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
#!/C:/Program Files/Git/usr/bin/sh.exe
|
|
168
|
+
# Diffron prepare-commit-msg hook wrapper
|
|
169
|
+
|
|
170
|
+
# Get the directory where this wrapper script is located
|
|
171
|
+
HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
172
|
+
python "$HOOK_DIR/prepare-commit-msg.py" "$1" "$2"
|
|
173
|
+
exit 0
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Purpose:** Git for Windows executes this shell script, which delegates to Python.
|
|
177
|
+
|
|
178
|
+
**Why a wrapper?**
|
|
179
|
+
- Git for Windows uses bundled Git Bash
|
|
180
|
+
- Python may not be in Git Bash PATH
|
|
181
|
+
- Wrapper ensures correct Python interpreter is used
|
|
182
|
+
|
|
183
|
+
#### prepare-commit-msg.py (Python Hook)
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
#!/usr/bin/env python
|
|
187
|
+
"""Diffron prepare-commit-msg Git hook."""
|
|
188
|
+
|
|
189
|
+
import sys
|
|
190
|
+
import os
|
|
191
|
+
from diffron.commit_gen import generate_commit_message
|
|
192
|
+
from diffron.lemonade import is_lemonade_running
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def main():
|
|
196
|
+
# Parse arguments
|
|
197
|
+
if len(sys.argv) < 2:
|
|
198
|
+
sys.exit(1)
|
|
199
|
+
|
|
200
|
+
commit_msg_file = sys.argv[1]
|
|
201
|
+
commit_source = sys.argv[2] if len(sys.argv) > 2 else ""
|
|
202
|
+
|
|
203
|
+
# Skip for merges, rebases, amends
|
|
204
|
+
skip_sources = {"merge", "squash", "commit"}
|
|
205
|
+
if commit_source in skip_sources:
|
|
206
|
+
sys.exit(0)
|
|
207
|
+
|
|
208
|
+
# Check if Lemonade is running
|
|
209
|
+
if not is_lemonade_running():
|
|
210
|
+
sys.exit(0) # Silent skip
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
# Generate commit message
|
|
214
|
+
commit_message = generate_commit_message()
|
|
215
|
+
|
|
216
|
+
if commit_message:
|
|
217
|
+
# Write to commit message file
|
|
218
|
+
with open(commit_msg_file, "w", encoding="utf-8") as f:
|
|
219
|
+
f.write(commit_message)
|
|
220
|
+
except Exception as e:
|
|
221
|
+
# Log error but don't block commit
|
|
222
|
+
error_msg = f"# Diffron error: {e}\n"
|
|
223
|
+
try:
|
|
224
|
+
with open(commit_msg_file, "a", encoding="utf-8") as f:
|
|
225
|
+
f.write(error_msg)
|
|
226
|
+
except Exception:
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
sys.exit(0)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
if __name__ == "__main__":
|
|
233
|
+
main()
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Windows-Specific Implementation
|
|
239
|
+
|
|
240
|
+
### GitHub Desktop 3.5.5+ Hooks Support
|
|
241
|
+
|
|
242
|
+
Before version 3.5.5, GitHub Desktop on Windows had issues with Git hooks:
|
|
243
|
+
|
|
244
|
+
| Issue | Status in 3.5.5+ |
|
|
245
|
+
|-------|------------------|
|
|
246
|
+
| PATH not inherited | ✅ Fixed |
|
|
247
|
+
| Shell interpreter missing | ✅ Fixed |
|
|
248
|
+
| Hooks path ignored | ✅ Fixed |
|
|
249
|
+
|
|
250
|
+
### Why This Implementation Works
|
|
251
|
+
|
|
252
|
+
1. **Shell Wrapper:** Uses Git Bash's `sh.exe` interpreter
|
|
253
|
+
- Path: `C:/Program Files/Git/usr/bin/sh.exe`
|
|
254
|
+
- Bundled with Git for Windows
|
|
255
|
+
|
|
256
|
+
2. **Python Path:** Wrapper finds Python from system PATH
|
|
257
|
+
- No hardcoded Python path
|
|
258
|
+
- Works with any Python installation
|
|
259
|
+
|
|
260
|
+
3. **Absolute Paths:** Hook directory resolved dynamically
|
|
261
|
+
```bash
|
|
262
|
+
HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
4. **UTF-8 Encoding:** All file I/O uses UTF-8
|
|
266
|
+
- Windows default is often CP1252
|
|
267
|
+
- Explicit encoding prevents issues
|
|
268
|
+
|
|
269
|
+
### Git Configuration
|
|
270
|
+
|
|
271
|
+
**Check current hooks path:**
|
|
272
|
+
```bash
|
|
273
|
+
git config --global core.hooksPath
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Set hooks path manually:**
|
|
277
|
+
```bash
|
|
278
|
+
git config --global core.hooksPath "C:/Users/YourName/.diffron-hooks"
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Verify Git can find hooks:**
|
|
282
|
+
```bash
|
|
283
|
+
git config core.hooksPath
|
|
284
|
+
dir "%USERPROFILE%\.diffron-hooks"
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Security Considerations
|
|
290
|
+
|
|
291
|
+
### Hook Permissions
|
|
292
|
+
|
|
293
|
+
On Windows, file permissions are less restrictive than Unix. However:
|
|
294
|
+
|
|
295
|
+
- Hooks should only be writable by the user
|
|
296
|
+
- Avoid running Git as Administrator
|
|
297
|
+
- Keep `.diffron-hooks` in user directory
|
|
298
|
+
|
|
299
|
+
### Code Execution
|
|
300
|
+
|
|
301
|
+
Git hooks execute arbitrary code. Security measures:
|
|
302
|
+
|
|
303
|
+
1. **Diffron hooks are open source** - code is auditable
|
|
304
|
+
2. **No network calls except to localhost** - Lemonade runs locally
|
|
305
|
+
3. **No sensitive data transmitted** - only code diffs
|
|
306
|
+
|
|
307
|
+
### Supply Chain
|
|
308
|
+
|
|
309
|
+
When installing from PyPI:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
pip install diffron
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The package is verified by PyPI's security measures. For maximum security, install from source:
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
git clone https://github.com/diffron/diffron.git
|
|
319
|
+
cd diffron
|
|
320
|
+
pip install -e .
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Customization
|
|
326
|
+
|
|
327
|
+
### Modify Hook Behavior
|
|
328
|
+
|
|
329
|
+
Edit `.diffron-hooks/prepare-commit-msg.py`:
|
|
330
|
+
|
|
331
|
+
```python
|
|
332
|
+
# Change max diff size
|
|
333
|
+
from diffron.commit_gen import generate_commit_message
|
|
334
|
+
msg = generate_commit_message(max_chars=8000) # Default: 4000
|
|
335
|
+
|
|
336
|
+
# Change temperature (creativity)
|
|
337
|
+
from diffron.lemonade import LemonadeClient
|
|
338
|
+
client = LemonadeClient()
|
|
339
|
+
msg = client.generate_commit_message(temperature=0.5) # Default: 0.2
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Add Custom Logic
|
|
343
|
+
|
|
344
|
+
```python
|
|
345
|
+
# Add timestamp comment
|
|
346
|
+
with open(commit_msg_file, "a", encoding="utf-8") as f:
|
|
347
|
+
f.write(f"\n# Generated by Diffron at {datetime.now()}")
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Disable for Specific Repositories
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
# In specific repository
|
|
354
|
+
cd path/to/repo
|
|
355
|
+
git config --unset core.hooksPath
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Use Different Model Per Repository
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
# In specific repository
|
|
362
|
+
cd path/to/repo
|
|
363
|
+
git config diffron.model "your-model-name"
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Then modify hook to read this config:
|
|
367
|
+
|
|
368
|
+
```python
|
|
369
|
+
import subprocess
|
|
370
|
+
result = subprocess.run(
|
|
371
|
+
["git", "config", "diffron.model"],
|
|
372
|
+
capture_output=True,
|
|
373
|
+
text=True
|
|
374
|
+
)
|
|
375
|
+
model = result.stdout.strip() or "qwen2.5-it-3b-FLM"
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Debugging Hooks
|
|
381
|
+
|
|
382
|
+
### Enable Verbose Output
|
|
383
|
+
|
|
384
|
+
Add to `prepare-commit-msg.py`:
|
|
385
|
+
|
|
386
|
+
```python
|
|
387
|
+
import sys
|
|
388
|
+
|
|
389
|
+
# Log to file for debugging
|
|
390
|
+
with open("C:/temp/diffron-hook.log", "a", encoding="utf-8") as log:
|
|
391
|
+
log.write(f"Arguments: {sys.argv}\n")
|
|
392
|
+
log.write(f"Commit source: {commit_source}\n")
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Test Hook Manually
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
# Create test commit message file
|
|
399
|
+
echo "test message" > C:\temp\test-commit-msg.txt
|
|
400
|
+
|
|
401
|
+
# Run hook manually
|
|
402
|
+
python C:\Users\YourName\.diffron-hooks\prepare-commit-msg.py C:\temp\test-commit-msg.txt message
|
|
403
|
+
|
|
404
|
+
# Check result
|
|
405
|
+
type C:\temp\test-commit-msg.txt
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Check Git Hook Execution
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
# Enable Git trace
|
|
412
|
+
export GIT_TRACE=1
|
|
413
|
+
git commit -m "test"
|
|
414
|
+
|
|
415
|
+
# Look for hook execution in output
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## Hook Lifecycle
|
|
421
|
+
|
|
422
|
+
### Installation
|
|
423
|
+
1. User runs `install_hooks(global_install=True)`
|
|
424
|
+
2. Files copied to `.diffron-hooks`
|
|
425
|
+
3. Git config set to hooks path
|
|
426
|
+
|
|
427
|
+
### Execution (per commit)
|
|
428
|
+
1. Git detects `prepare-commit-msg` hook
|
|
429
|
+
2. Shell wrapper executes
|
|
430
|
+
3. Python script runs
|
|
431
|
+
4. Message generated and written
|
|
432
|
+
5. Git opens editor
|
|
433
|
+
|
|
434
|
+
### Uninstallation
|
|
435
|
+
1. User runs `uninstall_hooks(global_install=True)`
|
|
436
|
+
2. Git config unset
|
|
437
|
+
3. `.diffron-hooks` directory removed
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
*Last updated: 2026-03-28*
|
|
442
|
+
*Version: 0.1.0*
|