python-semantic-release 9.8.3__py3-none-any.whl → 9.8.4__py3-none-any.whl

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 (31) hide show
  1. {python_semantic_release-9.8.3.dist-info → python_semantic_release-9.8.4.dist-info}/METADATA +4 -4
  2. {python_semantic_release-9.8.3.dist-info → python_semantic_release-9.8.4.dist-info}/RECORD +29 -28
  3. python_semantic_release-9.8.4.dist-info/entry_points.txt +3 -0
  4. semantic_release/__init__.py +20 -2
  5. semantic_release/__main__.py +1 -1
  6. semantic_release/changelog/context.py +25 -1
  7. semantic_release/changelog/release_history.py +3 -0
  8. semantic_release/cli/__init__.py +0 -10
  9. semantic_release/cli/changelog_writer.py +169 -0
  10. semantic_release/cli/{commands/cli_context.py → cli_context.py} +7 -3
  11. semantic_release/cli/commands/changelog.py +89 -81
  12. semantic_release/cli/commands/main.py +32 -2
  13. semantic_release/cli/commands/publish.py +56 -21
  14. semantic_release/cli/commands/version.py +308 -351
  15. semantic_release/cli/config.py +67 -23
  16. semantic_release/commit_parser/_base.py +2 -1
  17. semantic_release/commit_parser/scipy.py +10 -1
  18. semantic_release/errors.py +33 -1
  19. semantic_release/gitproject.py +254 -0
  20. semantic_release/hvcs/bitbucket.py +3 -2
  21. semantic_release/hvcs/gitea.py +26 -1
  22. semantic_release/hvcs/github.py +26 -1
  23. semantic_release/hvcs/gitlab.py +8 -1
  24. semantic_release/hvcs/remote_hvcs_base.py +2 -1
  25. semantic_release/version/algorithm.py +1 -1
  26. python_semantic_release-9.8.3.dist-info/entry_points.txt +0 -3
  27. semantic_release/cli/common.py +0 -50
  28. {python_semantic_release-9.8.3.dist-info → python_semantic_release-9.8.4.dist-info}/AUTHORS.rst +0 -0
  29. {python_semantic_release-9.8.3.dist-info → python_semantic_release-9.8.4.dist-info}/LICENSE +0 -0
  30. {python_semantic_release-9.8.3.dist-info → python_semantic_release-9.8.4.dist-info}/WHEEL +0 -0
  31. {python_semantic_release-9.8.3.dist-info → python_semantic_release-9.8.4.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-semantic-release
3
- Version: 9.8.3
3
+ Version: 9.8.4
4
4
  Summary: Automatic Semantic Versioning for Python projects
5
5
  Author-email: Rolf Erik Lekang <me@rolflekang.com>
6
6
  License: MIT
@@ -37,15 +37,15 @@ Requires-Dist: build ~=1.2 ; extra == 'build'
37
37
  Provides-Extra: dev
38
38
  Requires-Dist: pre-commit ~=3.5 ; extra == 'dev'
39
39
  Requires-Dist: tox ~=4.11 ; extra == 'dev'
40
- Requires-Dist: ruff ==0.4.9 ; extra == 'dev'
40
+ Requires-Dist: ruff ==0.5.0 ; extra == 'dev'
41
41
  Provides-Extra: docs
42
42
  Requires-Dist: Sphinx ~=6.0 ; extra == 'docs'
43
43
  Requires-Dist: sphinxcontrib-apidoc ==0.5.0 ; extra == 'docs'
44
44
  Requires-Dist: sphinx-autobuild ==2024.2.4 ; extra == 'docs'
45
45
  Requires-Dist: furo ~=2024.1 ; extra == 'docs'
46
46
  Provides-Extra: mypy
47
- Requires-Dist: mypy ==1.10.0 ; extra == 'mypy'
48
- Requires-Dist: types-requests ~=2.31.0 ; extra == 'mypy'
47
+ Requires-Dist: mypy ==1.10.1 ; extra == 'mypy'
48
+ Requires-Dist: types-requests ~=2.32.0 ; extra == 'mypy'
49
49
  Provides-Extra: test
50
50
  Requires-Dist: coverage[toml] ~=7.0 ; extra == 'test'
51
51
  Requires-Dist: pytest ~=7.0 ; extra == 'test'
@@ -1,32 +1,33 @@
1
- semantic_release/__init__.py,sha256=uLmkFIh4Q9cOM3_CTkDX-83OfqCUiHwEpU0k8ZGmoWA,870
2
- semantic_release/__main__.py,sha256=vXOARLV7KHeVXgpOMU0Q-8wovpdsOrtTsycxulI5xIE,106
1
+ semantic_release/__init__.py,sha256=2zPAOZ5IfYsvLnhab9ZWAjy_bunkNmm0OwE3CVcP6rI,1228
2
+ semantic_release/__main__.py,sha256=blPn7CMpuSe4Z-GBIDv0QA0OEDTMGU-7edsLRkkhPR4,120
3
3
  semantic_release/const.py,sha256=Z1o2QNh60wSLeF-_1TemMBjU3ZXbV0XghnUFsbTVfOs,831
4
4
  semantic_release/enums.py,sha256=D5B_reQGGKQQT22HO5PUtvn2Bok3fkht6TfJtXkmAUg,1020
5
- semantic_release/errors.py,sha256=2hrKuCRWN5O_0z-C2Owf6gcTUe2MpP4Y27nHoUzJBng,1695
5
+ semantic_release/errors.py,sha256=bPQDMW9MVhCwkE_TrpsmsRYZRuk5iOnqi-hKeujXQc0,2676
6
+ semantic_release/gitproject.py,sha256=izWc4NLdUzAwxGG_fJeqqHW9ivSrPcWBzSaOijQx4f8,8564
6
7
  semantic_release/helpers.py,sha256=d1jOX0SNyqPc_3wr14xR25FfpqhMd4Ev7MNBOWlScc0,5581
7
8
  semantic_release/changelog/__init__.py,sha256=Bg6Xe5Vt32rWoMscW-hd4sUwiZqzWmsg4CD1EhMesMY,262
8
- semantic_release/changelog/context.py,sha256=YAtv_svSbRsRUqHfTbmfJRpE5xkOEUxmYaBINYJyXLE,1051
9
- semantic_release/changelog/release_history.py,sha256=ShzZpxTjhxka6TMbzOzYzG3EuCH8j3xjUQzll-FKcCo,6675
9
+ semantic_release/changelog/context.py,sha256=8cbOv9cB9Ji7xjRn4-YUUHBfg9LLfSwmmXuZpqEmvns,1653
10
+ semantic_release/changelog/release_history.py,sha256=jfkfnlATBqRpVLzxVi7wf2fO_bH_GOifQbaMO-mqDeY,6781
10
11
  semantic_release/changelog/template.py,sha256=JqZcjdozM9-059Sa8PfiHxYNeyZFa0jxk_beyRLv5sU,4640
11
- semantic_release/cli/__init__.py,sha256=gwAYhZO-4TR0tKCkQ4GqutWsL--0CII2_dcbrElI4M4,419
12
- semantic_release/cli/common.py,sha256=cVQUI7Uaac_nJ7VEXuJ7Q9pVJpuMVRvE7bBl_i96g20,1439
13
- semantic_release/cli/config.py,sha256=01BDXKi_LOMSozyTLcvztZnsu8ISwqea4cvHExQL4k8,21870
12
+ semantic_release/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ semantic_release/cli/changelog_writer.py,sha256=Ga8QAq12kUD-fWWlrFoQCpn1pOzZmHnaCeZfFbBytFo,5027
14
+ semantic_release/cli/cli_context.py,sha256=LApmhR6PTdHTPxs42KYMT_yYbaEXae-myuvio0mhI8w,3522
15
+ semantic_release/cli/config.py,sha256=NAKOqLAc8NV0oL9XxAUPz2FWwS89Bjz8aKSCsRmQsvw,23661
14
16
  semantic_release/cli/const.py,sha256=S8oNNpSaKdZxcAs7PAKEjQHic0czoJBGd7XHo57FOzw,39
15
17
  semantic_release/cli/github_actions_output.py,sha256=Wy4jEIpSFgoMyJqq4FD7scIpgovivutFLZ7Fs4y2CUY,2110
16
18
  semantic_release/cli/masking_filter.py,sha256=DxqjiJyABlzwwwZ1r8JGQpb6QrF00StJFm0-2-s5Fv0,3071
17
19
  semantic_release/cli/util.py,sha256=FyXaBkeL7nXKjy3X9rQLEwvn7p46xPekp2V8Z-5MVrk,3755
18
20
  semantic_release/cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- semantic_release/cli/commands/changelog.py,sha256=rNZwWucEbR2BjbmgHQa9R1nJk5Fu0Ph3U3wh-daH42Q,4197
20
- semantic_release/cli/commands/cli_context.py,sha256=fOLTUKb4UnZGQ6zVhftuvdfZ7fK7bDDIvj7Ud5yU98c,3443
21
+ semantic_release/cli/commands/changelog.py,sha256=zincrvCBwMb7S3K3udRcArtQCuGkree-7IEzNtvF42A,3489
21
22
  semantic_release/cli/commands/generate_config.py,sha256=2xZOu3NpyhBp0pWr7d8ugKl_kjqQgpSsSMHq5wHTfrE,1699
22
- semantic_release/cli/commands/main.py,sha256=LuL-O0LGonI5fQTZn0AUtYv2Ei_CYyNqYHMF_wN4InI,2900
23
- semantic_release/cli/commands/publish.py,sha256=ZA7DuEGxZdIT9wUwP1S24qi1RdcKV-1FnYci-PvyXIg,1958
24
- semantic_release/cli/commands/version.py,sha256=lJ_DiD17dTXZ0hB8l_lBsL960dxuUmawR9dmr1KJjvA,27279
23
+ semantic_release/cli/commands/main.py,sha256=kqO8bZw9Nv6T5QkIl53zzmH2rd4LuLoA8agweH0VYPY,4022
24
+ semantic_release/cli/commands/publish.py,sha256=SZQlIewvqyIC14dkIIVVFetE0tPsKbO1cUyxnZsicrw,2845
25
+ semantic_release/cli/commands/version.py,sha256=Saunh8juMDHJuPK8VcH0ZiUfYJduNODSwsAX2DjLki4,23809
25
26
  semantic_release/commit_parser/__init__.py,sha256=cv5HFBdw7OJd4Laj4Ex8ZZ5Tml8GwXgQcXW6Pasr2Ao,615
26
- semantic_release/commit_parser/_base.py,sha256=D2dY8dALV3QPK0XOLZru9btMW5dlx9YSmDIe3v5T0QA,3004
27
+ semantic_release/commit_parser/_base.py,sha256=t-Z9ALgAe7aZpYXz1mk3Fe-uAvipgKdNrq4Okg_WW9c,3026
27
28
  semantic_release/commit_parser/angular.py,sha256=ERWeTBTFNIBOaTIopY4Vff0KbeFwrSQWbADe3smVW-8,4579
28
29
  semantic_release/commit_parser/emoji.py,sha256=M6zgqsXqegLK5FxZGyfsOPM3vOsJkPKVAjnGATOhwa4,3452
29
- semantic_release/commit_parser/scipy.py,sha256=uRNLSXH4LApxm8xSyUil23WVO3C8VEFRCnwTIKZbyQk,5879
30
+ semantic_release/commit_parser/scipy.py,sha256=AH_GeMZ9hWiNQTL78bovpQy7mBeobGpUk5Shzyxxm6c,5951
30
31
  semantic_release/commit_parser/tag.py,sha256=SAop9INQvKOwktNmVypKFngd6xHmY_XZrLUWChUjz84,3353
31
32
  semantic_release/commit_parser/token.py,sha256=BB4ZCyt753CCaBFF95cQ4sFm6Au96wpO0YyTAWdcOvE,1609
32
33
  semantic_release/commit_parser/util.py,sha256=vLcVDErZrExM55jMffos0hyMbNVQoJ-PeeVDG1Ej51I,730
@@ -34,22 +35,22 @@ semantic_release/data/templates/CHANGELOG.md.j2,sha256=4SNA1uTM2_qLA_3QTbS8JHZi9
34
35
  semantic_release/data/templates/release_notes.md.j2,sha256=27uIHgG6iYmqdMaoWFP5LJRZcrO-3paqFzHHHsGpXXE,465
35
36
  semantic_release/hvcs/__init__.py,sha256=JwoaLOF-12L-OBo_9-tOXXhdiHKeVungA9865to2oZk,494
36
37
  semantic_release/hvcs/_base.py,sha256=9-iTqTPSbiEevKbCBP9K2hq4c-2T4wPbeLWe-kAxBzo,2607
37
- semantic_release/hvcs/bitbucket.py,sha256=tMpl69CbZSQ8cdgiqtIzf7WSz8OQwDInGpoq-9tQr3k,9216
38
- semantic_release/hvcs/gitea.py,sha256=CSItRSJU39hohO3oE-ohX65Er_s2nVrDqvfc7KQtmv8,11152
39
- semantic_release/hvcs/github.py,sha256=u-n1h3ivB8d-ca2Z8Jdgkhk-JTu-XONAqpiso8rKshk,18381
40
- semantic_release/hvcs/gitlab.py,sha256=ZVfeA2Uzl--6vVMJfJWXniPFzrsZ001Guzuu7AVSLOI,9414
41
- semantic_release/hvcs/remote_hvcs_base.py,sha256=plZkc5cSaiTxgIR5HhlEWrYoyWzzSXr-Jw3aXbMK_iA,6055
38
+ semantic_release/hvcs/bitbucket.py,sha256=nqlOmeNda0sRSEBGWMluphy1KlpRTQrHV7itxf0IXE0,9266
39
+ semantic_release/hvcs/gitea.py,sha256=XPwsjQ1XeSAdAsAjW7_QJdjxfTPJvW-Vnmoze7kCZP8,11919
40
+ semantic_release/hvcs/github.py,sha256=TVXlviaXn2N_DBH0GxvIhTH-SQdn5mxbkjgu8rvb1y0,19148
41
+ semantic_release/hvcs/gitlab.py,sha256=ia8Gjfm8mIctlEu5xqMPa-6t7YGSZQUbzKhKzgRuC3Q,9683
42
+ semantic_release/hvcs/remote_hvcs_base.py,sha256=2TaPPp1NzsQjUMOKa0NjqkjYTxJpX245qGm7-GCrnx8,6085
42
43
  semantic_release/hvcs/token_auth.py,sha256=ZjT56-NIPB4OKIt1qwHCu1TavXnrWFIBl9ARlg56hgU,663
43
44
  semantic_release/hvcs/util.py,sha256=PNKXApgL2np9lpnKjAbo77toRgElLPXrbS1q7621waI,2886
44
45
  semantic_release/version/__init__.py,sha256=CLhtGQry9dLIij5XyRa9ZevxU_1p8tjMTSQ-K_GMpWM,270
45
- semantic_release/version/algorithm.py,sha256=-9ulTy1z4u-jUvbc-FrvEdFL7Omc3O3HhaPohjiCmm0,16854
46
+ semantic_release/version/algorithm.py,sha256=ofx_bIWq6ptJVr-ekI11IzxzDEctDKFiVwaSmmmgRlE,16870
46
47
  semantic_release/version/declaration.py,sha256=f6Ld7hIhrqvDrRBapJHr-KDimuyo-4IG8009Zu9BIgU,7357
47
48
  semantic_release/version/translator.py,sha256=P1noIsVBn8u6zNOFjG0xKYOWapxqf_PHSMvMeLJ9kXg,3050
48
49
  semantic_release/version/version.py,sha256=6PCtSbLP88U1daoxnCwHc--YguZo4waGNLqJ5JfeczE,14175
49
- python_semantic_release-9.8.3.dist-info/AUTHORS.rst,sha256=XOReVvpymEFUPsS2QPH97jlfJBVrxwS2eu8-jVAe4gk,230
50
- python_semantic_release-9.8.3.dist-info/LICENSE,sha256=NE85nszX252sdQdu0xgS9qwfYES0k8qS6gW3uO4jRGE,1083
51
- python_semantic_release-9.8.3.dist-info/METADATA,sha256=NfEykbj0ZB8WpYwC5CNepm16izRbP_O_8UfuXTqRRhs,5404
52
- python_semantic_release-9.8.3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
53
- python_semantic_release-9.8.3.dist-info/entry_points.txt,sha256=qojUBx_Ywf4u7KgP6NWlDaBDymZVUGQ7m5wF2duC2F0,95
54
- python_semantic_release-9.8.3.dist-info/top_level.txt,sha256=qYA24nyg3eP-ti5UW7Vuj2aXVmM0wqVHx4mREdRZNAA,17
55
- python_semantic_release-9.8.3.dist-info/RECORD,,
50
+ python_semantic_release-9.8.4.dist-info/AUTHORS.rst,sha256=XOReVvpymEFUPsS2QPH97jlfJBVrxwS2eu8-jVAe4gk,230
51
+ python_semantic_release-9.8.4.dist-info/LICENSE,sha256=NE85nszX252sdQdu0xgS9qwfYES0k8qS6gW3uO4jRGE,1083
52
+ python_semantic_release-9.8.4.dist-info/METADATA,sha256=pfsnObyVM-vMXf-fME2dZbgH1yyiboC8gH805RJLvVE,5404
53
+ python_semantic_release-9.8.4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
54
+ python_semantic_release-9.8.4.dist-info/entry_points.txt,sha256=6bS6euSagjerp7onDtfI9_ZpczWreZF3gjHfGAegkbo,123
55
+ python_semantic_release-9.8.4.dist-info/top_level.txt,sha256=qYA24nyg3eP-ti5UW7Vuj2aXVmM0wqVHx4mREdRZNAA,17
56
+ python_semantic_release-9.8.4.dist-info/RECORD,,
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ psr = semantic_release.cli.commands.main:main
3
+ semantic-release = semantic_release.cli.commands.main:main
@@ -24,7 +24,25 @@ from semantic_release.version import (
24
24
  tags_and_versions,
25
25
  )
26
26
 
27
- __version__ = "9.8.3"
27
+ __version__ = "9.8.4"
28
+
29
+ __all__ = [
30
+ "CommitParser",
31
+ "ParsedCommit",
32
+ "ParseError",
33
+ "ParseResult",
34
+ "ParseResultType",
35
+ "ParserOptions",
36
+ "LevelBump",
37
+ "SemanticReleaseBaseError",
38
+ "CommitParseError",
39
+ "InvalidConfiguration",
40
+ "InvalidVersion",
41
+ "Version",
42
+ "VersionTranslator",
43
+ "next_version",
44
+ "tags_and_versions",
45
+ ]
28
46
 
29
47
 
30
48
  def setup_hook(argv: list[str]) -> None:
@@ -36,6 +54,6 @@ def setup_hook(argv: list[str]) -> None:
36
54
  if len(argv) > 1 and any(
37
55
  cmd in argv for cmd in ["version", "publish", "changelog"]
38
56
  ):
39
- from semantic_release.cli import main
57
+ from semantic_release.cli.commands.main import main
40
58
 
41
59
  main()
@@ -1,6 +1,6 @@
1
1
  import sys
2
2
 
3
- from semantic_release.cli import main
3
+ from semantic_release.cli.commands.main import main
4
4
 
5
5
  if __name__ == "__main__":
6
6
  main(args=sys.argv[1:])
@@ -6,8 +6,32 @@ from typing import TYPE_CHECKING, Any, Callable
6
6
  if TYPE_CHECKING:
7
7
  from jinja2 import Environment
8
8
 
9
- from semantic_release.changelog.release_history import ReleaseHistory
9
+ from semantic_release.changelog.release_history import Release, ReleaseHistory
10
10
  from semantic_release.hvcs._base import HvcsBase
11
+ from semantic_release.version.version import Version
12
+
13
+
14
+ @dataclass
15
+ class ReleaseNotesContext:
16
+ repo_name: str
17
+ repo_owner: str
18
+ hvcs_type: str
19
+ version: Version
20
+ release: Release
21
+ filters: tuple[Callable[..., Any], ...] = ()
22
+
23
+ def bind_to_environment(self, env: Environment) -> Environment:
24
+ env_globals = dict(
25
+ filter(lambda k_v: k_v[0] != "filters", self.__dict__.items())
26
+ )
27
+
28
+ for g, v in env_globals.items():
29
+ env.globals[g] = v
30
+
31
+ for f in self.filters:
32
+ env.filters[f.__name__] = f
33
+
34
+ return env
11
35
 
12
36
 
13
37
  @dataclass
@@ -99,6 +99,7 @@ class ReleaseHistory:
99
99
  committer=committer,
100
100
  tagged_date=tagged_date,
101
101
  elements=defaultdict(list),
102
+ version=the_version,
102
103
  )
103
104
 
104
105
  released.setdefault(the_version, release)
@@ -165,6 +166,7 @@ class ReleaseHistory:
165
166
  "committer": committer,
166
167
  "tagged_date": tagged_date,
167
168
  "elements": self.unreleased,
169
+ "version": version,
168
170
  },
169
171
  **self.released,
170
172
  },
@@ -183,3 +185,4 @@ class Release(TypedDict):
183
185
  committer: Actor
184
186
  tagged_date: datetime
185
187
  elements: dict[str, list[ParseResult]]
188
+ version: Version
@@ -1,10 +0,0 @@
1
- from semantic_release.cli.commands.changelog import changelog
2
- from semantic_release.cli.commands.generate_config import generate_config
3
- from semantic_release.cli.commands.main import main
4
- from semantic_release.cli.commands.publish import publish
5
- from semantic_release.cli.commands.version import version
6
-
7
- main.add_command(changelog)
8
- main.add_command(generate_config)
9
- main.add_command(version)
10
- main.add_command(publish)
@@ -0,0 +1,169 @@
1
+ from __future__ import annotations
2
+
3
+ from logging import getLogger
4
+ from os import listdir
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING
7
+
8
+ # NOTE: use backport with newer API than stdlib
9
+ from importlib_resources import files
10
+
11
+ from semantic_release.changelog.context import (
12
+ ReleaseNotesContext,
13
+ make_changelog_context,
14
+ )
15
+ from semantic_release.changelog.template import environment, recursive_render
16
+ from semantic_release.cli.util import noop_report
17
+
18
+ if TYPE_CHECKING:
19
+ from jinja2 import Environment
20
+
21
+ from semantic_release.changelog.release_history import Release, ReleaseHistory
22
+ from semantic_release.cli.config import RuntimeContext
23
+ from semantic_release.hvcs._base import HvcsBase
24
+
25
+
26
+ log = getLogger(__name__)
27
+
28
+
29
+ def get_release_notes_template(template_dir: Path) -> str:
30
+ """Read the project's template for release notes, falling back to the default."""
31
+ fname = template_dir / ".release_notes.md.j2"
32
+ try:
33
+ return fname.read_text(encoding="utf-8")
34
+ except FileNotFoundError:
35
+ return (
36
+ files("semantic_release")
37
+ .joinpath("data/templates/release_notes.md.j2")
38
+ .read_text(encoding="utf-8")
39
+ )
40
+
41
+
42
+ def render_default_changelog_file(template_env: Environment) -> str:
43
+ changelog_text = (
44
+ files("semantic_release")
45
+ .joinpath("data/templates/CHANGELOG.md.j2")
46
+ .read_text(encoding="utf-8")
47
+ )
48
+ template = template_env.from_string(changelog_text)
49
+ return template.render().rstrip()
50
+
51
+
52
+ def render_release_notes(
53
+ release_notes_template: str,
54
+ template_env: Environment,
55
+ ) -> str:
56
+ template = template_env.from_string(release_notes_template)
57
+ return template.render().rstrip()
58
+
59
+
60
+ def apply_user_changelog_template_directory(
61
+ template_dir: Path,
62
+ environment: Environment,
63
+ destination_dir: Path,
64
+ noop: bool = False,
65
+ ) -> list[str]:
66
+ if noop:
67
+ noop_report(
68
+ str.join(
69
+ " ",
70
+ [
71
+ "would have recursively rendered the template directory",
72
+ f"{template_dir!r} relative to {destination_dir!r}.",
73
+ "Paths which would be modified by this operation cannot be",
74
+ "determined in no-op mode.",
75
+ ],
76
+ )
77
+ )
78
+ return []
79
+
80
+ return recursive_render(
81
+ template_dir, environment=environment, _root_dir=destination_dir
82
+ )
83
+
84
+
85
+ def write_default_changelog(
86
+ changelog_file: Path,
87
+ destination_dir: Path,
88
+ environment: Environment,
89
+ noop: bool = False,
90
+ ) -> str:
91
+ if noop:
92
+ noop_report(
93
+ str.join(
94
+ " ",
95
+ [
96
+ "would have written your changelog to",
97
+ str(changelog_file.relative_to(destination_dir)),
98
+ ],
99
+ )
100
+ )
101
+ else:
102
+ changelog_text = render_default_changelog_file(environment)
103
+ changelog_file.write_text(f"{changelog_text}\n", encoding="utf-8")
104
+
105
+ return str(changelog_file)
106
+
107
+
108
+ def write_changelog_files(
109
+ runtime_ctx: RuntimeContext,
110
+ release_history: ReleaseHistory,
111
+ hvcs_client: HvcsBase,
112
+ noop: bool = False,
113
+ ) -> list[str]:
114
+ project_dir = Path(runtime_ctx.repo_dir)
115
+ template_dir = runtime_ctx.template_dir
116
+
117
+ changelog_context = make_changelog_context(
118
+ hvcs_client=hvcs_client,
119
+ release_history=release_history,
120
+ )
121
+
122
+ changelog_context.bind_to_environment(runtime_ctx.template_environment)
123
+
124
+ use_user_template_dir = bool(
125
+ # Directory exists and directory is not empty
126
+ template_dir.exists() and template_dir.is_dir() and listdir(template_dir)
127
+ )
128
+
129
+ if use_user_template_dir:
130
+ return apply_user_changelog_template_directory(
131
+ template_dir=template_dir,
132
+ environment=runtime_ctx.template_environment,
133
+ destination_dir=project_dir,
134
+ noop=noop,
135
+ )
136
+
137
+ log.info("No contents found in %r, using default changelog template", template_dir)
138
+ return [
139
+ write_default_changelog(
140
+ changelog_file=runtime_ctx.changelog_file,
141
+ destination_dir=project_dir,
142
+ environment=runtime_ctx.template_environment,
143
+ noop=noop,
144
+ )
145
+ ]
146
+
147
+
148
+ def generate_release_notes(
149
+ hvcs_client: HvcsBase,
150
+ release: Release,
151
+ template_dir: Path,
152
+ ) -> str:
153
+ release_notes_env = ReleaseNotesContext(
154
+ repo_name=hvcs_client.repo_name,
155
+ repo_owner=hvcs_client.owner,
156
+ hvcs_type=hvcs_client.__class__.__name__.lower(),
157
+ version=release["version"],
158
+ release=release,
159
+ filters=(*hvcs_client.get_changelog_context_filters(),),
160
+ ).bind_to_environment(
161
+ # Use a new, non-configurable environment for release notes -
162
+ # not user-configurable at the moment
163
+ environment(template_dir=template_dir)
164
+ )
165
+
166
+ return render_release_notes(
167
+ release_notes_template=get_release_notes_template(template_dir),
168
+ template_env=release_notes_env,
169
+ )
@@ -14,7 +14,11 @@ from semantic_release.cli.config import (
14
14
  RuntimeContext,
15
15
  )
16
16
  from semantic_release.cli.util import load_raw_config_file, rprint
17
- from semantic_release.errors import InvalidConfiguration, NotAReleaseBranch
17
+ from semantic_release.errors import (
18
+ DetachedHeadGitError,
19
+ InvalidConfiguration,
20
+ NotAReleaseBranch,
21
+ )
18
22
 
19
23
  if TYPE_CHECKING:
20
24
  from semantic_release.cli.config import GlobalCommandLineOptions
@@ -58,7 +62,7 @@ class CliContextObj:
58
62
 
59
63
  try:
60
64
  if was_conf_file_user_provided and not conf_file_exists:
61
- raise FileNotFoundError(
65
+ raise FileNotFoundError( # noqa: TRY301
62
66
  f"File {self.global_opts.config_file} does not exist"
63
67
  )
64
68
 
@@ -75,7 +79,7 @@ class CliContextObj:
75
79
  raw_config,
76
80
  global_cli_options=self.global_opts,
77
81
  )
78
- except NotAReleaseBranch as exc:
82
+ except (DetachedHeadGitError, NotAReleaseBranch) as exc:
79
83
  rprint(f"[bold {'red' if self.global_opts.strict else 'orange1'}]{exc!s}")
80
84
  # If not strict, exit 0 so other processes can continue. For example, in
81
85
  # multibranch CI it might be desirable to run a non-release branch's pipeline
@@ -1,28 +1,50 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- import os
5
4
  from typing import TYPE_CHECKING
6
5
 
7
6
  import click
7
+ from git import Repo
8
8
 
9
- from semantic_release.changelog import ReleaseHistory, recursive_render
10
- from semantic_release.changelog.context import make_changelog_context
11
- from semantic_release.cli.common import (
12
- get_release_notes_template,
13
- render_default_changelog_file,
14
- render_release_notes,
9
+ from semantic_release.changelog import ReleaseHistory
10
+ from semantic_release.cli.changelog_writer import (
11
+ generate_release_notes,
12
+ write_changelog_files,
15
13
  )
16
14
  from semantic_release.cli.util import noop_report
17
15
  from semantic_release.hvcs.remote_hvcs_base import RemoteHvcsBase
18
16
 
19
17
  if TYPE_CHECKING:
20
- from semantic_release.cli.commands.cli_context import CliContextObj
21
- from semantic_release.version import Version
18
+ from semantic_release.cli.cli_context import CliContextObj
19
+
22
20
 
23
21
  log = logging.getLogger(__name__)
24
22
 
25
23
 
24
+ def post_release_notes(
25
+ release_tag: str,
26
+ release_notes: str,
27
+ prerelease: bool,
28
+ hvcs_client: RemoteHvcsBase,
29
+ noop: bool = False,
30
+ ) -> None:
31
+ if noop:
32
+ noop_report(
33
+ str.join(
34
+ "\n",
35
+ [
36
+ f"would have posted the following release notes for tag {release_tag}:",
37
+ release_notes,
38
+ ],
39
+ )
40
+ )
41
+ return
42
+
43
+ hvcs_client.create_or_update_release(
44
+ release_tag, f"{release_notes}\n", prerelease=prerelease
45
+ )
46
+
47
+
26
48
  @click.command(
27
49
  short_help="Generate a changelog",
28
50
  context_settings={
@@ -36,86 +58,72 @@ log = logging.getLogger(__name__)
36
58
  help="Post the generated release notes to the remote VCS's release for this tag",
37
59
  )
38
60
  @click.pass_obj
39
- def changelog(cli_ctx: CliContextObj, release_tag: str | None = None) -> None:
61
+ def changelog(cli_ctx: CliContextObj, release_tag: str | None) -> None:
40
62
  """Generate and optionally publish a changelog for your project"""
41
63
  ctx = click.get_current_context()
42
64
  runtime = cli_ctx.runtime_ctx
43
- repo = runtime.repo
44
- parser = runtime.commit_parser
45
65
  translator = runtime.version_translator
46
66
  hvcs_client = runtime.hvcs_client
47
- env = runtime.template_environment
48
- template_dir = runtime.template_dir
49
- changelog_file = runtime.changelog_file
50
- changelog_excluded_commit_patterns = runtime.changelog_excluded_commit_patterns
51
-
52
- rh = ReleaseHistory.from_git_history(
53
- repo=repo,
54
- translator=translator,
55
- commit_parser=parser,
56
- exclude_commit_patterns=changelog_excluded_commit_patterns,
57
- )
58
- changelog_context = make_changelog_context(
59
- hvcs_client=hvcs_client, release_history=rh
67
+
68
+ with Repo(str(runtime.repo_dir)) as git_repo:
69
+ release_history = ReleaseHistory.from_git_history(
70
+ repo=git_repo,
71
+ translator=translator,
72
+ commit_parser=runtime.commit_parser,
73
+ exclude_commit_patterns=runtime.changelog_excluded_commit_patterns,
74
+ )
75
+
76
+ write_changelog_files(
77
+ runtime_ctx=runtime,
78
+ release_history=release_history,
79
+ hvcs_client=hvcs_client,
80
+ noop=runtime.global_cli_options.noop,
60
81
  )
61
- changelog_context.bind_to_environment(env)
62
-
63
- if not os.path.exists(template_dir):
64
- log.info("Path %r not found, using default changelog template", template_dir)
65
- if runtime.global_cli_options.noop:
66
- noop_report(
67
- "would have written your changelog to "
68
- + str(changelog_file.relative_to(repo.working_dir))
69
- )
70
- else:
71
- changelog_text = render_default_changelog_file(env)
72
- changelog_file.write_text(f"{changelog_text}\n", encoding="utf-8")
73
-
74
- else:
75
- if runtime.global_cli_options.noop:
76
- noop_report(
77
- f"would have recursively rendered the template directory "
78
- f"{template_dir!r} relative to {repo.working_dir!r}"
79
- )
80
- else:
81
- recursive_render(template_dir, environment=env, _root_dir=repo.working_dir)
82
82
 
83
- if release_tag and isinstance(hvcs_client, RemoteHvcsBase):
84
- if runtime.global_cli_options.noop:
85
- noop_report(
86
- f"would have posted changelog to the release for tag {release_tag}"
87
- )
83
+ if not release_tag:
84
+ return
88
85
 
89
- # note: the following check ensures 'version is not None', but mypy can't follow
90
- version: Version = translator.from_tag(release_tag) # type: ignore[assignment]
91
- if not version:
92
- ctx.fail(
93
- f"Tag {release_tag!r} doesn't match tag format "
94
- f"{translator.tag_format!r}"
95
- )
86
+ if not isinstance(hvcs_client, RemoteHvcsBase):
87
+ click.echo(
88
+ "Remote does not support releases. Skipping release notes update...",
89
+ err=True,
90
+ )
91
+ return
96
92
 
97
- try:
98
- release = rh.released[version]
99
- except KeyError:
100
- ctx.fail(f"tag {release_tag} not in release history")
101
-
102
- template = get_release_notes_template(template_dir)
103
- release_notes = render_release_notes(
104
- release_notes_template=template,
105
- template_environment=env,
106
- version=version,
107
- release=release,
93
+ if not (version := translator.from_tag(release_tag)):
94
+ click.echo(
95
+ str.join(
96
+ " ",
97
+ [
98
+ f"Tag {release_tag!r} does not match the tag format",
99
+ repr(translator.tag_format),
100
+ ],
101
+ ),
102
+ err=True,
108
103
  )
104
+ ctx.exit(1)
109
105
 
110
- if runtime.global_cli_options.noop:
111
- noop_report(
112
- "would have posted the following release notes:\n" + release_notes
113
- )
114
- else:
115
- try:
116
- hvcs_client.create_or_update_release(
117
- release_tag, f"{release_notes}\n", prerelease=version.is_prerelease
118
- )
119
- except Exception as e:
120
- log.exception(e)
121
- ctx.fail(str(e))
106
+ try:
107
+ release = release_history.released[version]
108
+ except KeyError:
109
+ click.echo(f"tag {release_tag} not in release history", err=True)
110
+ ctx.exit(2)
111
+
112
+ release_notes = generate_release_notes(
113
+ hvcs_client,
114
+ release,
115
+ template_dir=runtime.template_dir,
116
+ )
117
+
118
+ try:
119
+ post_release_notes(
120
+ release_tag=release_tag,
121
+ release_notes=release_notes,
122
+ prerelease=version.is_prerelease,
123
+ hvcs_client=hvcs_client,
124
+ noop=runtime.global_cli_options.noop,
125
+ )
126
+ except Exception as e:
127
+ log.exception(e)
128
+ click.echo("Failed to post release notes to remote", err=True)
129
+ ctx.exit(1)