tgit 0.25.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.25.0 → tgit-0.26.0}/CHANGELOG.md +12 -0
- {tgit-0.25.0 → tgit-0.26.0}/PKG-INFO +1 -1
- {tgit-0.25.0 → tgit-0.26.0}/pyproject.toml +1 -1
- {tgit-0.25.0 → tgit-0.26.0}/tests/unit/test_commit.py +70 -5
- {tgit-0.25.0 → tgit-0.26.0}/tgit/commit.py +21 -4
- {tgit-0.25.0 → tgit-0.26.0}/tgit/prompts/commit.txt +18 -5
- tgit-0.26.0/uv.lock +902 -0
- tgit-0.25.0/uv.lock +0 -874
- {tgit-0.25.0 → tgit-0.26.0}/.claude/settings.local.json +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/.github/workflows/build.yml +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/.github/workflows/ci.yml +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/.github/workflows/claude-code-review.yml +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/.github/workflows/claude.yml +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/.gitignore +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/.python-version +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/.tgit/settings.json +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/.vscode/extensions.json +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/.vscode/launch.json +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/CLAUDE.md +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/README.md +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/pyrightconfig.json +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/scripts/publish.sh +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/scripts/test.sh +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tests/README.md +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tests/__init__.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tests/conftest.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tests/integration/__init__.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tests/integration/test_version_integration.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tests/unit/__init__.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tests/unit/test_add.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tests/unit/test_changelog.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tests/unit/test_cli.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tests/unit/test_interactive_settings.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tests/unit/test_settings.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tests/unit/test_types.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tests/unit/test_utils.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tests/unit/test_version.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tgit/__init__.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tgit/add.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tgit/changelog.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tgit/cli.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tgit/constants.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tgit/interactive_settings.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tgit/settings.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tgit/shared.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tgit/types.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tgit/utils/__init__.py +0 -0
- {tgit-0.25.0 → tgit-0.26.0}/tgit/version.py +0 -0
|
@@ -1,3 +1,15 @@
|
|
|
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
|
+
|
|
1
13
|
## v0.25.0
|
|
2
14
|
|
|
3
15
|
[v0.24.1...v0.25.0](https://github.com/Jannchie/tgit/compare/v0.24.1...v0.25.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
|
|
@@ -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
|
|
|
@@ -334,7 +342,7 @@ class TestGenerateCommitWithAI:
|
|
|
334
342
|
mock_settings.model = "o1-mini"
|
|
335
343
|
|
|
336
344
|
mock_response = Mock()
|
|
337
|
-
mock_commit_data = CommitData(type="fix", scope=None, msg="correct bug", is_breaking=False)
|
|
345
|
+
mock_commit_data = CommitData(type="fix", scope=None, msg="correct bug", is_breaking=False, secrets=[])
|
|
338
346
|
mock_response.output_parsed = mock_commit_data
|
|
339
347
|
mock_client.responses.parse.return_value = mock_response
|
|
340
348
|
|
|
@@ -396,7 +404,7 @@ class TestGetAICommand:
|
|
|
396
404
|
mock_repo_instance.active_branch.name = "main"
|
|
397
405
|
mock_settings.commit.emoji = True
|
|
398
406
|
|
|
399
|
-
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=[])
|
|
400
408
|
mock_generate.return_value = mock_commit_data
|
|
401
409
|
mock_get_commit_command.return_value = "git commit -m 'feat(auth): add login'"
|
|
402
410
|
|
|
@@ -406,6 +414,63 @@ class TestGetAICommand:
|
|
|
406
414
|
mock_generate.assert_called_once()
|
|
407
415
|
mock_get_commit_command.assert_called_once_with("feat", "auth", "add login", use_emoji=True, is_breaking=False)
|
|
408
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
|
+
|
|
409
474
|
@patch("tgit.commit.Path.cwd")
|
|
410
475
|
@patch("tgit.commit.git.Repo")
|
|
411
476
|
@patch("tgit.commit.get_filtered_diff_files")
|
|
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
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
14
|
from tgit.constants import DEFAULT_MODEL, REASONING_MODEL_HINTS
|
|
@@ -25,6 +25,7 @@ with importlib.resources.path("tgit", "prompts") as prompt_path:
|
|
|
25
25
|
commit_types = ["feat", "fix", "chore", "docs", "style", "refactor", "perf", "wip"]
|
|
26
26
|
commit_file = "commit.txt"
|
|
27
27
|
commit_prompt_template = env.get_template("commit.txt")
|
|
28
|
+
DEFAULT_MAX_OUTPUT_TOKENS = 256
|
|
28
29
|
|
|
29
30
|
# Define click arguments/options at module level to avoid B008
|
|
30
31
|
MESSAGE_ARG = click.argument(
|
|
@@ -65,11 +66,17 @@ class TemplateParams:
|
|
|
65
66
|
specified_type: str | None = None
|
|
66
67
|
|
|
67
68
|
|
|
69
|
+
class PotentialSecret(BaseModel):
|
|
70
|
+
file: str
|
|
71
|
+
description: str
|
|
72
|
+
|
|
73
|
+
|
|
68
74
|
class CommitData(BaseModel):
|
|
69
75
|
type: str
|
|
70
|
-
scope: str | None
|
|
76
|
+
scope: str | None = None
|
|
71
77
|
msg: str
|
|
72
|
-
is_breaking: bool
|
|
78
|
+
is_breaking: bool = False
|
|
79
|
+
secrets: list[PotentialSecret] = Field(default_factory=list)
|
|
73
80
|
|
|
74
81
|
|
|
75
82
|
def _supports_reasoning(model: str) -> bool:
|
|
@@ -196,7 +203,7 @@ def _generate_commit_with_ai(diff: str, specified_type: str | None, current_bran
|
|
|
196
203
|
{"role": "user", "content": diff},
|
|
197
204
|
],
|
|
198
205
|
"model": model_name,
|
|
199
|
-
"max_output_tokens":
|
|
206
|
+
"max_output_tokens": DEFAULT_MAX_OUTPUT_TOKENS,
|
|
200
207
|
"text_format": CommitData,
|
|
201
208
|
}
|
|
202
209
|
if _supports_reasoning(model_name):
|
|
@@ -243,6 +250,16 @@ def get_ai_command(specified_type: str | None = None) -> str | None:
|
|
|
243
250
|
print(e)
|
|
244
251
|
return None
|
|
245
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
|
+
|
|
246
263
|
# 如果用户指定了类型,则使用用户指定的类型,否则使用 AI 生成的类型
|
|
247
264
|
commit_type = specified_type if specified_type is not None else resp.type
|
|
248
265
|
|
|
@@ -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.
|