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.
Files changed (52) hide show
  1. {tgit-0.26.0 → tgit-0.27.0}/CHANGELOG.md +20 -0
  2. {tgit-0.26.0 → tgit-0.27.0}/PKG-INFO +2 -1
  3. {tgit-0.26.0 → tgit-0.27.0}/README.md +1 -0
  4. {tgit-0.26.0 → tgit-0.27.0}/pyproject.toml +2 -1
  5. {tgit-0.26.0 → tgit-0.27.0}/tests/integration/test_version_integration.py +6 -6
  6. tgit-0.27.0/tests/unit/test_changelog_coverage.py +78 -0
  7. {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_commit.py +39 -0
  8. tgit-0.27.0/tests/unit/test_commit_coverage.py +74 -0
  9. tgit-0.27.0/tests/unit/test_utils_coverage.py +120 -0
  10. {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_version.py +1 -1
  11. tgit-0.27.0/tests/unit/test_version_coverage.py +303 -0
  12. {tgit-0.26.0 → tgit-0.27.0}/tgit/commit.py +52 -11
  13. {tgit-0.26.0 → tgit-0.27.0}/tgit/prompts/commit.txt +6 -3
  14. {tgit-0.26.0 → tgit-0.27.0}/tgit/version.py +22 -7
  15. tgit-0.27.0/uv.lock +905 -0
  16. tgit-0.26.0/uv.lock +0 -902
  17. {tgit-0.26.0 → tgit-0.27.0}/.claude/settings.local.json +0 -0
  18. {tgit-0.26.0 → tgit-0.27.0}/.github/workflows/build.yml +0 -0
  19. {tgit-0.26.0 → tgit-0.27.0}/.github/workflows/ci.yml +0 -0
  20. {tgit-0.26.0 → tgit-0.27.0}/.github/workflows/claude-code-review.yml +0 -0
  21. {tgit-0.26.0 → tgit-0.27.0}/.github/workflows/claude.yml +0 -0
  22. {tgit-0.26.0 → tgit-0.27.0}/.gitignore +0 -0
  23. {tgit-0.26.0 → tgit-0.27.0}/.python-version +0 -0
  24. {tgit-0.26.0 → tgit-0.27.0}/.tgit/settings.json +0 -0
  25. {tgit-0.26.0 → tgit-0.27.0}/.vscode/extensions.json +0 -0
  26. {tgit-0.26.0 → tgit-0.27.0}/.vscode/launch.json +0 -0
  27. {tgit-0.26.0 → tgit-0.27.0}/CLAUDE.md +0 -0
  28. {tgit-0.26.0 → tgit-0.27.0}/pyrightconfig.json +0 -0
  29. {tgit-0.26.0 → tgit-0.27.0}/scripts/publish.sh +0 -0
  30. {tgit-0.26.0 → tgit-0.27.0}/scripts/test.sh +0 -0
  31. {tgit-0.26.0 → tgit-0.27.0}/tests/README.md +0 -0
  32. {tgit-0.26.0 → tgit-0.27.0}/tests/__init__.py +0 -0
  33. {tgit-0.26.0 → tgit-0.27.0}/tests/conftest.py +0 -0
  34. {tgit-0.26.0 → tgit-0.27.0}/tests/integration/__init__.py +0 -0
  35. {tgit-0.26.0 → tgit-0.27.0}/tests/unit/__init__.py +0 -0
  36. {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_add.py +0 -0
  37. {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_changelog.py +0 -0
  38. {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_cli.py +0 -0
  39. {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_interactive_settings.py +0 -0
  40. {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_settings.py +0 -0
  41. {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_types.py +0 -0
  42. {tgit-0.26.0 → tgit-0.27.0}/tests/unit/test_utils.py +0 -0
  43. {tgit-0.26.0 → tgit-0.27.0}/tgit/__init__.py +0 -0
  44. {tgit-0.26.0 → tgit-0.27.0}/tgit/add.py +0 -0
  45. {tgit-0.26.0 → tgit-0.27.0}/tgit/changelog.py +0 -0
  46. {tgit-0.26.0 → tgit-0.27.0}/tgit/cli.py +0 -0
  47. {tgit-0.26.0 → tgit-0.27.0}/tgit/constants.py +0 -0
  48. {tgit-0.26.0 → tgit-0.27.0}/tgit/interactive_settings.py +0 -0
  49. {tgit-0.26.0 → tgit-0.27.0}/tgit/settings.py +0 -0
  50. {tgit-0.26.0 → tgit-0.27.0}/tgit/shared.py +0 -0
  51. {tgit-0.26.0 → tgit-0.27.0}/tgit/types.py +0 -0
  52. {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.26.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.26.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, reclusive=False)
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, reclusive=False)
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, reclusive=False)
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, reclusive=True)
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, reclusive=True)
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, reclusive=False)
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, reclusive=True)
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")