@vavasilva/git-commit-ai 0.2.3 → 0.3.0
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.
- package/README.md +359 -7
- package/dist/chunk-5MPJCPJ4.js +225 -0
- package/dist/chunk-5MPJCPJ4.js.map +1 -0
- package/dist/git-F4ZHBA3B.js +36 -0
- package/dist/git-F4ZHBA3B.js.map +1 -0
- package/dist/index.js +753 -137
- package/dist/index.js.map +1 -1
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# git-commit-ai
|
|
2
2
|
|
|
3
|
-
Generate commit messages using
|
|
3
|
+
Generate commit messages using LLMs (Ollama, OpenAI, Anthropic, Groq, llama.cpp).
|
|
4
4
|
|
|
5
|
-
A CLI tool that analyzes your staged changes and generates [Karma-style](https://karma-runner.github.io/6.4/dev/git-commit-msg.html) commit messages using
|
|
5
|
+
A CLI tool that analyzes your staged changes and generates [Karma-style](https://karma-runner.github.io/6.4/dev/git-commit-msg.html) commit messages using AI.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **
|
|
9
|
+
- **Multiple Backends** - Ollama (local), llama.cpp (local), OpenAI, Anthropic Claude, Groq
|
|
10
|
+
- **Auto-Detection** - Automatically selects available backend
|
|
10
11
|
- **Karma Convention** - Generates `type(scope): subject` format commits
|
|
11
12
|
- **Interactive Flow** - Confirm, Edit, Regenerate, or Abort before committing
|
|
12
13
|
- **Individual Commits** - Option to commit each file separately
|
|
14
|
+
- **Dry Run** - Preview messages without committing
|
|
13
15
|
- **Git Hook** - Auto-generate messages on `git commit`
|
|
14
16
|
- **Summarize** - Preview changes in plain English before committing
|
|
15
17
|
- **Debug Mode** - Troubleshoot LLM responses
|
|
@@ -19,14 +21,233 @@ A CLI tool that analyzes your staged changes and generates [Karma-style](https:/
|
|
|
19
21
|
|
|
20
22
|
```bash
|
|
21
23
|
# Requires Node.js 20+
|
|
22
|
-
npm install -g git-commit-ai
|
|
24
|
+
npm install -g @vavasilva/git-commit-ai
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Backend Setup
|
|
28
|
+
|
|
29
|
+
Choose at least one backend:
|
|
23
30
|
|
|
24
|
-
|
|
31
|
+
**Ollama (Local, Free)**
|
|
32
|
+
```bash
|
|
33
|
+
# macOS
|
|
25
34
|
brew install ollama
|
|
26
35
|
brew services start ollama
|
|
36
|
+
|
|
37
|
+
# Linux
|
|
38
|
+
curl -fsSL https://ollama.com/install.sh | sh
|
|
39
|
+
sudo systemctl start ollama
|
|
40
|
+
|
|
41
|
+
# Windows - download installer from:
|
|
42
|
+
# https://ollama.com/download/windows
|
|
43
|
+
|
|
44
|
+
# Pull a model (all platforms)
|
|
27
45
|
ollama pull llama3.1:8b
|
|
28
46
|
```
|
|
29
47
|
|
|
48
|
+
**llama.cpp (Local, Free, Low Memory)**
|
|
49
|
+
|
|
50
|
+
Run local GGUF models with `llama-server` (auto-detected on port 8080):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Install llama.cpp
|
|
54
|
+
# macOS
|
|
55
|
+
brew install llama.cpp
|
|
56
|
+
|
|
57
|
+
# Linux (Ubuntu/Debian) - build from source
|
|
58
|
+
sudo apt install build-essential cmake
|
|
59
|
+
git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp
|
|
60
|
+
cmake -B build && cmake --build build --config Release
|
|
61
|
+
sudo cp build/bin/llama-server /usr/local/bin/
|
|
62
|
+
|
|
63
|
+
# Windows - download pre-built binaries from:
|
|
64
|
+
# https://github.com/ggml-org/llama.cpp/releases
|
|
65
|
+
|
|
66
|
+
# Start the server (downloads model automatically from Hugging Face)
|
|
67
|
+
llama-server -hf Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF -ngl 99 --port 8080
|
|
68
|
+
|
|
69
|
+
# Use with git-commit-ai (auto-detected if running on port 8080)
|
|
70
|
+
git-commit-ai
|
|
71
|
+
|
|
72
|
+
# Or explicitly use llamacpp backend
|
|
73
|
+
git-commit-ai --backend llamacpp
|
|
74
|
+
|
|
75
|
+
# Configure as default backend
|
|
76
|
+
git-commit-ai config --set backend=llamacpp
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Run llama-server as a service**
|
|
80
|
+
|
|
81
|
+
<details>
|
|
82
|
+
<summary><strong>macOS (launchd)</strong></summary>
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Create launchd service
|
|
86
|
+
cat > ~/Library/LaunchAgents/com.llamacpp.server.plist << 'EOF'
|
|
87
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
88
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
89
|
+
<plist version="1.0">
|
|
90
|
+
<dict>
|
|
91
|
+
<key>Label</key>
|
|
92
|
+
<string>com.llamacpp.server</string>
|
|
93
|
+
<key>ProgramArguments</key>
|
|
94
|
+
<array>
|
|
95
|
+
<string>/opt/homebrew/bin/llama-server</string>
|
|
96
|
+
<string>-hf</string>
|
|
97
|
+
<string>Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF</string>
|
|
98
|
+
<string>-ngl</string>
|
|
99
|
+
<string>99</string>
|
|
100
|
+
<string>--port</string>
|
|
101
|
+
<string>8080</string>
|
|
102
|
+
</array>
|
|
103
|
+
<key>RunAtLoad</key>
|
|
104
|
+
<true/>
|
|
105
|
+
<key>KeepAlive</key>
|
|
106
|
+
<true/>
|
|
107
|
+
<key>StandardOutPath</key>
|
|
108
|
+
<string>/tmp/llama-server.log</string>
|
|
109
|
+
<key>StandardErrorPath</key>
|
|
110
|
+
<string>/tmp/llama-server.err</string>
|
|
111
|
+
</dict>
|
|
112
|
+
</plist>
|
|
113
|
+
EOF
|
|
114
|
+
|
|
115
|
+
# Start the service
|
|
116
|
+
launchctl load ~/Library/LaunchAgents/com.llamacpp.server.plist
|
|
117
|
+
|
|
118
|
+
# Stop the service
|
|
119
|
+
launchctl unload ~/Library/LaunchAgents/com.llamacpp.server.plist
|
|
120
|
+
|
|
121
|
+
# Check logs
|
|
122
|
+
tail -f /tmp/llama-server.log
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
</details>
|
|
126
|
+
|
|
127
|
+
<details>
|
|
128
|
+
<summary><strong>Linux (systemd)</strong></summary>
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Create systemd service
|
|
132
|
+
sudo cat > /etc/systemd/system/llama-server.service << 'EOF'
|
|
133
|
+
[Unit]
|
|
134
|
+
Description=llama.cpp Server
|
|
135
|
+
After=network.target
|
|
136
|
+
|
|
137
|
+
[Service]
|
|
138
|
+
Type=simple
|
|
139
|
+
User=$USER
|
|
140
|
+
ExecStart=/usr/local/bin/llama-server -hf Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF -ngl 99 --port 8080
|
|
141
|
+
Restart=on-failure
|
|
142
|
+
RestartSec=10
|
|
143
|
+
StandardOutput=append:/var/log/llama-server.log
|
|
144
|
+
StandardError=append:/var/log/llama-server.err
|
|
145
|
+
|
|
146
|
+
[Install]
|
|
147
|
+
WantedBy=multi-user.target
|
|
148
|
+
EOF
|
|
149
|
+
|
|
150
|
+
# Replace $USER with your username
|
|
151
|
+
sudo sed -i "s/\$USER/$USER/" /etc/systemd/system/llama-server.service
|
|
152
|
+
|
|
153
|
+
# Enable and start the service
|
|
154
|
+
sudo systemctl daemon-reload
|
|
155
|
+
sudo systemctl enable llama-server
|
|
156
|
+
sudo systemctl start llama-server
|
|
157
|
+
|
|
158
|
+
# Check status
|
|
159
|
+
sudo systemctl status llama-server
|
|
160
|
+
|
|
161
|
+
# View logs
|
|
162
|
+
journalctl -u llama-server -f
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
</details>
|
|
166
|
+
|
|
167
|
+
<details>
|
|
168
|
+
<summary><strong>Windows (Task Scheduler)</strong></summary>
|
|
169
|
+
|
|
170
|
+
**Option 1: PowerShell script with Task Scheduler**
|
|
171
|
+
|
|
172
|
+
1. Create a startup script `C:\llama-server\start-llama.ps1`:
|
|
173
|
+
```powershell
|
|
174
|
+
# start-llama.ps1
|
|
175
|
+
Start-Process -FilePath "C:\llama-server\llama-server.exe" `
|
|
176
|
+
-ArgumentList "-hf", "Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF", "-ngl", "99", "--port", "8080" `
|
|
177
|
+
-WindowStyle Hidden `
|
|
178
|
+
-RedirectStandardOutput "C:\llama-server\llama-server.log" `
|
|
179
|
+
-RedirectStandardError "C:\llama-server\llama-server.err"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
2. Create a scheduled task (run in PowerShell as Administrator):
|
|
183
|
+
```powershell
|
|
184
|
+
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
|
|
185
|
+
-Argument "-ExecutionPolicy Bypass -File C:\llama-server\start-llama.ps1"
|
|
186
|
+
$trigger = New-ScheduledTaskTrigger -AtStartup
|
|
187
|
+
$principal = New-ScheduledTaskPrincipal -UserId "$env:USERNAME" -LogonType S4U
|
|
188
|
+
Register-ScheduledTask -TaskName "LlamaServer" -Action $action -Trigger $trigger -Principal $principal
|
|
189
|
+
|
|
190
|
+
# Start immediately
|
|
191
|
+
Start-ScheduledTask -TaskName "LlamaServer"
|
|
192
|
+
|
|
193
|
+
# Stop the service
|
|
194
|
+
Stop-ScheduledTask -TaskName "LlamaServer"
|
|
195
|
+
|
|
196
|
+
# Remove the service
|
|
197
|
+
Unregister-ScheduledTask -TaskName "LlamaServer" -Confirm:$false
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Option 2: Using NSSM (Non-Sucking Service Manager)**
|
|
201
|
+
|
|
202
|
+
```powershell
|
|
203
|
+
# Install NSSM (using chocolatey)
|
|
204
|
+
choco install nssm
|
|
205
|
+
|
|
206
|
+
# Install llama-server as a Windows service
|
|
207
|
+
nssm install LlamaServer "C:\llama-server\llama-server.exe" "-hf Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF -ngl 99 --port 8080"
|
|
208
|
+
nssm set LlamaServer AppDirectory "C:\llama-server"
|
|
209
|
+
nssm set LlamaServer AppStdout "C:\llama-server\llama-server.log"
|
|
210
|
+
nssm set LlamaServer AppStderr "C:\llama-server\llama-server.err"
|
|
211
|
+
|
|
212
|
+
# Start the service
|
|
213
|
+
nssm start LlamaServer
|
|
214
|
+
|
|
215
|
+
# Stop the service
|
|
216
|
+
nssm stop LlamaServer
|
|
217
|
+
|
|
218
|
+
# Remove the service
|
|
219
|
+
nssm remove LlamaServer confirm
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
</details>
|
|
223
|
+
|
|
224
|
+
**OpenAI**
|
|
225
|
+
```bash
|
|
226
|
+
export OPENAI_API_KEY="your-api-key"
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**OpenAI-Compatible APIs**
|
|
230
|
+
|
|
231
|
+
Any OpenAI-compatible API can be used by setting `OPENAI_BASE_URL`:
|
|
232
|
+
```bash
|
|
233
|
+
# Local server (llama.cpp, vLLM, etc.)
|
|
234
|
+
export OPENAI_BASE_URL="http://localhost:8080/v1"
|
|
235
|
+
|
|
236
|
+
# Or other providers (Together AI, Anyscale, etc.)
|
|
237
|
+
export OPENAI_BASE_URL="https://api.together.xyz/v1"
|
|
238
|
+
export OPENAI_API_KEY="your-api-key"
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Anthropic (Claude)**
|
|
242
|
+
```bash
|
|
243
|
+
export ANTHROPIC_API_KEY="your-api-key"
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Groq (Fast & Free tier)**
|
|
247
|
+
```bash
|
|
248
|
+
export GROQ_API_KEY="your-api-key"
|
|
249
|
+
```
|
|
250
|
+
|
|
30
251
|
## Quick Start
|
|
31
252
|
|
|
32
253
|
```bash
|
|
@@ -64,6 +285,44 @@ git-commit-ai --push
|
|
|
64
285
|
# Commit each modified file separately
|
|
65
286
|
git-commit-ai --individual
|
|
66
287
|
|
|
288
|
+
# Preview message without committing (dry run)
|
|
289
|
+
git add .
|
|
290
|
+
git-commit-ai --dry-run
|
|
291
|
+
|
|
292
|
+
# Amend the last commit with a new message
|
|
293
|
+
git-commit-ai --amend
|
|
294
|
+
|
|
295
|
+
# Force a specific scope and type
|
|
296
|
+
git-commit-ai --scope auth --type fix
|
|
297
|
+
|
|
298
|
+
# Generate message in a specific language
|
|
299
|
+
git-commit-ai --lang pt
|
|
300
|
+
|
|
301
|
+
# Reference an issue
|
|
302
|
+
git-commit-ai --issue 123
|
|
303
|
+
|
|
304
|
+
# Mark as breaking change
|
|
305
|
+
git-commit-ai --breaking
|
|
306
|
+
|
|
307
|
+
# Add co-authors
|
|
308
|
+
git-commit-ai --co-author "Jane Doe <jane@example.com>"
|
|
309
|
+
|
|
310
|
+
# Provide additional context
|
|
311
|
+
git-commit-ai --context "This fixes the login bug reported by QA"
|
|
312
|
+
|
|
313
|
+
# Use a specific backend
|
|
314
|
+
git-commit-ai --backend llamacpp
|
|
315
|
+
git-commit-ai --backend openai
|
|
316
|
+
git-commit-ai --backend anthropic
|
|
317
|
+
git-commit-ai --backend groq
|
|
318
|
+
|
|
319
|
+
# Override model
|
|
320
|
+
git-commit-ai --model gpt-4o
|
|
321
|
+
git-commit-ai --model claude-3-sonnet-20240229
|
|
322
|
+
|
|
323
|
+
# Adjust creativity (temperature)
|
|
324
|
+
git-commit-ai --temperature 0.3
|
|
325
|
+
|
|
67
326
|
# Preview changes before committing
|
|
68
327
|
git add .
|
|
69
328
|
git-commit-ai summarize
|
|
@@ -74,7 +333,21 @@ git-commit-ai --debug
|
|
|
74
333
|
# Show current config
|
|
75
334
|
git-commit-ai config
|
|
76
335
|
|
|
77
|
-
#
|
|
336
|
+
# Set a config value
|
|
337
|
+
git-commit-ai config --set backend=llamacpp
|
|
338
|
+
git-commit-ai config --set model=gpt-4o
|
|
339
|
+
git-commit-ai config --set temperature=0.5
|
|
340
|
+
|
|
341
|
+
# Use short aliases
|
|
342
|
+
git-commit-ai config --set lang=pt # → default_language
|
|
343
|
+
git-commit-ai config --set scope=api # → default_scope
|
|
344
|
+
git-commit-ai config --set type=feat # → default_type
|
|
345
|
+
git-commit-ai config --set temp=0.5 # → temperature
|
|
346
|
+
|
|
347
|
+
# List valid config keys and aliases
|
|
348
|
+
git-commit-ai config --list-keys
|
|
349
|
+
|
|
350
|
+
# Create/edit config file manually
|
|
78
351
|
git-commit-ai config --edit
|
|
79
352
|
```
|
|
80
353
|
|
|
@@ -110,15 +383,85 @@ git-commit-ai hook --remove
|
|
|
110
383
|
|
|
111
384
|
## Configuration
|
|
112
385
|
|
|
113
|
-
|
|
386
|
+
### Global Config
|
|
387
|
+
|
|
388
|
+
Location: `~/.config/git-commit-ai/config.toml`
|
|
114
389
|
|
|
115
390
|
```toml
|
|
391
|
+
# Backend: ollama, llamacpp, openai, anthropic, groq
|
|
392
|
+
backend = "ollama"
|
|
116
393
|
model = "llama3.1:8b"
|
|
117
394
|
ollama_url = "http://localhost:11434"
|
|
118
395
|
temperature = 0.7
|
|
119
396
|
retry_temperatures = [0.5, 0.3, 0.2]
|
|
397
|
+
|
|
398
|
+
# OpenAI Base URL - change this to use OpenAI-compatible APIs
|
|
399
|
+
# Examples:
|
|
400
|
+
# - Default OpenAI: https://api.openai.com/v1
|
|
401
|
+
# - llama.cpp: http://localhost:8080/v1
|
|
402
|
+
# - Together AI: https://api.together.xyz/v1
|
|
403
|
+
openai_base_url = "https://api.openai.com/v1"
|
|
404
|
+
|
|
405
|
+
# Optional: Ignore files from diff analysis
|
|
406
|
+
ignore_patterns = ["*.lock", "package-lock.json", "*.min.js"]
|
|
407
|
+
|
|
408
|
+
# Optional: Set defaults for commit messages
|
|
409
|
+
default_scope = "api" # Default scope if not specified
|
|
410
|
+
default_type = "feat" # Default commit type
|
|
411
|
+
default_language = "en" # Default language (en, pt, es, fr, de)
|
|
120
412
|
```
|
|
121
413
|
|
|
414
|
+
### Local Config (per-project)
|
|
415
|
+
|
|
416
|
+
Create `.gitcommitai` or `.gitcommitai.toml` in your project root to override global settings:
|
|
417
|
+
|
|
418
|
+
```toml
|
|
419
|
+
# .gitcommitai
|
|
420
|
+
default_scope = "frontend"
|
|
421
|
+
default_language = "pt"
|
|
422
|
+
ignore_patterns = ["dist/*", "*.generated.ts"]
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Default Models by Backend
|
|
426
|
+
|
|
427
|
+
| Backend | Default Model |
|
|
428
|
+
|---------|---------------|
|
|
429
|
+
| ollama | llama3.1:8b |
|
|
430
|
+
| llamacpp | gpt-4o-mini (alias) |
|
|
431
|
+
| openai | gpt-4o-mini |
|
|
432
|
+
| anthropic | claude-3-haiku-20240307 |
|
|
433
|
+
| groq | llama-3.1-8b-instant |
|
|
434
|
+
|
|
435
|
+
## CLI Options
|
|
436
|
+
|
|
437
|
+
| Option | Description |
|
|
438
|
+
|--------|-------------|
|
|
439
|
+
| `-p, --push` | Push after commit |
|
|
440
|
+
| `-y, --yes` | Skip confirmation |
|
|
441
|
+
| `-i, --individual` | Commit files individually |
|
|
442
|
+
| `-d, --debug` | Enable debug output |
|
|
443
|
+
| `--dry-run` | Show message without committing |
|
|
444
|
+
| `--amend` | Regenerate and amend the last commit |
|
|
445
|
+
| `-b, --backend <name>` | Backend to use |
|
|
446
|
+
| `-m, --model <name>` | Override model |
|
|
447
|
+
| `-t, --temperature <n>` | Override temperature (0.0-1.0) |
|
|
448
|
+
| `-s, --scope <scope>` | Force a specific scope (e.g., auth, api) |
|
|
449
|
+
| `--type <type>` | Force commit type (feat, fix, docs, etc.) |
|
|
450
|
+
| `-c, --context <text>` | Provide additional context for generation |
|
|
451
|
+
| `-l, --lang <code>` | Language for message (en, pt, es, fr, de) |
|
|
452
|
+
| `--issue <ref>` | Reference an issue (e.g., 123 or #123) |
|
|
453
|
+
| `--breaking` | Mark as breaking change (adds ! to type) |
|
|
454
|
+
| `--co-author <author>` | Add co-author (can be repeated) |
|
|
455
|
+
|
|
456
|
+
## Config Commands
|
|
457
|
+
|
|
458
|
+
| Command | Description |
|
|
459
|
+
|---------|-------------|
|
|
460
|
+
| `config` | Show current configuration |
|
|
461
|
+
| `config --edit` | Create/edit config file manually |
|
|
462
|
+
| `config --set <key=value>` | Set a config value |
|
|
463
|
+
| `config --list-keys` | List all valid config keys |
|
|
464
|
+
|
|
122
465
|
## Commit Types (Karma Convention)
|
|
123
466
|
|
|
124
467
|
| Type | Description |
|
|
@@ -132,6 +475,15 @@ retry_temperatures = [0.5, 0.3, 0.2]
|
|
|
132
475
|
| `build` | Build system or dependencies |
|
|
133
476
|
| `chore` | Maintenance tasks |
|
|
134
477
|
|
|
478
|
+
## Environment Variables
|
|
479
|
+
|
|
480
|
+
| Variable | Description |
|
|
481
|
+
|----------|-------------|
|
|
482
|
+
| `OPENAI_API_KEY` | OpenAI API key |
|
|
483
|
+
| `OPENAI_BASE_URL` | OpenAI-compatible API base URL (default: `https://api.openai.com/v1`) |
|
|
484
|
+
| `ANTHROPIC_API_KEY` | Anthropic API key |
|
|
485
|
+
| `GROQ_API_KEY` | Groq API key |
|
|
486
|
+
|
|
135
487
|
## License
|
|
136
488
|
|
|
137
489
|
MIT
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/git.ts
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
function matchesPattern(filePath, pattern) {
|
|
6
|
+
let regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "<<GLOBSTAR>>").replace(/\*/g, "[^/]*").replace(/<<GLOBSTAR>>/g, ".*").replace(/\?/g, ".");
|
|
7
|
+
if (!pattern.startsWith("/") && !pattern.startsWith("*")) {
|
|
8
|
+
regexStr = `(^|/)${regexStr}`;
|
|
9
|
+
}
|
|
10
|
+
regexStr = `${regexStr}($|/)`;
|
|
11
|
+
try {
|
|
12
|
+
const regex = new RegExp(regexStr);
|
|
13
|
+
return regex.test(filePath);
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function shouldIgnoreFile(filePath, patterns) {
|
|
19
|
+
return patterns.some((pattern) => matchesPattern(filePath, pattern));
|
|
20
|
+
}
|
|
21
|
+
function filterDiffByPatterns(diff, patterns) {
|
|
22
|
+
if (!patterns || patterns.length === 0) {
|
|
23
|
+
return diff;
|
|
24
|
+
}
|
|
25
|
+
const sections = diff.split(/(?=^diff --git)/m);
|
|
26
|
+
const filtered = sections.filter((section) => {
|
|
27
|
+
const match = section.match(/^diff --git a\/(.+?) b\//m);
|
|
28
|
+
if (!match) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
const filePath = match[1];
|
|
32
|
+
return !shouldIgnoreFile(filePath, patterns);
|
|
33
|
+
});
|
|
34
|
+
return filtered.join("");
|
|
35
|
+
}
|
|
36
|
+
var GitError = class extends Error {
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "GitError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
function runGit(...args) {
|
|
43
|
+
try {
|
|
44
|
+
const result = execSync(["git", ...args].join(" "), {
|
|
45
|
+
encoding: "utf-8",
|
|
46
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
47
|
+
});
|
|
48
|
+
return result.trim();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
const err = error;
|
|
51
|
+
const message = err.stderr?.trim() || err.message;
|
|
52
|
+
throw new GitError(`Git command failed: ${message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function runGitSafe(...args) {
|
|
56
|
+
try {
|
|
57
|
+
return runGit(...args);
|
|
58
|
+
} catch {
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function getStagedDiff() {
|
|
63
|
+
const diff = runGitSafe("diff", "--cached");
|
|
64
|
+
const stats = runGitSafe("diff", "--cached", "--stat");
|
|
65
|
+
const filesOutput = runGitSafe("diff", "--cached", "--name-only");
|
|
66
|
+
const files = filesOutput.split("\n").filter((f) => f);
|
|
67
|
+
const statusOutput = runGitSafe("diff", "--cached", "--name-status");
|
|
68
|
+
const filesAdded = [];
|
|
69
|
+
const filesDeleted = [];
|
|
70
|
+
const filesModified = [];
|
|
71
|
+
statusOutput.split("\n").filter((f) => f).forEach((line) => {
|
|
72
|
+
const [status, ...pathParts] = line.split(" ");
|
|
73
|
+
const filePath = pathParts.join(" ");
|
|
74
|
+
if (status.startsWith("A")) {
|
|
75
|
+
filesAdded.push(filePath);
|
|
76
|
+
} else if (status.startsWith("D")) {
|
|
77
|
+
filesDeleted.push(filePath);
|
|
78
|
+
} else if (status.startsWith("M") || status.startsWith("R")) {
|
|
79
|
+
filesModified.push(filePath);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
diff,
|
|
84
|
+
stats,
|
|
85
|
+
files,
|
|
86
|
+
filesAdded,
|
|
87
|
+
filesDeleted,
|
|
88
|
+
filesModified,
|
|
89
|
+
isEmpty: !diff.trim()
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function getFileDiff(filePath) {
|
|
93
|
+
const diff = runGitSafe("diff", "--cached", "--", filePath);
|
|
94
|
+
const stats = runGitSafe("diff", "--cached", "--stat", "--", filePath);
|
|
95
|
+
const files = diff ? [filePath] : [];
|
|
96
|
+
const statusOutput = runGitSafe("diff", "--cached", "--name-status", "--", filePath);
|
|
97
|
+
const filesAdded = [];
|
|
98
|
+
const filesDeleted = [];
|
|
99
|
+
const filesModified = [];
|
|
100
|
+
if (statusOutput) {
|
|
101
|
+
const [status] = statusOutput.split(" ");
|
|
102
|
+
if (status.startsWith("A")) {
|
|
103
|
+
filesAdded.push(filePath);
|
|
104
|
+
} else if (status.startsWith("D")) {
|
|
105
|
+
filesDeleted.push(filePath);
|
|
106
|
+
} else if (status.startsWith("M") || status.startsWith("R")) {
|
|
107
|
+
filesModified.push(filePath);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
diff,
|
|
112
|
+
stats,
|
|
113
|
+
files,
|
|
114
|
+
filesAdded,
|
|
115
|
+
filesDeleted,
|
|
116
|
+
filesModified,
|
|
117
|
+
isEmpty: !diff.trim()
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function addFiles(...paths) {
|
|
121
|
+
if (paths.length === 0) {
|
|
122
|
+
paths = ["."];
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
runGit("add", ...paths);
|
|
126
|
+
return true;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
const err = error;
|
|
129
|
+
if (err.message.includes("ignored by one of your .gitignore") || err.message.includes("pathspec") && err.message.includes("did not match")) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function commit(message) {
|
|
136
|
+
runGit("commit", "-m", `"${message.replace(/"/g, '\\"')}"`);
|
|
137
|
+
return runGit("rev-parse", "HEAD");
|
|
138
|
+
}
|
|
139
|
+
function push() {
|
|
140
|
+
const branch = getCurrentBranch();
|
|
141
|
+
runGit("push", "origin", branch);
|
|
142
|
+
}
|
|
143
|
+
function getCurrentBranch() {
|
|
144
|
+
return runGit("rev-parse", "--abbrev-ref", "HEAD");
|
|
145
|
+
}
|
|
146
|
+
function getModifiedFiles() {
|
|
147
|
+
const files = /* @__PURE__ */ new Set();
|
|
148
|
+
const staged = runGitSafe("diff", "--cached", "--name-only");
|
|
149
|
+
if (staged) {
|
|
150
|
+
staged.split("\n").forEach((f) => files.add(f));
|
|
151
|
+
}
|
|
152
|
+
const modified = runGitSafe("diff", "--name-only");
|
|
153
|
+
if (modified) {
|
|
154
|
+
modified.split("\n").forEach((f) => files.add(f));
|
|
155
|
+
}
|
|
156
|
+
const untracked = runGitSafe("ls-files", "--others", "--exclude-standard");
|
|
157
|
+
if (untracked) {
|
|
158
|
+
untracked.split("\n").forEach((f) => files.add(f));
|
|
159
|
+
}
|
|
160
|
+
return Array.from(files).filter((f) => f);
|
|
161
|
+
}
|
|
162
|
+
function hasStagedChanges() {
|
|
163
|
+
const diff = runGitSafe("diff", "--cached", "--name-only");
|
|
164
|
+
return Boolean(diff.trim());
|
|
165
|
+
}
|
|
166
|
+
function getStagedFiles() {
|
|
167
|
+
const output = runGitSafe("diff", "--cached", "--name-only");
|
|
168
|
+
return output.split("\n").filter((f) => f);
|
|
169
|
+
}
|
|
170
|
+
function resetStaged() {
|
|
171
|
+
runGitSafe("reset", "HEAD");
|
|
172
|
+
}
|
|
173
|
+
function getLastCommitDiff() {
|
|
174
|
+
const diff = runGitSafe("diff", "HEAD~1", "HEAD");
|
|
175
|
+
const stats = runGitSafe("diff", "HEAD~1", "HEAD", "--stat");
|
|
176
|
+
const filesOutput = runGitSafe("diff", "HEAD~1", "HEAD", "--name-only");
|
|
177
|
+
const files = filesOutput.split("\n").filter((f) => f);
|
|
178
|
+
const statusOutput = runGitSafe("diff", "HEAD~1", "HEAD", "--name-status");
|
|
179
|
+
const filesAdded = [];
|
|
180
|
+
const filesDeleted = [];
|
|
181
|
+
const filesModified = [];
|
|
182
|
+
statusOutput.split("\n").filter((f) => f).forEach((line) => {
|
|
183
|
+
const [status, ...pathParts] = line.split(" ");
|
|
184
|
+
const filePath = pathParts.join(" ");
|
|
185
|
+
if (status.startsWith("A")) {
|
|
186
|
+
filesAdded.push(filePath);
|
|
187
|
+
} else if (status.startsWith("D")) {
|
|
188
|
+
filesDeleted.push(filePath);
|
|
189
|
+
} else if (status.startsWith("M") || status.startsWith("R")) {
|
|
190
|
+
filesModified.push(filePath);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
return {
|
|
194
|
+
diff,
|
|
195
|
+
stats,
|
|
196
|
+
files,
|
|
197
|
+
filesAdded,
|
|
198
|
+
filesDeleted,
|
|
199
|
+
filesModified,
|
|
200
|
+
isEmpty: !diff.trim()
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function commitAmend(message) {
|
|
204
|
+
runGit("commit", "--amend", "-m", `"${message.replace(/"/g, '\\"')}"`);
|
|
205
|
+
return runGit("rev-parse", "HEAD");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export {
|
|
209
|
+
shouldIgnoreFile,
|
|
210
|
+
filterDiffByPatterns,
|
|
211
|
+
GitError,
|
|
212
|
+
getStagedDiff,
|
|
213
|
+
getFileDiff,
|
|
214
|
+
addFiles,
|
|
215
|
+
commit,
|
|
216
|
+
push,
|
|
217
|
+
getCurrentBranch,
|
|
218
|
+
getModifiedFiles,
|
|
219
|
+
hasStagedChanges,
|
|
220
|
+
getStagedFiles,
|
|
221
|
+
resetStaged,
|
|
222
|
+
getLastCommitDiff,
|
|
223
|
+
commitAmend
|
|
224
|
+
};
|
|
225
|
+
//# sourceMappingURL=chunk-5MPJCPJ4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/git.ts"],"sourcesContent":["import { execSync } from \"node:child_process\";\nimport type { DiffResult } from \"./types.js\";\n\nfunction matchesPattern(filePath: string, pattern: string): boolean {\n // Convert glob pattern to regex\n // Support: *, **, ?\n let regexStr = pattern\n .replace(/\\./g, \"\\\\.\")\n .replace(/\\*\\*/g, \"<<GLOBSTAR>>\")\n .replace(/\\*/g, \"[^/]*\")\n .replace(/<<GLOBSTAR>>/g, \".*\")\n .replace(/\\?/g, \".\");\n\n // If pattern doesn't start with / or *, it can match anywhere in path\n if (!pattern.startsWith(\"/\") && !pattern.startsWith(\"*\")) {\n regexStr = `(^|/)${regexStr}`;\n }\n\n // Match end of string or after /\n regexStr = `${regexStr}($|/)`;\n\n try {\n const regex = new RegExp(regexStr);\n return regex.test(filePath);\n } catch {\n return false;\n }\n}\n\nexport function shouldIgnoreFile(filePath: string, patterns: string[]): boolean {\n return patterns.some((pattern) => matchesPattern(filePath, pattern));\n}\n\nexport function filterDiffByPatterns(diff: string, patterns: string[]): string {\n if (!patterns || patterns.length === 0) {\n return diff;\n }\n\n // Split diff into file sections\n const sections = diff.split(/(?=^diff --git)/m);\n const filtered = sections.filter((section) => {\n // Extract file path from diff header\n const match = section.match(/^diff --git a\\/(.+?) b\\//m);\n if (!match) {\n return true; // Keep sections without proper header\n }\n const filePath = match[1];\n return !shouldIgnoreFile(filePath, patterns);\n });\n\n return filtered.join(\"\");\n}\n\nexport class GitError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"GitError\";\n }\n}\n\nfunction runGit(...args: string[]): string {\n try {\n const result = execSync([\"git\", ...args].join(\" \"), {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n return result.trim();\n } catch (error) {\n const err = error as { stderr?: string; message: string };\n const message = err.stderr?.trim() || err.message;\n throw new GitError(`Git command failed: ${message}`);\n }\n}\n\nfunction runGitSafe(...args: string[]): string {\n try {\n return runGit(...args);\n } catch {\n return \"\";\n }\n}\n\nexport function getStagedDiff(): DiffResult {\n const diff = runGitSafe(\"diff\", \"--cached\");\n const stats = runGitSafe(\"diff\", \"--cached\", \"--stat\");\n const filesOutput = runGitSafe(\"diff\", \"--cached\", \"--name-only\");\n const files = filesOutput.split(\"\\n\").filter((f) => f);\n\n // Get file status (A=added, D=deleted, M=modified, R=renamed)\n const statusOutput = runGitSafe(\"diff\", \"--cached\", \"--name-status\");\n const filesAdded: string[] = [];\n const filesDeleted: string[] = [];\n const filesModified: string[] = [];\n\n statusOutput.split(\"\\n\").filter((f) => f).forEach((line) => {\n const [status, ...pathParts] = line.split(\"\\t\");\n const filePath = pathParts.join(\"\\t\"); // Handle filenames with tabs\n if (status.startsWith(\"A\")) {\n filesAdded.push(filePath);\n } else if (status.startsWith(\"D\")) {\n filesDeleted.push(filePath);\n } else if (status.startsWith(\"M\") || status.startsWith(\"R\")) {\n filesModified.push(filePath);\n }\n });\n\n return {\n diff,\n stats,\n files,\n filesAdded,\n filesDeleted,\n filesModified,\n isEmpty: !diff.trim(),\n };\n}\n\nexport function getFileDiff(filePath: string): DiffResult {\n const diff = runGitSafe(\"diff\", \"--cached\", \"--\", filePath);\n const stats = runGitSafe(\"diff\", \"--cached\", \"--stat\", \"--\", filePath);\n const files = diff ? [filePath] : [];\n\n // Get file status for this specific file\n const statusOutput = runGitSafe(\"diff\", \"--cached\", \"--name-status\", \"--\", filePath);\n const filesAdded: string[] = [];\n const filesDeleted: string[] = [];\n const filesModified: string[] = [];\n\n if (statusOutput) {\n const [status] = statusOutput.split(\"\\t\");\n if (status.startsWith(\"A\")) {\n filesAdded.push(filePath);\n } else if (status.startsWith(\"D\")) {\n filesDeleted.push(filePath);\n } else if (status.startsWith(\"M\") || status.startsWith(\"R\")) {\n filesModified.push(filePath);\n }\n }\n\n return {\n diff,\n stats,\n files,\n filesAdded,\n filesDeleted,\n filesModified,\n isEmpty: !diff.trim(),\n };\n}\n\nexport function addFiles(...paths: string[]): boolean {\n if (paths.length === 0) {\n paths = [\".\"];\n }\n try {\n runGit(\"add\", ...paths);\n return true;\n } catch (error) {\n const err = error as GitError;\n // Ignore \"ignored file\" errors and \"pathspec did not match\" (deleted files)\n if (\n err.message.includes(\"ignored by one of your .gitignore\") ||\n err.message.includes(\"pathspec\") && err.message.includes(\"did not match\")\n ) {\n return false;\n }\n throw error;\n }\n}\n\nexport function commit(message: string): string {\n runGit(\"commit\", \"-m\", `\"${message.replace(/\"/g, '\\\\\"')}\"`);\n return runGit(\"rev-parse\", \"HEAD\");\n}\n\nexport function push(): void {\n const branch = getCurrentBranch();\n runGit(\"push\", \"origin\", branch);\n}\n\nexport function getCurrentBranch(): string {\n return runGit(\"rev-parse\", \"--abbrev-ref\", \"HEAD\");\n}\n\nexport function getModifiedFiles(): string[] {\n const files = new Set<string>();\n\n // Modified and staged files\n const staged = runGitSafe(\"diff\", \"--cached\", \"--name-only\");\n if (staged) {\n staged.split(\"\\n\").forEach((f) => files.add(f));\n }\n\n // Modified but not staged\n const modified = runGitSafe(\"diff\", \"--name-only\");\n if (modified) {\n modified.split(\"\\n\").forEach((f) => files.add(f));\n }\n\n // Untracked files\n const untracked = runGitSafe(\"ls-files\", \"--others\", \"--exclude-standard\");\n if (untracked) {\n untracked.split(\"\\n\").forEach((f) => files.add(f));\n }\n\n return Array.from(files).filter((f) => f);\n}\n\nexport function hasStagedChanges(): boolean {\n const diff = runGitSafe(\"diff\", \"--cached\", \"--name-only\");\n return Boolean(diff.trim());\n}\n\nexport function getStagedFiles(): string[] {\n const output = runGitSafe(\"diff\", \"--cached\", \"--name-only\");\n return output.split(\"\\n\").filter((f) => f);\n}\n\nexport function resetStaged(): void {\n runGitSafe(\"reset\", \"HEAD\");\n}\n\nexport function getLastCommitDiff(): DiffResult {\n const diff = runGitSafe(\"diff\", \"HEAD~1\", \"HEAD\");\n const stats = runGitSafe(\"diff\", \"HEAD~1\", \"HEAD\", \"--stat\");\n const filesOutput = runGitSafe(\"diff\", \"HEAD~1\", \"HEAD\", \"--name-only\");\n const files = filesOutput.split(\"\\n\").filter((f) => f);\n\n // Get file status for the last commit\n const statusOutput = runGitSafe(\"diff\", \"HEAD~1\", \"HEAD\", \"--name-status\");\n const filesAdded: string[] = [];\n const filesDeleted: string[] = [];\n const filesModified: string[] = [];\n\n statusOutput.split(\"\\n\").filter((f) => f).forEach((line) => {\n const [status, ...pathParts] = line.split(\"\\t\");\n const filePath = pathParts.join(\"\\t\");\n if (status.startsWith(\"A\")) {\n filesAdded.push(filePath);\n } else if (status.startsWith(\"D\")) {\n filesDeleted.push(filePath);\n } else if (status.startsWith(\"M\") || status.startsWith(\"R\")) {\n filesModified.push(filePath);\n }\n });\n\n return {\n diff,\n stats,\n files,\n filesAdded,\n filesDeleted,\n filesModified,\n isEmpty: !diff.trim(),\n };\n}\n\nexport function commitAmend(message: string): string {\n runGit(\"commit\", \"--amend\", \"-m\", `\"${message.replace(/\"/g, '\\\\\"')}\"`);\n return runGit(\"rev-parse\", \"HEAD\");\n}\n"],"mappings":"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","names":[]}
|