tgit 0.24.0__tar.gz → 0.26.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.
- {tgit-0.24.0 → tgit-0.26.0}/CHANGELOG.md +29 -0
- {tgit-0.24.0 → tgit-0.26.0}/PKG-INFO +3 -7
- {tgit-0.24.0 → tgit-0.26.0}/README.md +2 -6
- {tgit-0.24.0 → tgit-0.26.0}/pyproject.toml +1 -1
- {tgit-0.24.0 → tgit-0.26.0}/tests/unit/test_commit.py +96 -4
- {tgit-0.24.0 → tgit-0.26.0}/tgit/commit.py +42 -9
- tgit-0.26.0/tgit/constants.py +4 -0
- {tgit-0.24.0 → tgit-0.26.0}/tgit/interactive_settings.py +2 -1
- {tgit-0.24.0 → tgit-0.26.0}/tgit/prompts/commit.txt +18 -5
- {tgit-0.24.0 → tgit-0.26.0}/tgit/types.py +3 -1
- {tgit-0.24.0 → tgit-0.26.0}/tgit/utils/__init__.py +2 -1
- tgit-0.26.0/uv.lock +902 -0
- tgit-0.24.0/uv.lock +0 -781
- {tgit-0.24.0 → tgit-0.26.0}/.claude/settings.local.json +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/.github/workflows/build.yml +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/.github/workflows/ci.yml +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/.github/workflows/claude-code-review.yml +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/.github/workflows/claude.yml +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/.gitignore +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/.python-version +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/.tgit/settings.json +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/.vscode/extensions.json +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/.vscode/launch.json +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/CLAUDE.md +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/pyrightconfig.json +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/scripts/publish.sh +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/scripts/test.sh +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tests/README.md +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tests/__init__.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tests/conftest.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tests/integration/__init__.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tests/integration/test_version_integration.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tests/unit/__init__.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tests/unit/test_add.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tests/unit/test_changelog.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tests/unit/test_cli.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tests/unit/test_interactive_settings.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tests/unit/test_settings.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tests/unit/test_types.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tests/unit/test_utils.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tests/unit/test_version.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tgit/__init__.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tgit/add.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tgit/changelog.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tgit/cli.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tgit/settings.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tgit/shared.py +0 -0
- {tgit-0.24.0 → tgit-0.26.0}/tgit/version.py +0 -0
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
## v0.26.0
|
|
2
|
+
|
|
3
|
+
[v0.25.0...v0.26.0](https://github.com/Jannchie/tgit/compare/v0.25.0...v0.26.0)
|
|
4
|
+
|
|
5
|
+
### :sparkles: Features
|
|
6
|
+
|
|
7
|
+
- **commit**: add secret detection support - By [Jannchie](mailto:jannchie@gmail.com) in [e1f5207](https://github.com/Jannchie/tgit/commit/e1f5207)
|
|
8
|
+
|
|
9
|
+
### :wrench: Chores
|
|
10
|
+
|
|
11
|
+
- **commit**: add default token limit and defaults to commit model - By [Jannchie](mailto:jannchie@gmail.com) in [66d4d45](https://github.com/Jannchie/tgit/commit/66d4d45)
|
|
12
|
+
|
|
13
|
+
## v0.25.0
|
|
14
|
+
|
|
15
|
+
[v0.24.1...v0.25.0](https://github.com/Jannchie/tgit/compare/v0.24.1...v0.25.0)
|
|
16
|
+
|
|
17
|
+
### :sparkles: Features
|
|
18
|
+
|
|
19
|
+
- **ai**: add reasoning support for compatible models - By [Jannchie](mailto:jannchie@gmail.com) in [66d0b41](https://github.com/Jannchie/tgit/commit/66d0b41)
|
|
20
|
+
- **config**: centralize default model and use it - By [Jannchie](mailto:jannchie@gmail.com) in [a4e3ec1](https://github.com/Jannchie/tgit/commit/a4e3ec1)
|
|
21
|
+
|
|
22
|
+
### :wrench: Chores
|
|
23
|
+
|
|
24
|
+
- **constants**: add gpt-5 to reasoning model hints - By [Jannchie](mailto:jannchie@gmail.com) in [bad2722](https://github.com/Jannchie/tgit/commit/bad2722)
|
|
25
|
+
|
|
26
|
+
## v0.24.1
|
|
27
|
+
|
|
28
|
+
[v0.24.0...v0.24.1](https://github.com/Jannchie/tgit/compare/v0.24.0...v0.24.1)
|
|
29
|
+
|
|
1
30
|
## v0.24.0
|
|
2
31
|
|
|
3
32
|
[v0.23.1...v0.24.0](https://github.com/Jannchie/tgit/compare/v0.23.1...v0.24.0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tgit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.26.0
|
|
4
4
|
Summary: Tool for Git Interaction Temptation (tgit): An elegant CLI tool that simplifies and streamlines your Git workflow, making version control a breeze.
|
|
5
5
|
Author-email: Jannchie <jannchie@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -83,10 +83,6 @@ tgit changelog
|
|
|
83
83
|
# Bump version and generate changelog
|
|
84
84
|
tgit version
|
|
85
85
|
|
|
86
|
-
# Interactive settings configuration
|
|
87
|
-
tgit settings
|
|
88
|
-
```
|
|
89
|
-
|
|
90
86
|
### Configuration
|
|
91
87
|
|
|
92
88
|
The easiest way to configure TGIT is through the interactive settings command:
|
|
@@ -99,7 +95,7 @@ tgit settings
|
|
|
99
95
|
This will guide you through setting up:
|
|
100
96
|
|
|
101
97
|
- OpenAI API key for AI-powered commits
|
|
102
|
-
- Preferred AI model (gpt-
|
|
98
|
+
- Preferred AI model (gpt-5-mini, gpt-4.1, etc.)
|
|
103
99
|
- Commit emoji preferences
|
|
104
100
|
- Custom commit types
|
|
105
101
|
|
|
@@ -107,7 +103,7 @@ Alternatively, you can manually create a `.tgit.yaml` file in your project root
|
|
|
107
103
|
|
|
108
104
|
```yaml
|
|
109
105
|
apiKey: "your-openai-api-key"
|
|
110
|
-
model: "gpt-
|
|
106
|
+
model: "gpt-5-mini"
|
|
111
107
|
commit:
|
|
112
108
|
emoji: true
|
|
113
109
|
types:
|
|
@@ -58,10 +58,6 @@ tgit changelog
|
|
|
58
58
|
# Bump version and generate changelog
|
|
59
59
|
tgit version
|
|
60
60
|
|
|
61
|
-
# Interactive settings configuration
|
|
62
|
-
tgit settings
|
|
63
|
-
```
|
|
64
|
-
|
|
65
61
|
### Configuration
|
|
66
62
|
|
|
67
63
|
The easiest way to configure TGIT is through the interactive settings command:
|
|
@@ -74,7 +70,7 @@ tgit settings
|
|
|
74
70
|
This will guide you through setting up:
|
|
75
71
|
|
|
76
72
|
- OpenAI API key for AI-powered commits
|
|
77
|
-
- Preferred AI model (gpt-
|
|
73
|
+
- Preferred AI model (gpt-5-mini, gpt-4.1, etc.)
|
|
78
74
|
- Commit emoji preferences
|
|
79
75
|
- Custom commit types
|
|
80
76
|
|
|
@@ -82,7 +78,7 @@ Alternatively, you can manually create a `.tgit.yaml` file in your project root
|
|
|
82
78
|
|
|
83
79
|
```yaml
|
|
84
80
|
apiKey: "your-openai-api-key"
|
|
85
|
-
model: "gpt-
|
|
81
|
+
model: "gpt-5-mini"
|
|
86
82
|
commit:
|
|
87
83
|
emoji: true
|
|
88
84
|
types:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tgit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.26.0"
|
|
4
4
|
description = "Tool for Git Interaction Temptation (tgit): An elegant CLI tool that simplifies and streamlines your Git workflow, making version control a breeze."
|
|
5
5
|
authors = [{ name = "Jannchie", email = "jannchie@gmail.com" }]
|
|
6
6
|
dependencies = [
|
|
@@ -10,6 +10,7 @@ from click.testing import CliRunner
|
|
|
10
10
|
from tgit.commit import (
|
|
11
11
|
CommitArgs,
|
|
12
12
|
CommitData,
|
|
13
|
+
PotentialSecret,
|
|
13
14
|
TemplateParams,
|
|
14
15
|
get_changed_files_from_status,
|
|
15
16
|
get_file_change_sizes,
|
|
@@ -61,7 +62,7 @@ class TestCommitData:
|
|
|
61
62
|
|
|
62
63
|
def test_commit_data_creation(self):
|
|
63
64
|
"""Test creating CommitData instance."""
|
|
64
|
-
data = CommitData(type="feat", scope="auth", msg="add login functionality", is_breaking=False)
|
|
65
|
+
data = CommitData(type="feat", scope="auth", msg="add login functionality", is_breaking=False, secrets=[])
|
|
65
66
|
assert data.type == "feat"
|
|
66
67
|
assert data.scope == "auth"
|
|
67
68
|
assert data.msg == "add login functionality"
|
|
@@ -69,9 +70,16 @@ class TestCommitData:
|
|
|
69
70
|
|
|
70
71
|
def test_commit_data_with_none_scope(self):
|
|
71
72
|
"""Test CommitData with None scope."""
|
|
72
|
-
data = CommitData(type="fix", scope=None, msg="fix bug", is_breaking=False)
|
|
73
|
+
data = CommitData(type="fix", scope=None, msg="fix bug", is_breaking=False, secrets=[])
|
|
73
74
|
assert data.scope is None
|
|
74
75
|
|
|
76
|
+
def test_commit_data_with_secrets(self):
|
|
77
|
+
"""Test CommitData with suspected secrets."""
|
|
78
|
+
secret = PotentialSecret(file="config.env", description="looks like api key")
|
|
79
|
+
data = CommitData(type="chore", scope=None, msg="update config", is_breaking=False, secrets=[secret])
|
|
80
|
+
assert len(data.secrets) == 1
|
|
81
|
+
assert data.secrets[0].file == "config.env"
|
|
82
|
+
|
|
75
83
|
|
|
76
84
|
class TestGetChangedFilesFromStatus:
|
|
77
85
|
"""Test get_changed_files_from_status function."""
|
|
@@ -296,7 +304,7 @@ class TestGenerateCommitWithAI:
|
|
|
296
304
|
|
|
297
305
|
# Mock the response
|
|
298
306
|
mock_response = Mock()
|
|
299
|
-
mock_commit_data = CommitData(type="feat", scope="auth", msg="add login", is_breaking=False)
|
|
307
|
+
mock_commit_data = CommitData(type="feat", scope="auth", msg="add login", is_breaking=False, secrets=[])
|
|
300
308
|
mock_response.output_parsed = mock_commit_data
|
|
301
309
|
mock_client.responses.parse.return_value = mock_response
|
|
302
310
|
|
|
@@ -306,6 +314,8 @@ class TestGenerateCommitWithAI:
|
|
|
306
314
|
mock_check.assert_called_once()
|
|
307
315
|
mock_create_client.assert_called_once()
|
|
308
316
|
mock_client.responses.parse.assert_called_once()
|
|
317
|
+
_, kwargs = mock_client.responses.parse.call_args
|
|
318
|
+
assert "reasoning" not in kwargs
|
|
309
319
|
|
|
310
320
|
@patch("tgit.commit._check_openai_availability")
|
|
311
321
|
@patch("tgit.commit._create_openai_client")
|
|
@@ -319,6 +329,31 @@ class TestGenerateCommitWithAI:
|
|
|
319
329
|
with pytest.raises(Exception):
|
|
320
330
|
_generate_commit_with_ai("diff content", None, "main")
|
|
321
331
|
|
|
332
|
+
@patch("tgit.commit._check_openai_availability")
|
|
333
|
+
@patch("tgit.commit._create_openai_client")
|
|
334
|
+
@patch("tgit.commit.console")
|
|
335
|
+
@patch("tgit.commit.commit_prompt_template")
|
|
336
|
+
@patch("tgit.commit.settings")
|
|
337
|
+
def test_generate_commit_with_ai_reasoning_model(self, mock_settings, mock_template, mock_console, mock_create_client, mock_check):
|
|
338
|
+
"""Test reasoning effort is added for reasoning-capable models."""
|
|
339
|
+
mock_client = Mock()
|
|
340
|
+
mock_create_client.return_value = mock_client
|
|
341
|
+
mock_template.render.return_value = "system prompt"
|
|
342
|
+
mock_settings.model = "o1-mini"
|
|
343
|
+
|
|
344
|
+
mock_response = Mock()
|
|
345
|
+
mock_commit_data = CommitData(type="fix", scope=None, msg="correct bug", is_breaking=False, secrets=[])
|
|
346
|
+
mock_response.output_parsed = mock_commit_data
|
|
347
|
+
mock_client.responses.parse.return_value = mock_response
|
|
348
|
+
|
|
349
|
+
result = _generate_commit_with_ai("diff content", None, "main")
|
|
350
|
+
|
|
351
|
+
assert result == mock_commit_data
|
|
352
|
+
mock_check.assert_called_once()
|
|
353
|
+
mock_create_client.assert_called_once()
|
|
354
|
+
_, kwargs = mock_client.responses.parse.call_args
|
|
355
|
+
assert kwargs["reasoning"] == {"effort": "minimal"}
|
|
356
|
+
|
|
322
357
|
|
|
323
358
|
class TestGetAICommand:
|
|
324
359
|
"""Test get_ai_command function."""
|
|
@@ -369,7 +404,7 @@ class TestGetAICommand:
|
|
|
369
404
|
mock_repo_instance.active_branch.name = "main"
|
|
370
405
|
mock_settings.commit.emoji = True
|
|
371
406
|
|
|
372
|
-
mock_commit_data = CommitData(type="feat", scope="auth", msg="add login", is_breaking=False)
|
|
407
|
+
mock_commit_data = CommitData(type="feat", scope="auth", msg="add login", is_breaking=False, secrets=[])
|
|
373
408
|
mock_generate.return_value = mock_commit_data
|
|
374
409
|
mock_get_commit_command.return_value = "git commit -m 'feat(auth): add login'"
|
|
375
410
|
|
|
@@ -379,6 +414,63 @@ class TestGetAICommand:
|
|
|
379
414
|
mock_generate.assert_called_once()
|
|
380
415
|
mock_get_commit_command.assert_called_once_with("feat", "auth", "add login", use_emoji=True, is_breaking=False)
|
|
381
416
|
|
|
417
|
+
@patch("tgit.commit.click.confirm")
|
|
418
|
+
@patch("tgit.commit.Path.cwd")
|
|
419
|
+
@patch("tgit.commit.git.Repo")
|
|
420
|
+
@patch("tgit.commit.get_filtered_diff_files")
|
|
421
|
+
@patch("tgit.commit._generate_commit_with_ai")
|
|
422
|
+
@patch("tgit.commit.get_commit_command")
|
|
423
|
+
@patch("tgit.commit.settings")
|
|
424
|
+
def test_get_ai_command_detected_secrets_abort(self, mock_settings, mock_get_commit_command, mock_generate, mock_get_files, mock_repo, mock_cwd, mock_confirm):
|
|
425
|
+
"""Test get_ai_command aborts when secrets are detected and user declines."""
|
|
426
|
+
mock_cwd.return_value = Path(tempfile.gettempdir())
|
|
427
|
+
mock_repo_instance = Mock()
|
|
428
|
+
mock_repo.return_value = mock_repo_instance
|
|
429
|
+
mock_get_files.return_value = (["src/file.py"], [])
|
|
430
|
+
mock_repo_instance.git.diff.return_value = "diff content"
|
|
431
|
+
mock_repo_instance.active_branch.name = "main"
|
|
432
|
+
mock_settings.commit.emoji = True
|
|
433
|
+
|
|
434
|
+
secret = PotentialSecret(file="src/file.py", description="possible api key")
|
|
435
|
+
mock_commit_data = CommitData(type="feat", scope="auth", msg="add login", is_breaking=False, secrets=[secret])
|
|
436
|
+
mock_generate.return_value = mock_commit_data
|
|
437
|
+
mock_confirm.return_value = False
|
|
438
|
+
|
|
439
|
+
result = get_ai_command()
|
|
440
|
+
|
|
441
|
+
assert result is None
|
|
442
|
+
mock_confirm.assert_called_once_with("Detected potential secrets. Continue with commit?", default=False)
|
|
443
|
+
mock_get_commit_command.assert_not_called()
|
|
444
|
+
|
|
445
|
+
@patch("tgit.commit.click.confirm")
|
|
446
|
+
@patch("tgit.commit.Path.cwd")
|
|
447
|
+
@patch("tgit.commit.git.Repo")
|
|
448
|
+
@patch("tgit.commit.get_filtered_diff_files")
|
|
449
|
+
@patch("tgit.commit._generate_commit_with_ai")
|
|
450
|
+
@patch("tgit.commit.get_commit_command")
|
|
451
|
+
@patch("tgit.commit.settings")
|
|
452
|
+
def test_get_ai_command_detected_secrets_continue(self, mock_settings, mock_get_commit_command, mock_generate, mock_get_files, mock_repo, mock_cwd, mock_confirm):
|
|
453
|
+
"""Test get_ai_command continues when secrets are detected and user agrees."""
|
|
454
|
+
mock_cwd.return_value = Path(tempfile.gettempdir())
|
|
455
|
+
mock_repo_instance = Mock()
|
|
456
|
+
mock_repo.return_value = mock_repo_instance
|
|
457
|
+
mock_get_files.return_value = (["src/file.py"], [])
|
|
458
|
+
mock_repo_instance.git.diff.return_value = "diff content"
|
|
459
|
+
mock_repo_instance.active_branch.name = "main"
|
|
460
|
+
mock_settings.commit.emoji = True
|
|
461
|
+
|
|
462
|
+
secret = PotentialSecret(file="src/file.py", description="possible api key")
|
|
463
|
+
mock_commit_data = CommitData(type="feat", scope="auth", msg="add login", is_breaking=False, secrets=[secret])
|
|
464
|
+
mock_generate.return_value = mock_commit_data
|
|
465
|
+
mock_get_commit_command.return_value = "git commit -m 'feat(auth): add login'"
|
|
466
|
+
mock_confirm.return_value = True
|
|
467
|
+
|
|
468
|
+
result = get_ai_command()
|
|
469
|
+
|
|
470
|
+
assert result == "git commit -m 'feat(auth): add login'"
|
|
471
|
+
mock_confirm.assert_called_once_with("Detected potential secrets. Continue with commit?", default=False)
|
|
472
|
+
mock_get_commit_command.assert_called_once_with("feat", "auth", "add login", use_emoji=True, is_breaking=False)
|
|
473
|
+
|
|
382
474
|
@patch("tgit.commit.Path.cwd")
|
|
383
475
|
@patch("tgit.commit.git.Repo")
|
|
384
476
|
@patch("tgit.commit.get_filtered_diff_files")
|
|
@@ -3,14 +3,15 @@ import importlib.resources
|
|
|
3
3
|
import itertools
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
9
|
import git
|
|
10
10
|
from jinja2 import Environment, FileSystemLoader
|
|
11
|
-
from pydantic import BaseModel
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
12
|
from rich import get_console, print
|
|
13
13
|
|
|
14
|
+
from tgit.constants import DEFAULT_MODEL, REASONING_MODEL_HINTS
|
|
14
15
|
from tgit.shared import settings
|
|
15
16
|
from tgit.utils import get_commit_command, run_command, type_emojis
|
|
16
17
|
|
|
@@ -24,6 +25,7 @@ with importlib.resources.path("tgit", "prompts") as prompt_path:
|
|
|
24
25
|
commit_types = ["feat", "fix", "chore", "docs", "style", "refactor", "perf", "wip"]
|
|
25
26
|
commit_file = "commit.txt"
|
|
26
27
|
commit_prompt_template = env.get_template("commit.txt")
|
|
28
|
+
DEFAULT_MAX_OUTPUT_TOKENS = 256
|
|
27
29
|
|
|
28
30
|
# Define click arguments/options at module level to avoid B008
|
|
29
31
|
MESSAGE_ARG = click.argument(
|
|
@@ -64,11 +66,25 @@ class TemplateParams:
|
|
|
64
66
|
specified_type: str | None = None
|
|
65
67
|
|
|
66
68
|
|
|
69
|
+
class PotentialSecret(BaseModel):
|
|
70
|
+
file: str
|
|
71
|
+
description: str
|
|
72
|
+
|
|
73
|
+
|
|
67
74
|
class CommitData(BaseModel):
|
|
68
75
|
type: str
|
|
69
|
-
scope: str | None
|
|
76
|
+
scope: str | None = None
|
|
70
77
|
msg: str
|
|
71
|
-
is_breaking: bool
|
|
78
|
+
is_breaking: bool = False
|
|
79
|
+
secrets: list[PotentialSecret] = Field(default_factory=list)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _supports_reasoning(model: str) -> bool:
|
|
83
|
+
"""Return True when the selected model supports reasoning parameters."""
|
|
84
|
+
if not model:
|
|
85
|
+
return False
|
|
86
|
+
model_lower = model.lower()
|
|
87
|
+
return any(hint in model_lower for hint in REASONING_MODEL_HINTS)
|
|
72
88
|
|
|
73
89
|
|
|
74
90
|
def get_changed_files_from_status(repo: git.Repo) -> set[str]:
|
|
@@ -177,17 +193,24 @@ def _generate_commit_with_ai(diff: str, specified_type: str | None, current_bran
|
|
|
177
193
|
)
|
|
178
194
|
|
|
179
195
|
with console.status("[bold green]Generating commit message...[/bold green]"):
|
|
180
|
-
|
|
181
|
-
|
|
196
|
+
model_name = settings.model or DEFAULT_MODEL
|
|
197
|
+
request_kwargs: dict[str, Any] = {
|
|
198
|
+
"input": [
|
|
182
199
|
{
|
|
183
200
|
"role": "system",
|
|
184
201
|
"content": commit_prompt_template.render(**template_params.__dict__),
|
|
185
202
|
},
|
|
186
203
|
{"role": "user", "content": diff},
|
|
187
204
|
],
|
|
188
|
-
model
|
|
189
|
-
max_output_tokens
|
|
190
|
-
text_format
|
|
205
|
+
"model": model_name,
|
|
206
|
+
"max_output_tokens": DEFAULT_MAX_OUTPUT_TOKENS,
|
|
207
|
+
"text_format": CommitData,
|
|
208
|
+
}
|
|
209
|
+
if _supports_reasoning(model_name):
|
|
210
|
+
request_kwargs["reasoning"] = {"effort": "minimal"}
|
|
211
|
+
|
|
212
|
+
chat_completion = client.responses.parse(
|
|
213
|
+
**request_kwargs,
|
|
191
214
|
)
|
|
192
215
|
|
|
193
216
|
return chat_completion.output_parsed
|
|
@@ -227,6 +250,16 @@ def get_ai_command(specified_type: str | None = None) -> str | None:
|
|
|
227
250
|
print(e)
|
|
228
251
|
return None
|
|
229
252
|
|
|
253
|
+
detected_secrets: list[PotentialSecret] = resp.secrets if resp.secrets else []
|
|
254
|
+
if detected_secrets:
|
|
255
|
+
print("[red]Detected potential secrets in these files:[/red]")
|
|
256
|
+
for secret in detected_secrets:
|
|
257
|
+
print(f"[red]- {secret.file}: {secret.description}[/red]")
|
|
258
|
+
proceed = click.confirm("Detected potential secrets. Continue with commit?", default=False)
|
|
259
|
+
if not proceed:
|
|
260
|
+
print("[yellow]Commit aborted. Please review sensitive content.[/yellow]")
|
|
261
|
+
return None
|
|
262
|
+
|
|
230
263
|
# 如果用户指定了类型,则使用用户指定的类型,否则使用 AI 生成的类型
|
|
231
264
|
commit_type = specified_type if specified_type is not None else resp.type
|
|
232
265
|
|
|
@@ -8,6 +8,7 @@ import questionary
|
|
|
8
8
|
from questionary import Choice
|
|
9
9
|
from rich import print
|
|
10
10
|
|
|
11
|
+
from tgit.constants import DEFAULT_MODEL
|
|
11
12
|
from tgit.utils import load_global_settings, load_workspace_settings
|
|
12
13
|
|
|
13
14
|
|
|
@@ -84,7 +85,7 @@ def _configure_global_settings() -> None:
|
|
|
84
85
|
|
|
85
86
|
model = questionary.text(
|
|
86
87
|
"Model name",
|
|
87
|
-
default=current_settings.get("model",
|
|
88
|
+
default=current_settings.get("model", DEFAULT_MODEL),
|
|
88
89
|
).ask()
|
|
89
90
|
if model is None:
|
|
90
91
|
return
|
|
@@ -34,12 +34,18 @@ Mark `is_breaking: true` only if changes:
|
|
|
34
34
|
- Require user action for compatibility
|
|
35
35
|
- Remove or significantly change existing functionality
|
|
36
36
|
|
|
37
|
+
### Secret Detection
|
|
38
|
+
- Review the diff for potential secrets (API keys, tokens, passwords, private keys, credentials, etc.).
|
|
39
|
+
- For every suspected secret, add an entry to the `secrets` array with the file path and a brief description of why it is sensitive.
|
|
40
|
+
- If no secrets are found, return an empty array.
|
|
41
|
+
|
|
37
42
|
## Analysis Process
|
|
38
43
|
1. Review the diff comprehensively
|
|
39
44
|
2. Identify the primary type of change
|
|
40
45
|
3. Determine appropriate scope from modified files/areas
|
|
41
46
|
4. Craft a concise message covering main changes
|
|
42
47
|
5. Assess backward compatibility impact
|
|
48
|
+
6. Flag any suspected secrets
|
|
43
49
|
|
|
44
50
|
## Output Format
|
|
45
51
|
Return valid JSON matching this structure:
|
|
@@ -48,15 +54,22 @@ Return valid JSON matching this structure:
|
|
|
48
54
|
"type": "string",
|
|
49
55
|
"scope": "string|null",
|
|
50
56
|
"msg": "string",
|
|
51
|
-
"is_breaking": "boolean"
|
|
57
|
+
"is_breaking": "boolean",
|
|
58
|
+
"secrets": [
|
|
59
|
+
{
|
|
60
|
+
"file": "string",
|
|
61
|
+
"description": "string"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
52
64
|
}
|
|
53
65
|
```
|
|
54
66
|
|
|
55
67
|
## Examples
|
|
56
68
|
```json
|
|
57
|
-
{"type": "feat", "scope": "auth", "msg": "add oauth2 login", "is_breaking": false}
|
|
58
|
-
{"type": "fix", "scope": "api", "msg": "handle null user responses", "is_breaking": false}
|
|
59
|
-
{"type": "refactor", "scope": null, "msg": "restructure project layout", "is_breaking": true}
|
|
69
|
+
{"type": "feat", "scope": "auth", "msg": "add oauth2 login", "is_breaking": false, "secrets": []}
|
|
70
|
+
{"type": "fix", "scope": "api", "msg": "handle null user responses", "is_breaking": false, "secrets": []}
|
|
71
|
+
{"type": "refactor", "scope": null, "msg": "restructure project layout", "is_breaking": true, "secrets": []}
|
|
72
|
+
{"type": "chore", "scope": "config", "msg": "update secrets storage", "is_breaking": false, "secrets": [{"file": "config/.env", "description": "detected value resembling api key"}]}
|
|
60
73
|
```
|
|
61
74
|
|
|
62
|
-
Now analyze the provided diff and generate the commit message.
|
|
75
|
+
Now analyze the provided diff and generate the commit message.
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
from typing import TYPE_CHECKING, Any
|
|
5
5
|
|
|
6
|
+
from tgit.constants import DEFAULT_MODEL
|
|
7
|
+
|
|
6
8
|
if TYPE_CHECKING:
|
|
7
9
|
import argparse
|
|
8
10
|
|
|
@@ -31,6 +33,6 @@ class TGitSettings:
|
|
|
31
33
|
commit: CommitSettings = field(default_factory=CommitSettings)
|
|
32
34
|
api_key: str = ""
|
|
33
35
|
api_url: str = ""
|
|
34
|
-
model: str =
|
|
36
|
+
model: str = DEFAULT_MODEL
|
|
35
37
|
show_command: bool = True
|
|
36
38
|
skip_confirm: bool = False
|
|
@@ -8,6 +8,7 @@ import questionary
|
|
|
8
8
|
import rich
|
|
9
9
|
from rich.syntax import Syntax
|
|
10
10
|
|
|
11
|
+
from tgit.constants import DEFAULT_MODEL
|
|
11
12
|
from tgit.types import CommitSettings, CommitType, TGitSettings
|
|
12
13
|
|
|
13
14
|
console = rich.get_console()
|
|
@@ -147,7 +148,7 @@ def _dict_to_settings(data: dict[str, Any]) -> TGitSettings:
|
|
|
147
148
|
commit=commit_settings,
|
|
148
149
|
api_key=data.get("apiKey", ""),
|
|
149
150
|
api_url=data.get("apiUrl", ""),
|
|
150
|
-
model=data.get("model"
|
|
151
|
+
model=data.get("model") or DEFAULT_MODEL,
|
|
151
152
|
show_command=data.get("show_command", True),
|
|
152
153
|
skip_confirm=data.get("skip_confirm", False),
|
|
153
154
|
)
|