python-semantic-release 9.8.4__tar.gz → 9.8.6__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 (147) hide show
  1. {python_semantic_release-9.8.4/python_semantic_release.egg-info → python_semantic_release-9.8.6}/PKG-INFO +1 -1
  2. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/configuration.rst +1 -1
  3. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/pyproject.toml +1 -1
  4. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6/python_semantic_release.egg-info}/PKG-INFO +1 -1
  5. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/__init__.py +1 -1
  6. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/changelog/release_history.py +44 -39
  7. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/cli/cli_context.py +29 -9
  8. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/cli/commands/version.py +29 -10
  9. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/command_line/test_main.py +2 -2
  10. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/command_line/test_version.py +133 -5
  11. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/AUTHORS.rst +0 -0
  12. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/LICENSE +0 -0
  13. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/MANIFEST.in +0 -0
  14. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/README.rst +0 -0
  15. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/Makefile +0 -0
  16. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/algorithm.rst +0 -0
  17. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/automatic-releases/cronjobs.rst +0 -0
  18. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/automatic-releases/github-actions.rst +0 -0
  19. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/automatic-releases/index.rst +0 -0
  20. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/automatic-releases/travis.rst +0 -0
  21. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/changelog_templates.rst +0 -0
  22. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/commands.rst +0 -0
  23. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/commit-parsing.rst +0 -0
  24. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/conf.py +0 -0
  25. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/contributing.rst +0 -0
  26. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/contributors.rst +0 -0
  27. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/github-action.rst +0 -0
  28. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/index.rst +0 -0
  29. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/make.bat +0 -0
  30. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/migrating_from_v7.rst +0 -0
  31. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/multibranch_releases.rst +0 -0
  32. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/strict_mode.rst +0 -0
  33. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/docs/troubleshooting.rst +0 -0
  34. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/python_semantic_release.egg-info/SOURCES.txt +0 -0
  35. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/python_semantic_release.egg-info/dependency_links.txt +0 -0
  36. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/python_semantic_release.egg-info/entry_points.txt +0 -0
  37. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/python_semantic_release.egg-info/requires.txt +0 -0
  38. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/python_semantic_release.egg-info/top_level.txt +0 -0
  39. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/__main__.py +0 -0
  40. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/changelog/__init__.py +0 -0
  41. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/changelog/context.py +0 -0
  42. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/changelog/template.py +0 -0
  43. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/cli/__init__.py +0 -0
  44. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/cli/changelog_writer.py +0 -0
  45. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/cli/commands/__init__.py +0 -0
  46. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/cli/commands/changelog.py +0 -0
  47. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/cli/commands/generate_config.py +0 -0
  48. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/cli/commands/main.py +0 -0
  49. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/cli/commands/publish.py +0 -0
  50. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/cli/config.py +0 -0
  51. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/cli/const.py +0 -0
  52. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/cli/github_actions_output.py +0 -0
  53. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/cli/masking_filter.py +0 -0
  54. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/cli/util.py +0 -0
  55. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/commit_parser/__init__.py +0 -0
  56. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/commit_parser/_base.py +0 -0
  57. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/commit_parser/angular.py +0 -0
  58. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/commit_parser/emoji.py +0 -0
  59. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/commit_parser/scipy.py +0 -0
  60. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/commit_parser/tag.py +0 -0
  61. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/commit_parser/token.py +0 -0
  62. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/commit_parser/util.py +0 -0
  63. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/const.py +0 -0
  64. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/data/templates/CHANGELOG.md.j2 +0 -0
  65. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/data/templates/release_notes.md.j2 +0 -0
  66. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/enums.py +0 -0
  67. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/errors.py +0 -0
  68. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/gitproject.py +0 -0
  69. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/helpers.py +0 -0
  70. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/hvcs/__init__.py +0 -0
  71. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/hvcs/_base.py +0 -0
  72. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/hvcs/bitbucket.py +0 -0
  73. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/hvcs/gitea.py +0 -0
  74. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/hvcs/github.py +0 -0
  75. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/hvcs/gitlab.py +0 -0
  76. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/hvcs/remote_hvcs_base.py +0 -0
  77. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/hvcs/token_auth.py +0 -0
  78. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/hvcs/util.py +0 -0
  79. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/version/__init__.py +0 -0
  80. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/version/algorithm.py +0 -0
  81. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/version/declaration.py +0 -0
  82. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/version/translator.py +0 -0
  83. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/semantic_release/version/version.py +0 -0
  84. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/setup.cfg +0 -0
  85. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/__init__.py +0 -0
  86. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/command_line/__init__.py +0 -0
  87. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/command_line/conftest.py +0 -0
  88. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/command_line/test_changelog.py +0 -0
  89. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/command_line/test_generate_config.py +0 -0
  90. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/command_line/test_help.py +0 -0
  91. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/command_line/test_publish.py +0 -0
  92. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/conftest.py +0 -0
  93. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/const.py +0 -0
  94. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/fixtures/__init__.py +0 -0
  95. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/fixtures/commit_parsers.py +0 -0
  96. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/fixtures/example_project.py +0 -0
  97. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/fixtures/git_repo.py +0 -0
  98. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/fixtures/repos/__init__.py +0 -0
  99. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/fixtures/repos/git_flow/__init__.py +0 -0
  100. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/fixtures/repos/git_flow/repo_w_2_release_channels.py +0 -0
  101. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/fixtures/repos/git_flow/repo_w_3_release_channels.py +0 -0
  102. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/fixtures/repos/github_flow/__init__.py +0 -0
  103. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/fixtures/repos/github_flow/repo_w_release_channels.py +0 -0
  104. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/fixtures/repos/trunk_based_dev/__init__.py +0 -0
  105. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/fixtures/repos/trunk_based_dev/repo_w_no_tags.py +0 -0
  106. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/fixtures/repos/trunk_based_dev/repo_w_prereleases.py +0 -0
  107. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/fixtures/repos/trunk_based_dev/repo_w_tags.py +0 -0
  108. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/fixtures/scipy.py +0 -0
  109. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/scenario/__init__.py +0 -0
  110. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/scenario/test_next_version.py +0 -0
  111. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/scenario/test_release_history.py +0 -0
  112. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/scenario/test_template_render.py +0 -0
  113. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/__init__.py +0 -0
  114. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/__init__.py +0 -0
  115. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/changelog/__init__.py +0 -0
  116. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/changelog/test_changelog_context.py +0 -0
  117. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/changelog/test_default_changelog.py +0 -0
  118. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/changelog/test_release_notes.py +0 -0
  119. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/changelog/test_template.py +0 -0
  120. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/cli/__init__.py +0 -0
  121. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/cli/test_config.py +0 -0
  122. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/cli/test_github_actions_output.py +0 -0
  123. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/cli/test_masking_filter.py +0 -0
  124. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/cli/test_util.py +0 -0
  125. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/cli/test_version.py +0 -0
  126. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/commit_parser/__init__.py +0 -0
  127. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/commit_parser/test_angular.py +0 -0
  128. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/commit_parser/test_emoji.py +0 -0
  129. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/commit_parser/test_parsed_commit.py +0 -0
  130. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/commit_parser/test_scipy.py +0 -0
  131. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/commit_parser/test_tag.py +0 -0
  132. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/commit_parser/test_util.py +0 -0
  133. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/hvcs/__init__.py +0 -0
  134. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/hvcs/test__base.py +0 -0
  135. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/hvcs/test_bitbucket.py +0 -0
  136. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/hvcs/test_gitea.py +0 -0
  137. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/hvcs/test_github.py +0 -0
  138. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/hvcs/test_gitlab.py +0 -0
  139. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/hvcs/test_token_auth.py +0 -0
  140. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/hvcs/test_util.py +0 -0
  141. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/test_helpers.py +0 -0
  142. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/version/__init__.py +0 -0
  143. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/version/test_algorithm.py +0 -0
  144. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/version/test_declaration.py +0 -0
  145. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/version/test_translator.py +0 -0
  146. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/unit/semantic_release/version/test_version.py +0 -0
  147. {python_semantic_release-9.8.4 → python_semantic_release-9.8.6}/tests/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-semantic-release
3
- Version: 9.8.4
3
+ Version: 9.8.6
4
4
  Summary: Automatic Semantic Versioning for Python projects
5
5
  Author-email: Rolf Erik Lekang <me@rolflekang.com>
6
6
  License: MIT
@@ -561,7 +561,7 @@ Author used in commits in the format ``name <email>``.
561
561
  .. note::
562
562
  If you are using the built-in GitHub Action, the default value is set to
563
563
  ``github-actions <actions@github.com>``. You can modify this with the
564
- ``git_committer_name`` and ``git_committer_name`` inputs.
564
+ ``git_committer_name`` and ``git_committer_email`` inputs.
565
565
 
566
566
  .. seealso::
567
567
  - :ref:`github-actions`
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "python-semantic-release"
9
- version = "9.8.4"
9
+ version = "9.8.6"
10
10
  description = "Automatic Semantic Versioning for Python projects"
11
11
  requires-python = ">=3.8"
12
12
  license = { text = "MIT" }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-semantic-release
3
- Version: 9.8.4
3
+ Version: 9.8.6
4
4
  Summary: Automatic Semantic Versioning for Python projects
5
5
  Author-email: Rolf Erik Lekang <me@rolflekang.com>
6
6
  License: MIT
@@ -24,7 +24,7 @@ from semantic_release.version import (
24
24
  tags_and_versions,
25
25
  )
26
26
 
27
- __version__ = "9.8.4"
27
+ __version__ = "9.8.6"
28
28
 
29
29
  __all__ = [
30
30
  "CommitParser",
@@ -41,6 +41,13 @@ class ReleaseHistory:
41
41
  unreleased: dict[str, list[ParseResult]] = defaultdict(list)
42
42
  released: dict[Version, Release] = {}
43
43
 
44
+ # Performance optimization: create a mapping of tag sha to version
45
+ # so we can quickly look up the version for a given commit based on sha
46
+ tag_sha_2_version_lookup = {
47
+ tag.commit.hexsha: (tag, version)
48
+ for tag, version in all_git_tags_and_versions
49
+ }
50
+
44
51
  # Strategy:
45
52
  # Loop through commits in history, parsing as we go.
46
53
  # Add these commits to `unreleased` as a key-value mapping
@@ -54,7 +61,7 @@ class ReleaseHistory:
54
61
  is_commit_released = False
55
62
  the_version: Version | None = None
56
63
 
57
- for commit in repo.iter_commits():
64
+ for commit in repo.iter_commits("HEAD", topo_order=True):
58
65
  # mypy will be happy if we make this an explicit string
59
66
  commit_message = str(commit.message)
60
67
 
@@ -64,46 +71,44 @@ class ReleaseHistory:
64
71
  )
65
72
  log.debug("commit has type %s", commit_type)
66
73
 
67
- for tag, version in all_git_tags_and_versions:
68
- if tag.commit == commit:
69
- # we have found the latest commit introduced by this tag
70
- # so we create a new Release entry
71
- log.debug("found commit %s for tag %s", commit.hexsha, tag.name)
72
- is_commit_released = True
73
- the_version = version
74
-
75
- # tag.object is a Commit if the tag is lightweight, otherwise
76
- # it is a TagObject with additional metadata about the tag
77
- if isinstance(tag.object, TagObject):
78
- tagger = tag.object.tagger
79
- committer = tag.object.tagger.committer()
80
- _tz = timezone(
81
- timedelta(seconds=-1 * tag.object.tagger_tz_offset)
82
- )
83
- tagged_date = datetime.fromtimestamp(
84
- tag.object.tagged_date, tz=_tz
85
- )
86
- else:
87
- # For some reason, sometimes tag.object is a Commit
88
- tagger = tag.object.author
89
- committer = tag.object.author
90
- _tz = timezone(
91
- timedelta(seconds=-1 * tag.object.author_tz_offset)
92
- )
93
- tagged_date = datetime.fromtimestamp(
94
- tag.object.committed_date, tz=_tz
95
- )
96
-
97
- release = Release(
98
- tagger=tagger,
99
- committer=committer,
100
- tagged_date=tagged_date,
101
- elements=defaultdict(list),
102
- version=the_version,
74
+ log.debug("checking if commit %s matches any tags", commit.hexsha)
75
+ t_v = tag_sha_2_version_lookup.get(commit.hexsha, None)
76
+
77
+ if t_v is None:
78
+ log.debug("no tags correspond to commit %s", commit.hexsha)
79
+ else:
80
+ # Unpack the tuple (overriding the current version)
81
+ tag, the_version = t_v
82
+ # we have found the latest commit introduced by this tag
83
+ # so we create a new Release entry
84
+ log.debug("found commit %s for tag %s", commit.hexsha, tag.name)
85
+ is_commit_released = True
86
+
87
+ # tag.object is a Commit if the tag is lightweight, otherwise
88
+ # it is a TagObject with additional metadata about the tag
89
+ if isinstance(tag.object, TagObject):
90
+ tagger = tag.object.tagger
91
+ committer = tag.object.tagger.committer()
92
+ _tz = timezone(timedelta(seconds=-1 * tag.object.tagger_tz_offset))
93
+ tagged_date = datetime.fromtimestamp(tag.object.tagged_date, tz=_tz)
94
+ else:
95
+ # For some reason, sometimes tag.object is a Commit
96
+ tagger = tag.object.author
97
+ committer = tag.object.author
98
+ _tz = timezone(timedelta(seconds=-1 * tag.object.author_tz_offset))
99
+ tagged_date = datetime.fromtimestamp(
100
+ tag.object.committed_date, tz=_tz
103
101
  )
104
102
 
105
- released.setdefault(the_version, release)
106
- break
103
+ release = Release(
104
+ tagger=tagger,
105
+ committer=committer,
106
+ tagged_date=tagged_date,
107
+ elements=defaultdict(list),
108
+ version=the_version,
109
+ )
110
+
111
+ released.setdefault(the_version, release)
107
112
 
108
113
  if any(pat.match(commit_message) for pat in exclude_commit_patterns):
109
114
  log.debug(
@@ -34,10 +34,17 @@ class CliContextObj:
34
34
  logger: logging.Logger,
35
35
  global_opts: GlobalCommandLineOptions,
36
36
  ) -> None:
37
- self._runtime_ctx: RuntimeContext | None = None
38
37
  self.ctx = ctx
39
38
  self.logger = logger
40
39
  self.global_opts = global_opts
40
+ self._raw_config: RawConfig | None = None
41
+ self._runtime_ctx: RuntimeContext | None = None
42
+
43
+ @property
44
+ def raw_config(self) -> RawConfig:
45
+ if self._raw_config is None:
46
+ self._raw_config = self._init_raw_config()
47
+ return self._raw_config
41
48
 
42
49
  @property
43
50
  def runtime_ctx(self) -> RuntimeContext:
@@ -49,7 +56,7 @@ class CliContextObj:
49
56
  self._runtime_ctx = self._init_runtime_ctx()
50
57
  return self._runtime_ctx
51
58
 
52
- def _init_runtime_ctx(self) -> RuntimeContext:
59
+ def _init_raw_config(self) -> RawConfig:
53
60
  config_path = Path(self.global_opts.config_file)
54
61
  conf_file_exists = config_path.exists()
55
62
  was_conf_file_user_provided = bool(
@@ -60,6 +67,7 @@ class CliContextObj:
60
67
  )
61
68
  )
62
69
 
70
+ # TODO: Evaluate Exeception catches
63
71
  try:
64
72
  if was_conf_file_user_provided and not conf_file_exists:
65
73
  raise FileNotFoundError( # noqa: TRY301
@@ -74,24 +82,36 @@ class CliContextObj:
74
82
  "configuration empty, falling back to default configuration"
75
83
  )
76
84
 
77
- raw_config = RawConfig.model_validate(config_obj)
85
+ return RawConfig.model_validate(config_obj)
86
+ except FileNotFoundError as exc:
87
+ click.echo(str(exc), err=True)
88
+ self.ctx.exit(2)
89
+ except (
90
+ ValidationError,
91
+ InvalidConfiguration,
92
+ InvalidGitRepositoryError,
93
+ ) as exc:
94
+ click.echo(str(exc), err=True)
95
+ self.ctx.exit(1)
96
+
97
+ def _init_runtime_ctx(self) -> RuntimeContext:
98
+ # TODO: Evaluate Exception catches
99
+ try:
78
100
  runtime = RuntimeContext.from_raw_config(
79
- raw_config,
101
+ self.raw_config,
80
102
  global_cli_options=self.global_opts,
81
103
  )
82
- except (DetachedHeadGitError, NotAReleaseBranch) as exc:
104
+ except NotAReleaseBranch as exc:
83
105
  rprint(f"[bold {'red' if self.global_opts.strict else 'orange1'}]{exc!s}")
84
106
  # If not strict, exit 0 so other processes can continue. For example, in
85
107
  # multibranch CI it might be desirable to run a non-release branch's pipeline
86
108
  # without specifying conditional execution of PSR based on branch name
87
109
  self.ctx.exit(2 if self.global_opts.strict else 0)
88
- except FileNotFoundError as exc:
89
- click.echo(str(exc), err=True)
90
- self.ctx.exit(2)
91
110
  except (
92
- ValidationError,
111
+ DetachedHeadGitError,
93
112
  InvalidConfiguration,
94
113
  InvalidGitRepositoryError,
114
+ ValidationError,
95
115
  ) as exc:
96
116
  click.echo(str(exc), err=True)
97
117
  self.ctx.exit(1)
@@ -4,6 +4,7 @@ import logging
4
4
  import os
5
5
  import subprocess
6
6
  import sys
7
+ from collections import defaultdict
7
8
  from datetime import datetime
8
9
  from typing import TYPE_CHECKING
9
10
 
@@ -160,8 +161,17 @@ def shell(
160
161
  if not shell:
161
162
  raise TypeError("'shell' is None")
162
163
 
164
+ shell_cmd_param = defaultdict(
165
+ lambda: "-c",
166
+ {
167
+ "cmd": "/c",
168
+ "powershell": "-Command",
169
+ "pwsh": "-Command",
170
+ },
171
+ )
172
+
163
173
  return subprocess.run( # noqa: S603
164
- [shell, "-c" if shell != "cmd" else "/c", cmd],
174
+ [shell, shell_cmd_param[shell], cmd],
165
175
  env=(env or {}),
166
176
  check=check,
167
177
  )
@@ -413,26 +423,28 @@ def version( # noqa: C901
413
423
  * Create a release (if supported) in the remote VCS for this tag
414
424
  """
415
425
  ctx = click.get_current_context()
416
- runtime = cli_ctx.runtime_ctx
417
- translator = runtime.version_translator
426
+
427
+ # Enable any cli overrides of configuration before asking for the runtime context
428
+ config = cli_ctx.raw_config
418
429
 
419
430
  # We can short circuit updating the release if we are only printing the last released version
420
431
  if print_last_released or print_last_released_tag:
421
432
  # TODO: get tag format a better way
422
- if not (last_release := last_released(runtime.repo_dir, translator.tag_format)):
433
+ if not (
434
+ last_release := last_released(config.repo_dir, tag_format=config.tag_format)
435
+ ):
423
436
  log.warning("No release tags found.")
424
437
  return
425
438
 
426
439
  click.echo(last_release[0] if print_last_released_tag else last_release[1])
427
440
  return
428
441
 
442
+ # TODO: figure out --print of next version with & without branch validation
443
+ # do you always need a prerelease token if its not --as-prerelease?
444
+ runtime = cli_ctx.runtime_ctx
445
+ translator = runtime.version_translator
446
+
429
447
  parser = runtime.commit_parser
430
- forced_level_bump = None if not force_level else LevelBump.from_string(force_level)
431
- prerelease = is_forced_prerelease(
432
- as_prerelease=as_prerelease,
433
- forced_level_bump=forced_level_bump,
434
- prerelease=runtime.prerelease,
435
- )
436
448
  hvcs_client = runtime.hvcs_client
437
449
  assets = runtime.assets
438
450
  commit_author = runtime.commit_author
@@ -442,6 +454,13 @@ def version( # noqa: C901
442
454
  opts = runtime.global_cli_options
443
455
  gha_output = VersionGitHubActionsOutput(released=False)
444
456
 
457
+ forced_level_bump = None if not force_level else LevelBump.from_string(force_level)
458
+ prerelease = is_forced_prerelease(
459
+ as_prerelease=as_prerelease,
460
+ forced_level_bump=forced_level_bump,
461
+ prerelease=runtime.prerelease,
462
+ )
463
+
445
464
  if prerelease_token:
446
465
  log.info("Forcing use of %s as the prerelease token", prerelease_token)
447
466
  translator.prerelease_token = prerelease_token
@@ -85,8 +85,8 @@ def test_not_a_release_branch_detached_head_exit_code(
85
85
  cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--no-commit"]
86
86
  result = cli_runner.invoke(main, cli_cmd[1:])
87
87
 
88
- # as non-strict, this will return success exit code
89
- assert_successful_exit_code(result, cli_cmd)
88
+ # detached head states should throw an error as release branches cannot be determined
89
+ assert_exit_code(1, result, cli_cmd)
90
90
  assert expected_err_msg in result.stderr
91
91
 
92
92
 
@@ -16,6 +16,7 @@ import tomlkit
16
16
  from pytest_lazyfixture import lazy_fixture
17
17
 
18
18
  from semantic_release.cli.commands.main import main
19
+ from semantic_release.hvcs.github import Github
19
20
 
20
21
  from tests.const import (
21
22
  EXAMPLE_PROJECT_NAME,
@@ -53,6 +54,7 @@ if TYPE_CHECKING:
53
54
  UpdatePyprojectTomlFn,
54
55
  UseReleaseNotesTemplateFn,
55
56
  )
57
+ from tests.fixtures.git_repo import SimulateChangeCommitsNReturnChangelogEntryFn
56
58
 
57
59
 
58
60
  @pytest.mark.parametrize(
@@ -656,7 +658,7 @@ def test_version_version_no_verify(
656
658
  assert head_before in repo_with_single_branch_angular_commits.head.commit.parents
657
659
 
658
660
 
659
- @pytest.mark.parametrize("shell", ("/usr/bin/bash", "/usr/bin/zsh", "powershell"))
661
+ @pytest.mark.parametrize("shell", ("/usr/bin/bash", "/usr/bin/zsh"))
660
662
  def test_version_runs_build_command(
661
663
  repo_with_git_flow_angular_commits: Repo,
662
664
  cli_runner: CliRunner,
@@ -737,7 +739,7 @@ def test_version_runs_build_command(
737
739
  )
738
740
 
739
741
 
740
- @pytest.mark.parametrize("shell", ("powershell", "cmd"))
742
+ @pytest.mark.parametrize("shell", ("powershell", "pwsh", "cmd"))
741
743
  def test_version_runs_build_command_windows(
742
744
  repo_with_git_flow_angular_commits: Repo,
743
745
  cli_runner: CliRunner,
@@ -799,7 +801,7 @@ def test_version_runs_build_command_windows(
799
801
  # Evaluate
800
802
  assert_successful_exit_code(result, cli_cmd)
801
803
  patched_subprocess_run.assert_called_once_with(
802
- [exe, "-c" if shell != "cmd" else "/c", build_command],
804
+ [exe, "/c" if shell == "cmd" else "-Command", build_command],
803
805
  check=True,
804
806
  env={
805
807
  "NEW_VERSION": "1.2.1", # injected into environment
@@ -1001,6 +1003,33 @@ def test_version_exit_code_when_not_strict(
1001
1003
  assert_successful_exit_code(result, cli_cmd)
1002
1004
 
1003
1005
 
1006
+ @pytest.mark.parametrize(
1007
+ "is_strict, exit_code", [(True, 2), (False, 0)], ids=["strict", "non-strict"]
1008
+ )
1009
+ def test_version_on_nonrelease_branch(
1010
+ repo_with_single_branch_angular_commits: Repo,
1011
+ cli_runner: CliRunner,
1012
+ is_strict: bool,
1013
+ exit_code: int,
1014
+ ):
1015
+ branch = repo_with_single_branch_angular_commits.create_head("next")
1016
+ branch.checkout()
1017
+ expected_error_msg = (
1018
+ f"branch '{branch.name}' isn't in any release groups; no release will be made\n"
1019
+ )
1020
+
1021
+ # Act
1022
+ cli_cmd = list(
1023
+ filter(None, [MAIN_PROG_NAME, "--strict" if is_strict else "", VERSION_SUBCMD])
1024
+ )
1025
+ result = cli_runner.invoke(main, cli_cmd[1:])
1026
+
1027
+ # Evaluate (expected -> actual)
1028
+ assert_exit_code(exit_code, result, cli_cmd)
1029
+ assert not result.stdout
1030
+ assert expected_error_msg == result.stderr
1031
+
1032
+
1004
1033
  def test_custom_release_notes_template(
1005
1034
  mocked_git_push: MagicMock,
1006
1035
  repo_with_no_tags_angular_commits: Repo,
@@ -1017,7 +1046,7 @@ def test_custom_release_notes_template(
1017
1046
  runtime_context_with_no_tags = retrieve_runtime_context(
1018
1047
  repo_with_no_tags_angular_commits
1019
1048
  )
1020
- cli_cmd = ["semantic-release", VERSION_SUBCMD, "--skip-build", "--vcs-release"]
1049
+ cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--skip-build", "--vcs-release"]
1021
1050
 
1022
1051
  # Act
1023
1052
  result = cli_runner.invoke(main, cli_cmd[1:])
@@ -1056,7 +1085,7 @@ def test_version_tag_only_push(
1056
1085
 
1057
1086
  # Act
1058
1087
  cli_cmd = [
1059
- "semantic-release",
1088
+ MAIN_PROG_NAME,
1060
1089
  VERSION_SUBCMD,
1061
1090
  "--tag",
1062
1091
  "--no-commit",
@@ -1219,3 +1248,102 @@ def test_version_print_last_released_prints_nothing_if_no_tags(
1219
1248
  assert_successful_exit_code(result, cli_cmd)
1220
1249
  assert result.stdout == ""
1221
1250
  assert "No release tags found." in caplog.text
1251
+
1252
+
1253
+ def test_version_print_last_released_on_detached_head(
1254
+ cli_runner: CliRunner,
1255
+ repo_with_single_branch_tag_commits: Repo,
1256
+ ):
1257
+ last_version = "0.1.1"
1258
+ repo_with_single_branch_tag_commits.git.checkout("HEAD", detach=True)
1259
+
1260
+ cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-last-released"]
1261
+
1262
+ # Act
1263
+ result = cli_runner.invoke(main, cli_cmd[1:])
1264
+
1265
+ # Evaluate (expected -> actual)
1266
+ assert_successful_exit_code(result, cli_cmd)
1267
+ assert not result.stderr
1268
+ assert last_version == result.stdout.rstrip()
1269
+
1270
+
1271
+ def test_version_print_last_released_on_nonrelease_branch(
1272
+ cli_runner: CliRunner,
1273
+ repo_with_single_branch_tag_commits: Repo,
1274
+ ):
1275
+ last_version = "0.1.1"
1276
+ repo_with_single_branch_tag_commits.create_head("next").checkout()
1277
+
1278
+ cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-last-released"]
1279
+
1280
+ # Act
1281
+ result = cli_runner.invoke(main, cli_cmd[1:])
1282
+
1283
+ # Evaluate (expected -> actual)
1284
+ assert_successful_exit_code(result, cli_cmd)
1285
+ assert not result.stderr
1286
+ assert last_version == result.stdout.rstrip()
1287
+
1288
+
1289
+ def test_version_print_last_released_tag_on_detached_head(
1290
+ cli_runner: CliRunner,
1291
+ repo_with_single_branch_tag_commits: Repo,
1292
+ ):
1293
+ last_version = "v0.1.1"
1294
+ repo_with_single_branch_tag_commits.git.checkout("HEAD", detach=True)
1295
+
1296
+ cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-last-released-tag"]
1297
+
1298
+ # Act
1299
+ result = cli_runner.invoke(main, cli_cmd[1:])
1300
+
1301
+ # Evaluate (expected -> actual)
1302
+ assert_successful_exit_code(result, cli_cmd)
1303
+ assert not result.stderr
1304
+ assert last_version == result.stdout.rstrip()
1305
+
1306
+
1307
+ def test_version_print_last_released_tag_on_nonrelease_branch(
1308
+ cli_runner: CliRunner,
1309
+ repo_with_single_branch_tag_commits: Repo,
1310
+ ):
1311
+ last_version_tag = "v0.1.1"
1312
+ repo_with_single_branch_tag_commits.create_head("next").checkout()
1313
+
1314
+ cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print-last-released-tag"]
1315
+
1316
+ # Act
1317
+ result = cli_runner.invoke(main, cli_cmd[1:])
1318
+
1319
+ # Evaluate (expected -> actual)
1320
+ assert_successful_exit_code(result, cli_cmd)
1321
+ assert not result.stderr
1322
+ assert last_version_tag == result.stdout.rstrip()
1323
+
1324
+
1325
+ def test_version_print_next_version_fails_on_detached_head(
1326
+ cli_runner: CliRunner,
1327
+ example_git_ssh_url: str,
1328
+ repo_with_single_branch_tag_commits: Repo,
1329
+ simulate_change_commits_n_rtn_changelog_entry: SimulateChangeCommitsNReturnChangelogEntryFn,
1330
+ ):
1331
+ # Setup
1332
+ repo_with_single_branch_tag_commits.git.checkout("HEAD", detach=True)
1333
+ simulate_change_commits_n_rtn_changelog_entry(
1334
+ repo_with_single_branch_tag_commits,
1335
+ ["fix: make a patch fix to codebase"],
1336
+ Github(example_git_ssh_url),
1337
+ )
1338
+ expected_error_msg = (
1339
+ "Detached HEAD state cannot match any release groups; no release will be made\n"
1340
+ )
1341
+
1342
+ # Act
1343
+ cli_cmd = [MAIN_PROG_NAME, VERSION_SUBCMD, "--print"]
1344
+ result = cli_runner.invoke(main, cli_cmd[1:])
1345
+
1346
+ # Evaluate (expected -> actual)
1347
+ assert_exit_code(1, result, cli_cmd)
1348
+ assert not result.stdout
1349
+ assert expected_error_msg == result.stderr