commit-maker 0.2.1__py3-none-any.whl → 0.3.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.
- commit_maker/colored.py +59 -0
- commit_maker/custom_int_prompt.py +6 -0
- commit_maker/cut_think_part.py +6 -0
- commit_maker/main.py +485 -573
- commit_maker/mistral.py +68 -0
- commit_maker/ollama.py +65 -0
- commit_maker/rich_custom_formatter.py +11 -0
- commit_maker-0.3.0.dist-info/METADATA +127 -0
- commit_maker-0.3.0.dist-info/RECORD +14 -0
- {commit_maker-0.2.1.dist-info → commit_maker-0.3.0.dist-info}/WHEEL +1 -1
- {commit_maker-0.2.1.dist-info → commit_maker-0.3.0.dist-info/licenses}/LICENSE +21 -21
- commit_maker-0.2.1.dist-info/METADATA +0 -119
- commit_maker-0.2.1.dist-info/RECORD +0 -8
- {commit_maker-0.2.1.dist-info → commit_maker-0.3.0.dist-info}/entry_points.txt +0 -0
- {commit_maker-0.2.1.dist-info → commit_maker-0.3.0.dist-info}/top_level.txt +0 -0
commit_maker/main.py
CHANGED
|
@@ -1,573 +1,485 @@
|
|
|
1
|
-
# CLI
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
type=
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
""
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
)
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
)
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
)
|
|
487
|
-
if commit_with_message_from_ai != "r":
|
|
488
|
-
retry = False
|
|
489
|
-
break
|
|
490
|
-
if commit_with_message_from_ai == "y":
|
|
491
|
-
subprocess.run(
|
|
492
|
-
["git", "commit", "-m", f"{commit_message}"],
|
|
493
|
-
encoding="utf-8",
|
|
494
|
-
)
|
|
495
|
-
print(colored("Коммит успешно создан!", "green"))
|
|
496
|
-
else:
|
|
497
|
-
commit_message = client.message(
|
|
498
|
-
message=prompt_for_ai
|
|
499
|
-
+ "Git status: "
|
|
500
|
-
+ git_status.stdout
|
|
501
|
-
+ "Git diff: "
|
|
502
|
-
+ git_diff.stdout,
|
|
503
|
-
temperature=temperature,
|
|
504
|
-
)
|
|
505
|
-
print(
|
|
506
|
-
colored(
|
|
507
|
-
"Коммит-месседж успешно сгенерирован:", "green", False
|
|
508
|
-
)
|
|
509
|
-
)
|
|
510
|
-
print(
|
|
511
|
-
colored(
|
|
512
|
-
commit_message,
|
|
513
|
-
"yellow",
|
|
514
|
-
False,
|
|
515
|
-
)
|
|
516
|
-
)
|
|
517
|
-
return None
|
|
518
|
-
|
|
519
|
-
# Если нет
|
|
520
|
-
else:
|
|
521
|
-
init_git_repo = (
|
|
522
|
-
True
|
|
523
|
-
if input(
|
|
524
|
-
colored("Не инициализирован git репозиторий!", "red")
|
|
525
|
-
+ " Выполнить "
|
|
526
|
-
+ colored("git init", "yellow")
|
|
527
|
-
+ "? [y/N]: "
|
|
528
|
-
)
|
|
529
|
-
== "y"
|
|
530
|
-
else False
|
|
531
|
-
)
|
|
532
|
-
if init_git_repo:
|
|
533
|
-
subprocess.run(
|
|
534
|
-
["git", "init"],
|
|
535
|
-
capture_output=True,
|
|
536
|
-
encoding="utf-8",
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
(
|
|
540
|
-
(
|
|
541
|
-
subprocess.run(
|
|
542
|
-
["git", "add", "-A"],
|
|
543
|
-
),
|
|
544
|
-
subprocess.run(
|
|
545
|
-
[
|
|
546
|
-
"git",
|
|
547
|
-
"commit",
|
|
548
|
-
"-m",
|
|
549
|
-
"'Initial commit'",
|
|
550
|
-
],
|
|
551
|
-
encoding="utf-8",
|
|
552
|
-
),
|
|
553
|
-
)
|
|
554
|
-
if input(
|
|
555
|
-
"Сделать первый коммит с сообщением "
|
|
556
|
-
+ colored("'Initial commit?'", "yellow")
|
|
557
|
-
+ " [y/N]: "
|
|
558
|
-
)
|
|
559
|
-
== "y"
|
|
560
|
-
else None
|
|
561
|
-
)
|
|
562
|
-
except FileNotFoundError as e:
|
|
563
|
-
print(
|
|
564
|
-
colored("Ollama не установлена!", "red")
|
|
565
|
-
if "ollama" in str(e)
|
|
566
|
-
else colored(str(e), "red")
|
|
567
|
-
)
|
|
568
|
-
except Exception as e:
|
|
569
|
-
print(colored("Ошибка:", "red") + " " + str(e))
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if __name__ == "__main__":
|
|
573
|
-
main()
|
|
1
|
+
# CLI utility that generates commit messages using AI.
|
|
2
|
+
import argparse
|
|
3
|
+
import importlib
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
import rich.console
|
|
9
|
+
|
|
10
|
+
from .colored import colored
|
|
11
|
+
from .custom_int_prompt import CustomIntPrompt
|
|
12
|
+
from .cut_think_part import cut_think
|
|
13
|
+
from .mistral import MistralAI
|
|
14
|
+
from .ollama import Ollama
|
|
15
|
+
from .rich_custom_formatter import CustomFormatter
|
|
16
|
+
|
|
17
|
+
# Constants
|
|
18
|
+
mistral_api_key = os.environ.get("MISTRAL_API_KEY")
|
|
19
|
+
console = rich.console.Console()
|
|
20
|
+
prompt = CustomIntPrompt()
|
|
21
|
+
available_langs = ["en", "ru"]
|
|
22
|
+
|
|
23
|
+
# Argument parser
|
|
24
|
+
parser = argparse.ArgumentParser(
|
|
25
|
+
prog="commit_maker",
|
|
26
|
+
description="CLI utility that generates commit messages using AI. "
|
|
27
|
+
"Supports local models/Mistral AI API. Local models use ollama.",
|
|
28
|
+
formatter_class=CustomFormatter,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# General parameters
|
|
32
|
+
general_params = parser.add_argument_group("General parameters")
|
|
33
|
+
general_params.add_argument(
|
|
34
|
+
"-l",
|
|
35
|
+
"--local-models",
|
|
36
|
+
action="store_true",
|
|
37
|
+
default=False,
|
|
38
|
+
help="Use local models",
|
|
39
|
+
)
|
|
40
|
+
general_params.add_argument(
|
|
41
|
+
"-d",
|
|
42
|
+
"--dry-run",
|
|
43
|
+
action="store_true",
|
|
44
|
+
default=False,
|
|
45
|
+
help="Dry run: show commit message without creating commit",
|
|
46
|
+
)
|
|
47
|
+
general_params.add_argument(
|
|
48
|
+
"-V",
|
|
49
|
+
"--version",
|
|
50
|
+
action="version",
|
|
51
|
+
version=f"%(prog)s {importlib.metadata.version('commit-maker')}",
|
|
52
|
+
)
|
|
53
|
+
general_params.add_argument(
|
|
54
|
+
"-o",
|
|
55
|
+
"--timeout",
|
|
56
|
+
type=int,
|
|
57
|
+
default=None,
|
|
58
|
+
help="Change timeout for models. Default is None.",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Generation parameters
|
|
62
|
+
generation_params = parser.add_argument_group("Generation parameters")
|
|
63
|
+
generation_params.add_argument(
|
|
64
|
+
"-t",
|
|
65
|
+
"--temperature",
|
|
66
|
+
default=1.0,
|
|
67
|
+
type=float,
|
|
68
|
+
help="Model temperature for message generation. "
|
|
69
|
+
"Range: [0.0, 1.5]. Default: 1.0",
|
|
70
|
+
)
|
|
71
|
+
generation_params.add_argument(
|
|
72
|
+
"-m",
|
|
73
|
+
"--max-symbols",
|
|
74
|
+
type=int,
|
|
75
|
+
default=200,
|
|
76
|
+
help="Maximum commit message length. Default: 200",
|
|
77
|
+
)
|
|
78
|
+
generation_params.add_argument(
|
|
79
|
+
"-M",
|
|
80
|
+
"--model",
|
|
81
|
+
type=str,
|
|
82
|
+
help="Model to be used by ollama",
|
|
83
|
+
)
|
|
84
|
+
generation_params.add_argument(
|
|
85
|
+
"-e",
|
|
86
|
+
"--exclude",
|
|
87
|
+
nargs="+",
|
|
88
|
+
default=[],
|
|
89
|
+
help="Files to exclude when generating commit message",
|
|
90
|
+
)
|
|
91
|
+
generation_params.add_argument(
|
|
92
|
+
"-w",
|
|
93
|
+
"--wish",
|
|
94
|
+
default=None,
|
|
95
|
+
type=str,
|
|
96
|
+
help="Custom wishes/edits for the commit message",
|
|
97
|
+
)
|
|
98
|
+
generation_params.add_argument(
|
|
99
|
+
"-L",
|
|
100
|
+
"--language",
|
|
101
|
+
choices=available_langs,
|
|
102
|
+
default="ru",
|
|
103
|
+
help="Language of generated commit message (en/ru)",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# Main function
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def main() -> None:
|
|
111
|
+
# Parsing arguments
|
|
112
|
+
parsed_args = parser.parse_args()
|
|
113
|
+
use_local_models = parsed_args.local_models
|
|
114
|
+
max_symbols = parsed_args.max_symbols
|
|
115
|
+
model = parsed_args.model
|
|
116
|
+
dry_run = parsed_args.dry_run
|
|
117
|
+
temperature = parsed_args.temperature
|
|
118
|
+
excluded_files = parsed_args.exclude
|
|
119
|
+
wish = parsed_args.wish
|
|
120
|
+
timeout = parsed_args.timeout
|
|
121
|
+
lang = parsed_args.language
|
|
122
|
+
|
|
123
|
+
# AI prompt
|
|
124
|
+
prompt_for_ai = f"""You are a git commit message generator.
|
|
125
|
+
Generate a single commit message in
|
|
126
|
+
{"Russian" if lang == "ru" else "English"} that:
|
|
127
|
+
Clearly summarizes the purpose of the changes.
|
|
128
|
+
Does not exceed {max_symbols} characters.
|
|
129
|
+
Uses information from git status and git diff.
|
|
130
|
+
Takes into account user preferences: {wish}.
|
|
131
|
+
Output only the commit message — plain text, no markdown, no
|
|
132
|
+
explanations, no formatting."""
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
if not use_local_models and not mistral_api_key:
|
|
136
|
+
console.print(
|
|
137
|
+
"MISTRAL_API_KEY not found for API usage!",
|
|
138
|
+
style="red",
|
|
139
|
+
highlight=False,
|
|
140
|
+
)
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
# Get git version if available
|
|
144
|
+
git_version = subprocess.run( # noqa
|
|
145
|
+
["git", "--version"],
|
|
146
|
+
capture_output=True,
|
|
147
|
+
text=True,
|
|
148
|
+
encoding="utf-8",
|
|
149
|
+
).stdout
|
|
150
|
+
|
|
151
|
+
# Check if .git exists
|
|
152
|
+
dot_git = ".git" in os.listdir("./")
|
|
153
|
+
|
|
154
|
+
# If .git exists
|
|
155
|
+
if dot_git:
|
|
156
|
+
# Get commit differences
|
|
157
|
+
git_status = subprocess.run(
|
|
158
|
+
["git", "status"],
|
|
159
|
+
capture_output=True,
|
|
160
|
+
text=True,
|
|
161
|
+
encoding="utf-8",
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
new_files = subprocess.run(
|
|
165
|
+
["git", "ls-files", "--others", "--exclude-standard"],
|
|
166
|
+
capture_output=True,
|
|
167
|
+
text=True,
|
|
168
|
+
encoding="utf-8",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
if excluded_files:
|
|
172
|
+
git_diff_command = ["git", "diff", "--staged", "--", "."]
|
|
173
|
+
git_diff_command.extend([f":!{file}" for file in excluded_files]) # noqa
|
|
174
|
+
else:
|
|
175
|
+
git_diff_command = ["git", "diff", "--staged"]
|
|
176
|
+
|
|
177
|
+
git_diff = subprocess.run(
|
|
178
|
+
git_diff_command,
|
|
179
|
+
capture_output=True,
|
|
180
|
+
text=True,
|
|
181
|
+
encoding="utf-8",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if (
|
|
185
|
+
(not new_files.stdout)
|
|
186
|
+
and (not git_diff.stdout)
|
|
187
|
+
and (
|
|
188
|
+
not subprocess.run(
|
|
189
|
+
["git", "diff"],
|
|
190
|
+
capture_output=True,
|
|
191
|
+
encoding="utf-8",
|
|
192
|
+
).stdout
|
|
193
|
+
)
|
|
194
|
+
): # Check for no changes
|
|
195
|
+
console.print(
|
|
196
|
+
"[red]No changes added![/red]",
|
|
197
|
+
highlight=False,
|
|
198
|
+
)
|
|
199
|
+
return None
|
|
200
|
+
if not git_diff.stdout:
|
|
201
|
+
if not dry_run:
|
|
202
|
+
if (
|
|
203
|
+
input(
|
|
204
|
+
colored("No staged changes!", "red")
|
|
205
|
+
+ " Add all automatically using "
|
|
206
|
+
+ colored("git add -A", "yellow")
|
|
207
|
+
+ "? [y/N]: "
|
|
208
|
+
)
|
|
209
|
+
== "y"
|
|
210
|
+
):
|
|
211
|
+
subprocess.run(
|
|
212
|
+
["git", "add", "-A"],
|
|
213
|
+
)
|
|
214
|
+
else:
|
|
215
|
+
console.print(
|
|
216
|
+
"Add required files manually.",
|
|
217
|
+
style="yellow",
|
|
218
|
+
highlight=False,
|
|
219
|
+
)
|
|
220
|
+
return None
|
|
221
|
+
else:
|
|
222
|
+
console.print(
|
|
223
|
+
"[red]Nothing to commit![/red]"
|
|
224
|
+
" Add required files using "
|
|
225
|
+
"[yellow]git add <filename>[/yellow]",
|
|
226
|
+
highlight=False,
|
|
227
|
+
)
|
|
228
|
+
return None
|
|
229
|
+
git_diff = subprocess.run(
|
|
230
|
+
["git", "diff", "--staged"],
|
|
231
|
+
capture_output=True,
|
|
232
|
+
text=True,
|
|
233
|
+
encoding="utf-8",
|
|
234
|
+
)
|
|
235
|
+
if subprocess.run(
|
|
236
|
+
["git", "diff"],
|
|
237
|
+
capture_output=True,
|
|
238
|
+
encoding="utf-8",
|
|
239
|
+
).stdout:
|
|
240
|
+
console.print(
|
|
241
|
+
"[red]Note: You have unstaged changes![/red]"
|
|
242
|
+
" To add more files, press "
|
|
243
|
+
"[yellow]Ctrl + C[/yellow] and run "
|
|
244
|
+
"[yellow]git add <filename>[/yellow]",
|
|
245
|
+
highlight=False,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
if use_local_models:
|
|
249
|
+
# Check Ollama installation
|
|
250
|
+
try:
|
|
251
|
+
subprocess.run(
|
|
252
|
+
["ollama", "--version"],
|
|
253
|
+
text=True,
|
|
254
|
+
capture_output=True,
|
|
255
|
+
)
|
|
256
|
+
except FileNotFoundError:
|
|
257
|
+
console.print(
|
|
258
|
+
"Ollama is not installed!",
|
|
259
|
+
style="red bold",
|
|
260
|
+
)
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
# Check if Ollama is running
|
|
264
|
+
ollama_served = (
|
|
265
|
+
requests.get("http://localhost:11434").status_code == 200
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if ollama_served:
|
|
269
|
+
# Get list of models from Ollama
|
|
270
|
+
ollama_models_json = requests.get(
|
|
271
|
+
"http://localhost:11434/api/tags"
|
|
272
|
+
).json()
|
|
273
|
+
if ollama_models_json["models"]:
|
|
274
|
+
ollama_list_of_models = [
|
|
275
|
+
i["model"] for i in ollama_models_json["models"]
|
|
276
|
+
]
|
|
277
|
+
else:
|
|
278
|
+
console.print(
|
|
279
|
+
"[yellow]Ollama model list is empty!"
|
|
280
|
+
"[/yellow] To install models, visit "
|
|
281
|
+
"https://ollama.com/models",
|
|
282
|
+
highlight=False,
|
|
283
|
+
)
|
|
284
|
+
return None
|
|
285
|
+
else:
|
|
286
|
+
console.print(
|
|
287
|
+
"[yellow]Ollama server not running\n"
|
|
288
|
+
"or not installed![/yellow]"
|
|
289
|
+
)
|
|
290
|
+
return None
|
|
291
|
+
else:
|
|
292
|
+
ollama_list_of_models = 0
|
|
293
|
+
|
|
294
|
+
# Handle missing Ollama
|
|
295
|
+
if not ollama_list_of_models and use_local_models:
|
|
296
|
+
console.print(
|
|
297
|
+
"[yellow]Ollama is not installed or model list is empty!"
|
|
298
|
+
"[/yellow] To install, visit "
|
|
299
|
+
"https://ollama.com/download",
|
|
300
|
+
highlight=False,
|
|
301
|
+
)
|
|
302
|
+
return None
|
|
303
|
+
elif not use_local_models and model:
|
|
304
|
+
console.print(
|
|
305
|
+
f"To use {model} locally, use the flag "
|
|
306
|
+
"[yellow]--local-models[/yellow]. For help: "
|
|
307
|
+
"[yellow]--help[/yellow]",
|
|
308
|
+
highlight=False,
|
|
309
|
+
)
|
|
310
|
+
return None
|
|
311
|
+
elif ollama_list_of_models and use_local_models:
|
|
312
|
+
if not model:
|
|
313
|
+
if len(ollama_list_of_models) > 1:
|
|
314
|
+
console.print(
|
|
315
|
+
"[yellow]Select a local model:[/yellow]\n"
|
|
316
|
+
+ "\n".join(
|
|
317
|
+
[
|
|
318
|
+
f"[magenta]{i + 1}. {model}[/magenta]"
|
|
319
|
+
for i, model in enumerate(ollama_list_of_models) # noqa
|
|
320
|
+
]
|
|
321
|
+
),
|
|
322
|
+
highlight=False,
|
|
323
|
+
)
|
|
324
|
+
model_is_selected = False
|
|
325
|
+
while not model_is_selected:
|
|
326
|
+
model = prompt.ask(
|
|
327
|
+
"[yellow]Enter a number from 1 to "
|
|
328
|
+
f"{len(ollama_list_of_models)}[/yellow]",
|
|
329
|
+
)
|
|
330
|
+
if not (1 <= model <= len(ollama_list_of_models)):
|
|
331
|
+
console.print(
|
|
332
|
+
"[red]Enter a valid number![/red]",
|
|
333
|
+
highlight=False,
|
|
334
|
+
)
|
|
335
|
+
continue
|
|
336
|
+
model = ollama_list_of_models[model - 1]
|
|
337
|
+
model_is_selected = True
|
|
338
|
+
break
|
|
339
|
+
else:
|
|
340
|
+
model = ollama_list_of_models[0]
|
|
341
|
+
else:
|
|
342
|
+
if model not in ollama_list_of_models:
|
|
343
|
+
console.print(
|
|
344
|
+
f"[red]{model} is not an available model!"
|
|
345
|
+
"[/red] "
|
|
346
|
+
"Available models: [yellow]"
|
|
347
|
+
f"{', '.join(ollama_list_of_models)}[/yellow]",
|
|
348
|
+
highlight=False,
|
|
349
|
+
)
|
|
350
|
+
return None
|
|
351
|
+
if model:
|
|
352
|
+
console.print(
|
|
353
|
+
f"Selected model: [yellow]{model}[/yellow]",
|
|
354
|
+
highlight=False,
|
|
355
|
+
)
|
|
356
|
+
# Create AI client
|
|
357
|
+
if use_local_models:
|
|
358
|
+
client = Ollama(model=model)
|
|
359
|
+
else:
|
|
360
|
+
client = MistralAI(
|
|
361
|
+
api_key=mistral_api_key,
|
|
362
|
+
model="mistral-large-latest",
|
|
363
|
+
)
|
|
364
|
+
if not dry_run:
|
|
365
|
+
retry = True
|
|
366
|
+
while retry:
|
|
367
|
+
with console.status(
|
|
368
|
+
"[magenta bold]Generating commit message...",
|
|
369
|
+
spinner_style="magenta",
|
|
370
|
+
):
|
|
371
|
+
commit_message = cut_think(
|
|
372
|
+
client.message(
|
|
373
|
+
messages=[
|
|
374
|
+
{
|
|
375
|
+
"role": "system",
|
|
376
|
+
"content": prompt_for_ai,
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
"role": "user",
|
|
380
|
+
"content": "Git status: "
|
|
381
|
+
+ git_status.stdout
|
|
382
|
+
+ "Git diff: "
|
|
383
|
+
+ git_diff.stdout,
|
|
384
|
+
},
|
|
385
|
+
],
|
|
386
|
+
temperature=temperature,
|
|
387
|
+
timeout=timeout,
|
|
388
|
+
)
|
|
389
|
+
)
|
|
390
|
+
commit_with_message_from_ai = input(
|
|
391
|
+
"Commit with message "
|
|
392
|
+
+ colored(f"'{commit_message}'", "yellow")
|
|
393
|
+
+ "? [y/N/r]: "
|
|
394
|
+
)
|
|
395
|
+
if commit_with_message_from_ai != "r":
|
|
396
|
+
retry = False
|
|
397
|
+
break
|
|
398
|
+
if commit_with_message_from_ai == "y":
|
|
399
|
+
subprocess.run(
|
|
400
|
+
["git", "commit", "-m", f"{commit_message}"],
|
|
401
|
+
encoding="utf-8",
|
|
402
|
+
)
|
|
403
|
+
console.print(
|
|
404
|
+
"Commit created successfully!",
|
|
405
|
+
style="green bold",
|
|
406
|
+
highlight=False,
|
|
407
|
+
)
|
|
408
|
+
else:
|
|
409
|
+
with console.status(
|
|
410
|
+
"[magenta bold]Generating commit message...",
|
|
411
|
+
spinner_style="magenta",
|
|
412
|
+
):
|
|
413
|
+
commit_message = cut_think(
|
|
414
|
+
client.message(
|
|
415
|
+
messages=[
|
|
416
|
+
{
|
|
417
|
+
"role": "system",
|
|
418
|
+
"content": prompt_for_ai,
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
"role": "user",
|
|
422
|
+
"content": "Git status: "
|
|
423
|
+
+ git_status.stdout
|
|
424
|
+
+ "Git diff: "
|
|
425
|
+
+ git_diff.stdout,
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
temperature=temperature,
|
|
429
|
+
timeout=timeout,
|
|
430
|
+
)
|
|
431
|
+
)
|
|
432
|
+
console.print(commit_message, style="yellow", highlight=False)
|
|
433
|
+
return None
|
|
434
|
+
|
|
435
|
+
# If .git does not exist
|
|
436
|
+
else:
|
|
437
|
+
init_git_repo = (
|
|
438
|
+
True
|
|
439
|
+
if input(
|
|
440
|
+
colored("Git repository not initialized!", "red")
|
|
441
|
+
+ " Run "
|
|
442
|
+
+ colored("git init", "yellow")
|
|
443
|
+
+ "? [y/N]: "
|
|
444
|
+
)
|
|
445
|
+
== "y"
|
|
446
|
+
else False
|
|
447
|
+
)
|
|
448
|
+
if init_git_repo:
|
|
449
|
+
subprocess.run(
|
|
450
|
+
["git", "init"],
|
|
451
|
+
capture_output=True,
|
|
452
|
+
encoding="utf-8",
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
(
|
|
456
|
+
(
|
|
457
|
+
subprocess.run(
|
|
458
|
+
["git", "add", "-A"],
|
|
459
|
+
),
|
|
460
|
+
subprocess.run(
|
|
461
|
+
[
|
|
462
|
+
"git",
|
|
463
|
+
"commit",
|
|
464
|
+
"-m",
|
|
465
|
+
"'Initial commit'",
|
|
466
|
+
],
|
|
467
|
+
encoding="utf-8",
|
|
468
|
+
),
|
|
469
|
+
)
|
|
470
|
+
if input(
|
|
471
|
+
"Make first commit with message "
|
|
472
|
+
+ colored("'Initial commit?'", "yellow")
|
|
473
|
+
+ " [y/N]: "
|
|
474
|
+
)
|
|
475
|
+
== "y"
|
|
476
|
+
else None
|
|
477
|
+
)
|
|
478
|
+
except KeyboardInterrupt:
|
|
479
|
+
return None
|
|
480
|
+
except Exception:
|
|
481
|
+
console.print_exception()
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
if __name__ == "__main__":
|
|
485
|
+
main()
|