tgit 0.26.0__tar.gz → 0.27.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.26.0 → tgit-0.27.0}/CHANGELOG.md +20 -0
- {tgit-0.26.0 → tgit-0.27.0}/PKG-INFO +2 -1
- {tgit-0.26.0 → tgit-0.27.0}/README.md +1 -0
- {tgit-0.26.0 → tgit-0.27.0}/pyproject.toml +2 -1
- {tgit-0.26.0 → tgit-0.27.0}/tests/integration/test_version_integration.py +6 -6
- tgit-0.27.0/tests/unit/test_changelog_coverage.py +78 -0
- {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_commit.py +39 -0
- tgit-0.27.0/tests/unit/test_commit_coverage.py +74 -0
- tgit-0.27.0/tests/unit/test_utils_coverage.py +120 -0
- {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_version.py +1 -1
- tgit-0.27.0/tests/unit/test_version_coverage.py +303 -0
- {tgit-0.26.0 → tgit-0.27.0}/tgit/commit.py +52 -11
- {tgit-0.26.0 → tgit-0.27.0}/tgit/prompts/commit.txt +6 -3
- {tgit-0.26.0 → tgit-0.27.0}/tgit/version.py +22 -7
- tgit-0.27.0/uv.lock +905 -0
- tgit-0.26.0/uv.lock +0 -902
- {tgit-0.26.0 → tgit-0.27.0}/.claude/settings.local.json +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/.github/workflows/build.yml +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/.github/workflows/ci.yml +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/.github/workflows/claude-code-review.yml +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/.github/workflows/claude.yml +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/.gitignore +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/.python-version +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/.tgit/settings.json +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/.vscode/extensions.json +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/.vscode/launch.json +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/CLAUDE.md +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/pyrightconfig.json +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/scripts/publish.sh +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/scripts/test.sh +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tests/README.md +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tests/__init__.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tests/conftest.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tests/integration/__init__.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tests/unit/__init__.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_add.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_changelog.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_cli.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_interactive_settings.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_settings.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_types.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_utils.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tgit/__init__.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tgit/add.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tgit/changelog.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tgit/cli.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tgit/constants.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tgit/interactive_settings.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tgit/settings.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tgit/shared.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tgit/types.py +0 -0
- {tgit-0.26.0 → tgit-0.27.0}/tgit/utils/__init__.py +0 -0
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## v0.27.0
|
|
2
|
+
|
|
3
|
+
[v0.26.0...v0.27.0](https://github.com/Jannchie/tgit/compare/v0.26.0...v0.27.0)
|
|
4
|
+
|
|
5
|
+
### :sparkles: Features
|
|
6
|
+
|
|
7
|
+
- **secrets**: add two-level secret scanning and handling - By [Jannchie](mailto:jannchie@gmail.com) in [fd4d632](https://github.com/Jannchie/tgit/commit/fd4d632)
|
|
8
|
+
|
|
9
|
+
### :adhesive_bandage: Fixes
|
|
10
|
+
|
|
11
|
+
- **version**: fix typo: reclusive -> recursive - By [Jannchie](mailto:jannchie@gmail.com) in [37c88f5](https://github.com/Jannchie/tgit/commit/37c88f5)
|
|
12
|
+
|
|
13
|
+
### :art: Refactors
|
|
14
|
+
|
|
15
|
+
- **ai**: refactor ai commit generation flow - By [Jannchie](mailto:jannchie@gmail.com) in [dd3ac63](https://github.com/Jannchie/tgit/commit/dd3ac63)
|
|
16
|
+
|
|
17
|
+
### :memo: Documentation
|
|
18
|
+
|
|
19
|
+
- **readme**: mention secret scanning warnings - By [Jannchie](mailto:jannchie@gmail.com) in [10037b6](https://github.com/Jannchie/tgit/commit/10037b6)
|
|
20
|
+
|
|
1
21
|
## v0.26.0
|
|
2
22
|
|
|
3
23
|
[v0.25.0...v0.26.0](https://github.com/Jannchie/tgit/compare/v0.25.0...v0.26.0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tgit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.27.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
|
|
@@ -41,6 +41,7 @@ An elegant CLI tool that simplifies and streamlines your Git workflow with AI-po
|
|
|
41
41
|
- Smart diff analysis that focuses on meaningful changes
|
|
42
42
|
- Support for custom commit types and emojis
|
|
43
43
|
- Breaking change detection
|
|
44
|
+
- Two-level secret scanning: prompts (default yes) for key names and blocking prompts for exposed values
|
|
44
45
|
|
|
45
46
|
### 📝 Automated Changelog
|
|
46
47
|
|
|
@@ -16,6 +16,7 @@ An elegant CLI tool that simplifies and streamlines your Git workflow with AI-po
|
|
|
16
16
|
- Smart diff analysis that focuses on meaningful changes
|
|
17
17
|
- Support for custom commit types and emojis
|
|
18
18
|
- Breaking change detection
|
|
19
|
+
- Two-level secret scanning: prompts (default yes) for key names and blocking prompts for exposed values
|
|
19
20
|
|
|
20
21
|
### 📝 Automated Changelog
|
|
21
22
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tgit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.27.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 = [
|
|
@@ -112,6 +112,7 @@ exclude_lines = [
|
|
|
112
112
|
"if __name__ == .__main__.:",
|
|
113
113
|
"class .*\\bProtocol\\):",
|
|
114
114
|
"@(abc\\.)?abstractmethod",
|
|
115
|
+
"if TYPE_CHECKING:",
|
|
115
116
|
]
|
|
116
117
|
|
|
117
118
|
[dependency-groups]
|
|
@@ -127,7 +127,7 @@ dependencies = []
|
|
|
127
127
|
next_version = Version(major=1, minor=1, patch=0)
|
|
128
128
|
|
|
129
129
|
# Update version files
|
|
130
|
-
update_version_files(args, next_version, verbose=0,
|
|
130
|
+
update_version_files(args, next_version, verbose=0, recursive=False)
|
|
131
131
|
|
|
132
132
|
# Verify update
|
|
133
133
|
updated_content = json.loads(package_json.read_text())
|
|
@@ -171,7 +171,7 @@ description = "Test package"
|
|
|
171
171
|
next_version = Version(major=2, minor=0, patch=0)
|
|
172
172
|
|
|
173
173
|
# Update version files
|
|
174
|
-
update_version_files(args, next_version, verbose=0,
|
|
174
|
+
update_version_files(args, next_version, verbose=0, recursive=False)
|
|
175
175
|
|
|
176
176
|
# Verify update
|
|
177
177
|
updated_content = pyproject_toml.read_text()
|
|
@@ -208,7 +208,7 @@ description = "Test package"
|
|
|
208
208
|
next_version = Version(major=1, minor=0, patch=1)
|
|
209
209
|
|
|
210
210
|
# Update version files
|
|
211
|
-
update_version_files(args, next_version, verbose=0,
|
|
211
|
+
update_version_files(args, next_version, verbose=0, recursive=False)
|
|
212
212
|
|
|
213
213
|
# Verify updates
|
|
214
214
|
updated_package_json = json.loads(package_json.read_text())
|
|
@@ -251,7 +251,7 @@ description = "Test package"
|
|
|
251
251
|
next_version = Version(major=1, minor=1, patch=0)
|
|
252
252
|
|
|
253
253
|
# Update version files
|
|
254
|
-
update_version_files(args, next_version, verbose=0,
|
|
254
|
+
update_version_files(args, next_version, verbose=0, recursive=True)
|
|
255
255
|
|
|
256
256
|
# Verify updates in both directories
|
|
257
257
|
root_content = json.loads(root_package_json.read_text())
|
|
@@ -295,7 +295,7 @@ description = "Test package"
|
|
|
295
295
|
next_version = Version(major=2, minor=0, patch=0)
|
|
296
296
|
|
|
297
297
|
# Update version files
|
|
298
|
-
update_version_files(args, next_version, verbose=0,
|
|
298
|
+
update_version_files(args, next_version, verbose=0, recursive=True)
|
|
299
299
|
|
|
300
300
|
# Verify root package.json was updated
|
|
301
301
|
root_content = json.loads(root_package_json.read_text())
|
|
@@ -418,7 +418,7 @@ class TestVersionGitIntegration:
|
|
|
418
418
|
|
|
419
419
|
# Update version files
|
|
420
420
|
next_version = Version(major=1, minor=0, patch=1)
|
|
421
|
-
update_version_files(args, next_version, verbose=0,
|
|
421
|
+
update_version_files(args, next_version, verbose=0, recursive=False)
|
|
422
422
|
|
|
423
423
|
# Verify version was updated
|
|
424
424
|
updated_content = json.loads(package_json.read_text())
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from unittest.mock import Mock, patch
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from tgit.changelog import (
|
|
5
|
+
get_changelog_by_range,
|
|
6
|
+
get_commits,
|
|
7
|
+
prepare_changelog_segments,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestChangelogCoverage:
|
|
12
|
+
"""Additional tests to increase coverage for changelog.py."""
|
|
13
|
+
|
|
14
|
+
@patch("tgit.changelog.commit_pattern")
|
|
15
|
+
def test_get_commits_bytes_message(self, mock_pattern):
|
|
16
|
+
"""Test get_commits with bytes message."""
|
|
17
|
+
repo = Mock()
|
|
18
|
+
commit = Mock()
|
|
19
|
+
commit.message = b"feat: bytes message"
|
|
20
|
+
repo.iter_commits.return_value = [commit]
|
|
21
|
+
|
|
22
|
+
mock_pattern.match.return_value = None # Just to avoid further processing
|
|
23
|
+
|
|
24
|
+
get_commits(repo, "HEAD~1", "HEAD")
|
|
25
|
+
|
|
26
|
+
# Verify message was decoded
|
|
27
|
+
# Since we mocked match to return None, we can't verify the result list,
|
|
28
|
+
# but we can verify that no error occurred during decoding.
|
|
29
|
+
|
|
30
|
+
@patch("tgit.changelog.commit_pattern")
|
|
31
|
+
def test_get_commits_non_string_message(self, mock_pattern):
|
|
32
|
+
"""Test get_commits with non-string message."""
|
|
33
|
+
repo = Mock()
|
|
34
|
+
commit = Mock()
|
|
35
|
+
commit.message = 12345
|
|
36
|
+
repo.iter_commits.return_value = [commit]
|
|
37
|
+
|
|
38
|
+
mock_pattern.match.return_value = None
|
|
39
|
+
|
|
40
|
+
get_commits(repo, "HEAD~1", "HEAD")
|
|
41
|
+
|
|
42
|
+
def test_prepare_changelog_segments_with_latest_tag_in_file(self):
|
|
43
|
+
"""Test prepare_changelog_segments with latest_tag_in_file."""
|
|
44
|
+
repo = Mock()
|
|
45
|
+
tag1 = Mock()
|
|
46
|
+
tag1.name = "v1.0.0"
|
|
47
|
+
tag1.commit.hexsha = "hash1"
|
|
48
|
+
tag1.commit.committed_datetime = 1000
|
|
49
|
+
|
|
50
|
+
tag2 = Mock()
|
|
51
|
+
tag2.name = "v1.1.0"
|
|
52
|
+
tag2.commit.hexsha = "hash2"
|
|
53
|
+
tag2.commit.committed_datetime = 2000
|
|
54
|
+
|
|
55
|
+
repo.tags = [tag1, tag2]
|
|
56
|
+
repo.iter_commits.return_value = [Mock(hexsha="init")]
|
|
57
|
+
|
|
58
|
+
# Mock get_first_commit_hash
|
|
59
|
+
with patch("tgit.changelog.get_first_commit_hash", return_value="init"):
|
|
60
|
+
segments = prepare_changelog_segments(repo, latest_tag_in_file="v1.0.0")
|
|
61
|
+
|
|
62
|
+
assert len(segments) == 1
|
|
63
|
+
assert segments[0].from_name == "v1.0.0"
|
|
64
|
+
assert segments[0].to_name == "v1.1.0"
|
|
65
|
+
|
|
66
|
+
@patch("tgit.changelog.get_commits")
|
|
67
|
+
@patch("tgit.changelog.group_commits_by_type")
|
|
68
|
+
@patch("tgit.changelog.generate_changelog")
|
|
69
|
+
def test_get_changelog_by_range_no_remote(self, mock_gen, mock_group, mock_get_commits):
|
|
70
|
+
"""Test get_changelog_by_range when remote is missing."""
|
|
71
|
+
repo = Mock()
|
|
72
|
+
repo.remote.side_effect = ValueError("No remote")
|
|
73
|
+
|
|
74
|
+
with pytest.warns(UserWarning, match="Origin not found"):
|
|
75
|
+
get_changelog_by_range(repo, "HEAD~1", "HEAD")
|
|
76
|
+
|
|
77
|
+
mock_gen.assert_called_once()
|
|
78
|
+
assert mock_gen.call_args[0][3] is None # remote_uri should be None
|
|
@@ -79,6 +79,7 @@ class TestCommitData:
|
|
|
79
79
|
data = CommitData(type="chore", scope=None, msg="update config", is_breaking=False, secrets=[secret])
|
|
80
80
|
assert len(data.secrets) == 1
|
|
81
81
|
assert data.secrets[0].file == "config.env"
|
|
82
|
+
assert data.secrets[0].level == "error"
|
|
82
83
|
|
|
83
84
|
|
|
84
85
|
class TestGetChangedFilesFromStatus:
|
|
@@ -471,6 +472,44 @@ class TestGetAICommand:
|
|
|
471
472
|
mock_confirm.assert_called_once_with("Detected potential secrets. Continue with commit?", default=False)
|
|
472
473
|
mock_get_commit_command.assert_called_once_with("feat", "auth", "add login", use_emoji=True, is_breaking=False)
|
|
473
474
|
|
|
475
|
+
@patch("tgit.commit.click.confirm")
|
|
476
|
+
@patch("tgit.commit.Path.cwd")
|
|
477
|
+
@patch("tgit.commit.git.Repo")
|
|
478
|
+
@patch("tgit.commit.get_filtered_diff_files")
|
|
479
|
+
@patch("tgit.commit._generate_commit_with_ai")
|
|
480
|
+
@patch("tgit.commit.get_commit_command")
|
|
481
|
+
@patch("tgit.commit.settings")
|
|
482
|
+
def test_get_ai_command_detected_secrets_warning_only(
|
|
483
|
+
self,
|
|
484
|
+
mock_settings,
|
|
485
|
+
mock_get_commit_command,
|
|
486
|
+
mock_generate,
|
|
487
|
+
mock_get_files,
|
|
488
|
+
mock_repo,
|
|
489
|
+
mock_cwd,
|
|
490
|
+
mock_confirm,
|
|
491
|
+
):
|
|
492
|
+
"""Test get_ai_command continues when only warning-level secrets are detected."""
|
|
493
|
+
mock_cwd.return_value = Path(tempfile.gettempdir())
|
|
494
|
+
mock_repo_instance = Mock()
|
|
495
|
+
mock_repo.return_value = mock_repo_instance
|
|
496
|
+
mock_get_files.return_value = (["src/file.py"], [])
|
|
497
|
+
mock_repo_instance.git.diff.return_value = "diff content"
|
|
498
|
+
mock_repo_instance.active_branch.name = "main"
|
|
499
|
+
mock_settings.commit.emoji = True
|
|
500
|
+
|
|
501
|
+
secret = PotentialSecret(file="src/file.py", description="api key name only", level="warning")
|
|
502
|
+
mock_commit_data = CommitData(type="feat", scope="auth", msg="add login", is_breaking=False, secrets=[secret])
|
|
503
|
+
mock_generate.return_value = mock_commit_data
|
|
504
|
+
mock_get_commit_command.return_value = "git commit -m 'feat(auth): add login'"
|
|
505
|
+
mock_confirm.return_value = True
|
|
506
|
+
|
|
507
|
+
result = get_ai_command()
|
|
508
|
+
|
|
509
|
+
assert result == "git commit -m 'feat(auth): add login'"
|
|
510
|
+
mock_confirm.assert_called_once_with("Detected potential sensitive key names. Continue with commit?", default=True)
|
|
511
|
+
mock_get_commit_command.assert_called_once_with("feat", "auth", "add login", use_emoji=True, is_breaking=False)
|
|
512
|
+
|
|
474
513
|
@patch("tgit.commit.Path.cwd")
|
|
475
514
|
@patch("tgit.commit.git.Repo")
|
|
476
515
|
@patch("tgit.commit.get_filtered_diff_files")
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from unittest.mock import Mock, patch
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from tgit.commit import (
|
|
5
|
+
CommitArgs,
|
|
6
|
+
_supports_reasoning,
|
|
7
|
+
get_ai_command,
|
|
8
|
+
get_file_change_sizes,
|
|
9
|
+
handle_commit,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestCommitCoverage:
|
|
14
|
+
"""Additional tests to increase coverage for commit.py."""
|
|
15
|
+
|
|
16
|
+
def test_supports_reasoning_empty(self):
|
|
17
|
+
"""Test _supports_reasoning with empty model."""
|
|
18
|
+
assert _supports_reasoning("") is False
|
|
19
|
+
assert _supports_reasoning(None) is False
|
|
20
|
+
|
|
21
|
+
def test_get_file_change_sizes_value_error(self):
|
|
22
|
+
"""Test get_file_change_sizes with invalid numstat."""
|
|
23
|
+
repo = Mock()
|
|
24
|
+
# Simulate a case where numstat returns invalid integer
|
|
25
|
+
repo.git.diff.return_value = "invalid\tinvalid\timage.png"
|
|
26
|
+
|
|
27
|
+
sizes = get_file_change_sizes(repo)
|
|
28
|
+
assert sizes["image.png"] == 0
|
|
29
|
+
|
|
30
|
+
@patch("tgit.commit.git.Repo")
|
|
31
|
+
@patch("tgit.commit.get_filtered_diff_files")
|
|
32
|
+
def test_get_ai_command_no_diff(self, mock_filter, mock_repo):
|
|
33
|
+
"""Test get_ai_command when there is no diff after filtering."""
|
|
34
|
+
repo = Mock()
|
|
35
|
+
mock_repo.return_value = repo
|
|
36
|
+
|
|
37
|
+
# Return some files to pass the first check
|
|
38
|
+
mock_filter.return_value = (["file.txt"], [])
|
|
39
|
+
|
|
40
|
+
# But make git diff return empty string
|
|
41
|
+
repo.git.diff.return_value = ""
|
|
42
|
+
repo.active_branch.name = "main"
|
|
43
|
+
|
|
44
|
+
assert get_ai_command() is None
|
|
45
|
+
|
|
46
|
+
@patch("tgit.commit.git.Repo")
|
|
47
|
+
@patch("tgit.commit.get_filtered_diff_files")
|
|
48
|
+
@patch("tgit.commit._generate_commit_with_ai")
|
|
49
|
+
def test_get_ai_command_ai_failure(self, mock_gen, mock_filter, mock_repo):
|
|
50
|
+
"""Test get_ai_command when AI generation fails."""
|
|
51
|
+
repo = Mock()
|
|
52
|
+
mock_repo.return_value = repo
|
|
53
|
+
mock_filter.return_value = (["file.txt"], [])
|
|
54
|
+
repo.git.diff.return_value = "diff content"
|
|
55
|
+
repo.active_branch.name = "main"
|
|
56
|
+
|
|
57
|
+
mock_gen.return_value = None
|
|
58
|
+
|
|
59
|
+
assert get_ai_command() is None
|
|
60
|
+
|
|
61
|
+
@patch("tgit.commit.get_ai_command")
|
|
62
|
+
def test_handle_commit_ai_command_none(self, mock_get_ai):
|
|
63
|
+
"""Test handle_commit when get_ai_command returns None."""
|
|
64
|
+
mock_get_ai.return_value = None
|
|
65
|
+
|
|
66
|
+
args = CommitArgs(
|
|
67
|
+
message=["feat"],
|
|
68
|
+
emoji=False,
|
|
69
|
+
breaking=False,
|
|
70
|
+
ai=False,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
handle_commit(args)
|
|
74
|
+
# Should return without error and without running command
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from unittest.mock import Mock, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from click.testing import CliRunner
|
|
7
|
+
from tgit.cli import app
|
|
8
|
+
from tgit.utils import (
|
|
9
|
+
load_global_settings,
|
|
10
|
+
load_workspace_settings,
|
|
11
|
+
set_global_settings,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestUtilsCoverage:
|
|
16
|
+
"""Additional tests to increase coverage for utils and cli."""
|
|
17
|
+
|
|
18
|
+
def test_load_global_settings_empty_json(self, tmp_path):
|
|
19
|
+
"""Test load_global_settings with empty JSON (returns None)."""
|
|
20
|
+
settings_path = tmp_path / ".tgit" / "settings.json"
|
|
21
|
+
settings_path.parent.mkdir(parents=True)
|
|
22
|
+
settings_path.write_text("null")
|
|
23
|
+
|
|
24
|
+
with patch("tgit.utils.Path.home", return_value=tmp_path):
|
|
25
|
+
settings = load_global_settings()
|
|
26
|
+
assert settings == {}
|
|
27
|
+
|
|
28
|
+
def test_load_workspace_settings_empty_json(self, tmp_path):
|
|
29
|
+
"""Test load_workspace_settings with empty JSON (returns None)."""
|
|
30
|
+
settings_path = tmp_path / ".tgit" / "settings.json"
|
|
31
|
+
settings_path.parent.mkdir(parents=True)
|
|
32
|
+
settings_path.write_text("null")
|
|
33
|
+
|
|
34
|
+
with patch("tgit.utils.Path.cwd", return_value=tmp_path):
|
|
35
|
+
settings = load_workspace_settings()
|
|
36
|
+
assert settings == {}
|
|
37
|
+
|
|
38
|
+
def test_set_global_settings(self, tmp_path):
|
|
39
|
+
"""Test set_global_settings."""
|
|
40
|
+
with patch("tgit.utils.Path.home", return_value=tmp_path):
|
|
41
|
+
set_global_settings("test_key", "test_value")
|
|
42
|
+
|
|
43
|
+
settings_path = tmp_path / ".tgit" / "settings.json"
|
|
44
|
+
assert settings_path.exists()
|
|
45
|
+
content = json.loads(settings_path.read_text())
|
|
46
|
+
assert content["test_key"] == "test_value"
|
|
47
|
+
|
|
48
|
+
# Test updating existing settings
|
|
49
|
+
set_global_settings("test_key_2", "test_value_2")
|
|
50
|
+
content = json.loads(settings_path.read_text())
|
|
51
|
+
assert content["test_key"] == "test_value"
|
|
52
|
+
assert content["test_key_2"] == "test_value_2"
|
|
53
|
+
|
|
54
|
+
def test_set_global_settings_with_null_file(self, tmp_path):
|
|
55
|
+
"""Test set_global_settings when file exists but is null."""
|
|
56
|
+
settings_path = tmp_path / ".tgit" / "settings.json"
|
|
57
|
+
settings_path.parent.mkdir(parents=True)
|
|
58
|
+
settings_path.write_text("null")
|
|
59
|
+
|
|
60
|
+
with patch("tgit.utils.Path.home", return_value=tmp_path):
|
|
61
|
+
set_global_settings("test_key", "test_value")
|
|
62
|
+
|
|
63
|
+
content = json.loads(settings_path.read_text())
|
|
64
|
+
assert content["test_key"] == "test_value"
|
|
65
|
+
|
|
66
|
+
def test_load_global_settings_missing_file(self, tmp_path):
|
|
67
|
+
"""Test load_global_settings when file is missing."""
|
|
68
|
+
with patch("tgit.utils.Path.home", return_value=tmp_path):
|
|
69
|
+
settings = load_global_settings()
|
|
70
|
+
assert settings == {}
|
|
71
|
+
|
|
72
|
+
def test_load_workspace_settings_missing_file(self, tmp_path):
|
|
73
|
+
"""Test load_workspace_settings when file is missing."""
|
|
74
|
+
with patch("tgit.utils.Path.cwd", return_value=tmp_path):
|
|
75
|
+
settings = load_workspace_settings()
|
|
76
|
+
assert settings == {}
|
|
77
|
+
|
|
78
|
+
@patch("tgit.cli.threading.Thread")
|
|
79
|
+
def test_cli_app_import_openai(self, mock_thread):
|
|
80
|
+
"""Test cli app triggers openai import."""
|
|
81
|
+
# Mock Thread to run target immediately
|
|
82
|
+
def run_target(target=None, **kwargs):
|
|
83
|
+
target()
|
|
84
|
+
return Mock()
|
|
85
|
+
|
|
86
|
+
mock_thread.side_effect = run_target
|
|
87
|
+
|
|
88
|
+
runner = CliRunner()
|
|
89
|
+
# Invoke with a subcommand to ensure group function runs
|
|
90
|
+
# We use a non-existent command to trigger group execution before error?
|
|
91
|
+
# Or use 'version' command if available.
|
|
92
|
+
# Let's use 'settings' command which is added.
|
|
93
|
+
result = runner.invoke(app, ["settings", "--help"])
|
|
94
|
+
|
|
95
|
+
assert result.exit_code == 0
|
|
96
|
+
mock_thread.assert_called()
|
|
97
|
+
|
|
98
|
+
@patch("tgit.cli.threading.Thread")
|
|
99
|
+
def test_cli_app_import_openai_exception(self, mock_thread):
|
|
100
|
+
"""Test cli app handles openai import exception."""
|
|
101
|
+
def run_target(target=None, **kwargs):
|
|
102
|
+
# Mock import to raise exception
|
|
103
|
+
with patch("builtins.__import__", side_effect=ImportError("fail")):
|
|
104
|
+
target()
|
|
105
|
+
return Mock()
|
|
106
|
+
|
|
107
|
+
mock_thread.side_effect = run_target
|
|
108
|
+
|
|
109
|
+
runner = CliRunner()
|
|
110
|
+
result = runner.invoke(app, ["settings", "--help"])
|
|
111
|
+
|
|
112
|
+
assert result.exit_code == 0
|
|
113
|
+
mock_thread.assert_called()
|
|
114
|
+
|
|
115
|
+
def test_version_callback(self):
|
|
116
|
+
"""Test version callback."""
|
|
117
|
+
runner = CliRunner()
|
|
118
|
+
result = runner.invoke(app, ["--version"])
|
|
119
|
+
assert result.exit_code == 0
|
|
120
|
+
assert "TGIT - ver." in result.output
|
|
@@ -2186,7 +2186,7 @@ version = "2.0.0"
|
|
|
2186
2186
|
)
|
|
2187
2187
|
next_version = Version(2, 0, 0)
|
|
2188
2188
|
|
|
2189
|
-
update_version_files(args, next_version, 1,
|
|
2189
|
+
update_version_files(args, next_version, 1, recursive=True)
|
|
2190
2190
|
|
|
2191
2191
|
# Check verbose output
|
|
2192
2192
|
mock_console.print.assert_any_call("Current path: [cyan bold]/test")
|