python-semantic-release 10.3.0__py3-none-any.whl → 10.5.2__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 (23) hide show
  1. {python_semantic_release-10.3.0.dist-info → python_semantic_release-10.5.2.dist-info}/METADATA +12 -10
  2. {python_semantic_release-10.3.0.dist-info → python_semantic_release-10.5.2.dist-info}/RECORD +23 -19
  3. semantic_release/changelog/context.py +1 -1
  4. semantic_release/cli/commands/version.py +90 -16
  5. semantic_release/cli/config.py +8 -3
  6. semantic_release/cli/github_actions_output.py +21 -6
  7. semantic_release/cli/masking_filter.py +1 -3
  8. semantic_release/commit_parser/__init__.py +23 -0
  9. semantic_release/commit_parser/conventional/__init__.py +17 -0
  10. semantic_release/commit_parser/conventional/options.py +72 -0
  11. semantic_release/commit_parser/conventional/options_monorepo.py +90 -0
  12. semantic_release/commit_parser/{conventional.py → conventional/parser.py} +85 -127
  13. semantic_release/commit_parser/conventional/parser_monorepo.py +467 -0
  14. semantic_release/errors.py +16 -0
  15. semantic_release/gitproject.py +187 -20
  16. semantic_release/version/algorithm.py +5 -18
  17. semantic_release/version/declarations/pattern.py +2 -2
  18. semantic_release/version/translator.py +23 -5
  19. semantic_release/version/version.py +9 -0
  20. {python_semantic_release-10.3.0.dist-info → python_semantic_release-10.5.2.dist-info}/WHEEL +0 -0
  21. {python_semantic_release-10.3.0.dist-info → python_semantic_release-10.5.2.dist-info}/entry_points.txt +0 -0
  22. {python_semantic_release-10.3.0.dist-info → python_semantic_release-10.5.2.dist-info}/licenses/LICENSE +0 -0
  23. {python_semantic_release-10.3.0.dist-info → python_semantic_release-10.5.2.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-semantic-release
3
- Version: 10.3.0
3
+ Version: 10.5.2
4
4
  Summary: Automatic Semantic Versioning for Python projects
5
- Author-email: Rolf Erik Lekang <me@rolflekang.com>
5
+ Author-email: Rolf Erik Lekang <me@rolflekang.com>, codejedi365 <codejedi365@gmail.com>
6
6
  License: MIT
7
7
  Project-URL: changelog, https://github.com/python-semantic-release/python-semantic-release/blob/master/CHANGELOG.md
8
8
  Project-URL: documentation, https://python-semantic-release.readthedocs.io
@@ -17,7 +17,8 @@ Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Python :: 3.13
20
- Requires-Python: >=3.8
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Requires-Python: ~=3.8
21
22
  Description-Content-Type: text/x-rst
22
23
  License-File: LICENSE
23
24
  Requires-Dist: click~=8.1.0
@@ -26,7 +27,7 @@ Requires-Dist: gitpython~=3.0
26
27
  Requires-Dist: requests~=2.25
27
28
  Requires-Dist: jinja2~=3.1
28
29
  Requires-Dist: python-gitlab<7.0.0,>=4.0.0
29
- Requires-Dist: tomlkit~=0.11
30
+ Requires-Dist: tomlkit~=0.13.0
30
31
  Requires-Dist: dotty-dict~=1.3
31
32
  Requires-Dist: importlib-resources~=6.0
32
33
  Requires-Dist: pydantic~=2.0
@@ -35,11 +36,12 @@ Requires-Dist: shellingham~=1.5
35
36
  Requires-Dist: Deprecated~=1.2
36
37
  Provides-Extra: build
37
38
  Requires-Dist: build~=1.2; extra == "build"
39
+ Requires-Dist: tomlkit~=0.13.0; extra == "build"
38
40
  Provides-Extra: docs
39
- Requires-Dist: Sphinx~=6.0; extra == "docs"
40
- Requires-Dist: sphinxcontrib-apidoc==0.5.0; extra == "docs"
41
+ Requires-Dist: Sphinx~=7.4; extra == "docs"
42
+ Requires-Dist: sphinxcontrib-apidoc==0.6.0; extra == "docs"
41
43
  Requires-Dist: sphinx-autobuild==2024.2.4; extra == "docs"
42
- Requires-Dist: furo~=2024.1; extra == "docs"
44
+ Requires-Dist: furo~=2025.9; extra == "docs"
43
45
  Provides-Extra: test
44
46
  Requires-Dist: coverage[toml]~=7.0; extra == "test"
45
47
  Requires-Dist: filelock~=3.15; extra == "test"
@@ -48,9 +50,9 @@ Requires-Dist: freezegun~=1.5; extra == "test"
48
50
  Requires-Dist: pyyaml~=6.0; extra == "test"
49
51
  Requires-Dist: pytest~=8.3; extra == "test"
50
52
  Requires-Dist: pytest-clarity~=1.0; extra == "test"
51
- Requires-Dist: pytest-cov<7.0.0,>=5.0.0; extra == "test"
53
+ Requires-Dist: pytest-cov<8.0.0,>=5.0.0; extra == "test"
52
54
  Requires-Dist: pytest-env~=1.0; extra == "test"
53
- Requires-Dist: pytest-lazy-fixtures~=1.1.1; extra == "test"
55
+ Requires-Dist: pytest-lazy-fixtures~=1.4; extra == "test"
54
56
  Requires-Dist: pytest-mock~=3.0; extra == "test"
55
57
  Requires-Dist: pytest-order~=1.3; extra == "test"
56
58
  Requires-Dist: pytest-pretty~=1.2; extra == "test"
@@ -58,7 +60,7 @@ Requires-Dist: pytest-xdist~=3.0; extra == "test"
58
60
  Requires-Dist: responses~=0.25.0; extra == "test"
59
61
  Requires-Dist: requests-mock~=1.10; extra == "test"
60
62
  Provides-Extra: dev
61
- Requires-Dist: pre-commit~=3.5; extra == "dev"
63
+ Requires-Dist: pre-commit~=4.3; extra == "dev"
62
64
  Requires-Dist: tox~=4.11; extra == "dev"
63
65
  Requires-Dist: ruff==0.6.1; extra == "dev"
64
66
  Provides-Extra: mypy
@@ -1,40 +1,44 @@
1
- python_semantic_release-10.3.0.dist-info/licenses/LICENSE,sha256=NE85nszX252sdQdu0xgS9qwfYES0k8qS6gW3uO4jRGE,1083
1
+ python_semantic_release-10.5.2.dist-info/licenses/LICENSE,sha256=NE85nszX252sdQdu0xgS9qwfYES0k8qS6gW3uO4jRGE,1083
2
2
  semantic_release/__init__.py,sha256=tRJWhrn_dUt0QycXD2DoJSfEP5uwmxngH7jvbG2i-hA,1317
3
3
  semantic_release/__main__.py,sha256=pksxr6g1vkKq98Q1lShsxG8tk55IMiSMHzAHKyFU5x0,1704
4
4
  semantic_release/const.py,sha256=wInJR7vcOgT1ysm5VuJQ6lD_ZGYnCwRVKz7Uz3htQc4,861
5
5
  semantic_release/enums.py,sha256=vrEw1UNRcNrFjPqOFnuUzfeoqKj0ChixVVlyk5fqbng,1744
6
- semantic_release/errors.py,sha256=PY9rmviSFBZkqawW6VXbUfmF9C_RNOIObcmeGxLefMo,2904
7
- semantic_release/gitproject.py,sha256=UMvxcxqu9jRnZ3C5miNYYDh2i6hygLNEqo3-TuW4Pj4,9423
6
+ semantic_release/errors.py,sha256=FyocaqHbRhux-iNmCf9nI7awyUaGKjG9_5C_QDvhEas,3399
7
+ semantic_release/gitproject.py,sha256=qF4GVZh-aaS4bVV3OmFQLmXMAxHNFyZCwUaWPtUmZ1k,16546
8
8
  semantic_release/globals.py,sha256=IBhBbhZr2jx8dmpySnnu9m9jOGYu9Yu-vqHvAGQxgnw,464
9
9
  semantic_release/helpers.py,sha256=2uQXOiuiemiviVD52SH2FF6cn14p_gqvREeHvP7dexw,10012
10
10
  semantic_release/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  semantic_release/changelog/__init__.py,sha256=Bg6Xe5Vt32rWoMscW-hd4sUwiZqzWmsg4CD1EhMesMY,262
12
- semantic_release/changelog/context.py,sha256=WeLQ2BvYEWunIF8XEl6ldQE4IRg0d7r8mYSBi-TqK7o,5988
12
+ semantic_release/changelog/context.py,sha256=234LLcB_uuGaQ5N_zTbcQYF27ZwYIBmicIWkhFs7-aQ,5993
13
13
  semantic_release/changelog/release_history.py,sha256=kX6D9VReq85wnHz0D6JpmJJCfWA6axV6RToSM0O5laU,10602
14
14
  semantic_release/changelog/template.py,sha256=eqOjtVfBbksgTK4CAZOajfkaAcKueJ-FhFv99U3-J5E,5695
15
15
  semantic_release/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  semantic_release/cli/changelog_writer.py,sha256=2jP5b0LK61Y2tb22GQSFFmwHwXd2WjbopgsMAmsQsfY,9284
17
17
  semantic_release/cli/cli_context.py,sha256=Nop71LdVCJOeSUHgTXunMyK3xAu_QKQC2cRp1QBVkX0,4134
18
- semantic_release/cli/config.py,sha256=4UCx4-jzKpggb7dZM3fnZoi8rPiUGWtEmLiCfWlro7I,33458
18
+ semantic_release/cli/config.py,sha256=zNXlJHZBnFk2GEemgCg2Yw42EWihtFMot1twI7bEN-g,33668
19
19
  semantic_release/cli/const.py,sha256=h7XE2D0D__TAZSrUUtVszwvzpkHTMOiQCf97XQNbEvA,163
20
- semantic_release/cli/github_actions_output.py,sha256=3oWCUfOqa2qVV9DBgwYx2pvhYecVBF6DkzILNnDBikQ,4965
21
- semantic_release/cli/masking_filter.py,sha256=GsTyaoZbUVJLXVMqeXhCttXK84UnBQ8cNDSHxd52sOc,3218
20
+ semantic_release/cli/github_actions_output.py,sha256=yC0nsMvEFGACjDwB8DdmGKwNGI8aIIhDxRHrmcS7tzA,5410
21
+ semantic_release/cli/masking_filter.py,sha256=7t7XFL7Iy4QTvaYevZ-jnTkJBz15GoBw1vjW3hihe38,3159
22
22
  semantic_release/cli/util.py,sha256=4rf4xDHb7l-XpdcFNtslFz1xHslnBu6cTgWHXVnQXQs,3746
23
23
  semantic_release/cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  semantic_release/cli/commands/changelog.py,sha256=wJfd4VVfrGnu2jnIpG25cdVcbXIX-ElKl3b2jdtPPa0,5391
25
25
  semantic_release/cli/commands/generate_config.py,sha256=2xZOu3NpyhBp0pWr7d8ugKl_kjqQgpSsSMHq5wHTfrE,1699
26
26
  semantic_release/cli/commands/main.py,sha256=u1zhkkvKCZ2TtUqjzvdFTe5UZsvfws_pjqqo6CY0bBo,4351
27
27
  semantic_release/cli/commands/publish.py,sha256=CE_LJTxFnc337MfpsfdJopi7QCwwE13GqGNQ-dNgWis,2871
28
- semantic_release/cli/commands/version.py,sha256=lV4GiDbo27-du_pF1ImxmdX569BgyzryhP9UZxDKYAk,26434
29
- semantic_release/commit_parser/__init__.py,sha256=6euiDgj9bwOx1rP96vUjq090usviXkbo7OVOnRBGfcw,742
28
+ semantic_release/cli/commands/version.py,sha256=1zJ96_WSQh4bY_75P3eTJTfwrEZcJGHf7XJO2yA66SA,29459
29
+ semantic_release/commit_parser/__init__.py,sha256=uCC2YI-EDPUAa6TwWYJA8TS7baWE2bXrLEGcrhJKofI,1323
30
30
  semantic_release/commit_parser/_base.py,sha256=DLsHnbXG-39JkUbcnsBCSV2GmV35w1rasyoMhK8G0UE,3058
31
31
  semantic_release/commit_parser/angular.py,sha256=MY_fo9F4EZ-ac8wYzBR0uD94O5Li2D-8zEMR01wss4c,18534
32
- semantic_release/commit_parser/conventional.py,sha256=Iyy8txkshK8HikizFVa7pnJHBSWDhOhF45zR866QxQM,18672
33
32
  semantic_release/commit_parser/emoji.py,sha256=0VecUMMcj43UaKDZ2GaFFUG26zOJAvXsEobTHaXNkk0,17749
34
33
  semantic_release/commit_parser/scipy.py,sha256=gA9TfmrxGVvtv6kki6N_9abJx-_WlpBsSX2TBh0YgPw,19463
35
34
  semantic_release/commit_parser/tag.py,sha256=bVO2XghM0G_eW2rG9Xc2q5TPsjtxr-xcHK5RpE1u_HM,3537
36
35
  semantic_release/commit_parser/token.py,sha256=1_q8mJ4SRu7kNfa-Nxr8fEyuvCfjPgiPEitqSP1KR5g,7904
37
36
  semantic_release/commit_parser/util.py,sha256=hcLjc16o7l6y_5Hfl8IxmF4ijaJD62JdjdB2DJWAe-g,3959
37
+ semantic_release/commit_parser/conventional/__init__.py,sha256=JHPL3S6trM4Le9MXnXc6iPYxxQnE4j0LG83_BWIomOw,602
38
+ semantic_release/commit_parser/conventional/options.py,sha256=AnRgTRMF-3X5QonTqq6bqJEbmGgQBzppyRZ79hct28g,2598
39
+ semantic_release/commit_parser/conventional/options_monorepo.py,sha256=7OCpuaYhw01bfoffTGfPgEWzRRyVsdsq_n2eo8Dt8-8,3210
40
+ semantic_release/commit_parser/conventional/parser.py,sha256=Pex26cvMqAKx9NPH3KROYg7k76R0Nsbs4aHor5SbMZg,16671
41
+ semantic_release/commit_parser/conventional/parser_monorepo.py,sha256=nVAc_gK4tJ5UM_rd9KbXgBWeBgrI_q2-vav3_4748r8,21189
38
42
  semantic_release/data/templates/conventional/md/.release_notes.md.j2,sha256=DlMVAJMGqE27TwJ-2kviYaFhd3uWqXiU6Ikl15Ukne8,2512
39
43
  semantic_release/data/templates/conventional/md/CHANGELOG.md.j2,sha256=FZmrQ-qOIoSoJmAa_NFaRelfmqUpypU2xlDeScdGOf4,729
40
44
  semantic_release/data/templates/conventional/md/.components/changelog_header.md.j2,sha256=qNxTuSr59CV_yyimVU_RYp5azCnK0l6nJ03Zf0u5Ugg,166
@@ -64,17 +68,17 @@ semantic_release/hvcs/remote_hvcs_base.py,sha256=QtkjdMy9l-c7UOtyPz25cqVOkCk4IU-
64
68
  semantic_release/hvcs/token_auth.py,sha256=ZjT56-NIPB4OKIt1qwHCu1TavXnrWFIBl9ARlg56hgU,663
65
69
  semantic_release/hvcs/util.py,sha256=PUNV4yUlpzDtNCFmh2joaPdU4JyfUBnVp0zaQsT9EDQ,2871
66
70
  semantic_release/version/__init__.py,sha256=CLhtGQry9dLIij5XyRa9ZevxU_1p8tjMTSQ-K_GMpWM,270
67
- semantic_release/version/algorithm.py,sha256=84N2pisD-W9z2WuCNqwq1WHteGlnM4ECKt1maAXP8N8,16687
71
+ semantic_release/version/algorithm.py,sha256=IxgYNF78W7qdMzdV4WsoljwfJgB-sn2XdfkevD0aZlo,16254
68
72
  semantic_release/version/declaration.py,sha256=eot_lUyFaEhzK4bPncfv9tahf51LdxZP6EaS54h3aAs,3635
69
- semantic_release/version/translator.py,sha256=LjmsMHWJJOG8ES6lRhjQsP2t8pmw4Ux1XVYzvROMR_M,3047
70
- semantic_release/version/version.py,sha256=3QlPKsrmNnFH71GlsYvpI-WwhpkJEs_JrDBbMaepwJY,14183
73
+ semantic_release/version/translator.py,sha256=iIfu3WreB9qqPHgqJLILbBluVQQNcpP0DEsnn_WzAaM,3689
74
+ semantic_release/version/version.py,sha256=Y3Qqqv7CypirikT7jNqqFMNAvR2UjV-VQx-c2G0mZc0,14519
71
75
  semantic_release/version/declarations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
76
  semantic_release/version/declarations/enum.py,sha256=3n5Py9DoFkmItIdsmtQrJgmAhepTv_1EnogzSdXu1Wg,244
73
77
  semantic_release/version/declarations/i_version_replacer.py,sha256=oP6BxJuxwI44roI6448tomShv1sMoy9ry8TlhhIQtfc,2416
74
- semantic_release/version/declarations/pattern.py,sha256=MpUmsHYGAVAuFSKSb29FLcWeUCEHG_TRyhMO-2DWAAs,8308
78
+ semantic_release/version/declarations/pattern.py,sha256=sKk0uQpJjWVZc8RJUjxQoEPUvFLxXNGGBow5h1IqCTM,8378
75
79
  semantic_release/version/declarations/toml.py,sha256=2K4DtX5Qq1iHT8cG8mISPTMmp50w6Av0KmLAKZPYqq8,4931
76
- python_semantic_release-10.3.0.dist-info/METADATA,sha256=KC3FGBwckcF-gL-W3C-Xnvd-T1XHSQjwAgIinftCf4o,3927
77
- python_semantic_release-10.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
78
- python_semantic_release-10.3.0.dist-info/entry_points.txt,sha256=kzkCyDJsMOwgpFwEWKE9wxN1tXaUP6g6GIO4xtc0QuE,162
79
- python_semantic_release-10.3.0.dist-info/top_level.txt,sha256=qYA24nyg3eP-ti5UW7Vuj2aXVmM0wqVHx4mREdRZNAA,17
80
- python_semantic_release-10.3.0.dist-info/RECORD,,
80
+ python_semantic_release-10.5.2.dist-info/METADATA,sha256=RMojFB6HaaUcQbydFl05KFC6aIj2OhRVxHRRVeqZrqI,4064
81
+ python_semantic_release-10.5.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
82
+ python_semantic_release-10.5.2.dist-info/entry_points.txt,sha256=kzkCyDJsMOwgpFwEWKE9wxN1tXaUP6g6GIO4xtc0QuE,162
83
+ python_semantic_release-10.5.2.dist-info/top_level.txt,sha256=qYA24nyg3eP-ti5UW7Vuj2aXVmM0wqVHx4mREdRZNAA,17
84
+ python_semantic_release-10.5.2.dist-info/RECORD,,
@@ -119,7 +119,7 @@ def read_file(filepath: str) -> str:
119
119
  return rfd.read()
120
120
 
121
121
  except FileNotFoundError as err:
122
- logging.warning(err)
122
+ logging.warning(str(err))
123
123
  return ""
124
124
 
125
125
 
@@ -10,7 +10,7 @@ from typing import TYPE_CHECKING
10
10
  import click
11
11
  import shellingham # type: ignore[import]
12
12
  from click_option_group import MutuallyExclusiveOptionGroup, optgroup
13
- from git import Repo
13
+ from git import GitCommandError, Repo
14
14
  from requests import HTTPError
15
15
 
16
16
  from semantic_release.changelog.release_history import ReleaseHistory
@@ -18,15 +18,23 @@ from semantic_release.cli.changelog_writer import (
18
18
  generate_release_notes,
19
19
  write_changelog_files,
20
20
  )
21
- from semantic_release.cli.github_actions_output import VersionGitHubActionsOutput
21
+ from semantic_release.cli.github_actions_output import (
22
+ PersistenceMode,
23
+ VersionGitHubActionsOutput,
24
+ )
22
25
  from semantic_release.cli.util import noop_report, rprint
23
26
  from semantic_release.const import DEFAULT_SHELL, DEFAULT_VERSION
24
27
  from semantic_release.enums import LevelBump
25
28
  from semantic_release.errors import (
26
29
  BuildDistributionsError,
30
+ DetachedHeadGitError,
27
31
  GitCommitEmptyIndexError,
32
+ GitFetchError,
28
33
  InternalError,
34
+ LocalGitError,
29
35
  UnexpectedResponse,
36
+ UnknownUpstreamBranchError,
37
+ UpstreamBranchChangedError,
30
38
  )
31
39
  from semantic_release.gitproject import GitProject
32
40
  from semantic_release.globals import logger
@@ -72,10 +80,13 @@ def is_forced_prerelease(
72
80
  )
73
81
 
74
82
 
75
- def last_released(repo_dir: Path, tag_format: str) -> tuple[Tag, Version] | None:
83
+ def last_released(
84
+ repo_dir: Path, tag_format: str, add_partial_tags: bool = False
85
+ ) -> tuple[Tag, Version] | None:
76
86
  with Repo(str(repo_dir)) as git_repo:
77
87
  ts_and_vs = tags_and_versions(
78
- git_repo.tags, VersionTranslator(tag_format=tag_format)
88
+ git_repo.tags,
89
+ VersionTranslator(tag_format=tag_format, add_partial_tags=add_partial_tags),
79
90
  )
80
91
 
81
92
  return ts_and_vs[0] if ts_and_vs else None
@@ -446,7 +457,11 @@ def version( # noqa: C901
446
457
  if print_last_released or print_last_released_tag:
447
458
  # TODO: get tag format a better way
448
459
  if not (
449
- last_release := last_released(config.repo_dir, tag_format=config.tag_format)
460
+ last_release := last_released(
461
+ config.repo_dir,
462
+ tag_format=config.tag_format,
463
+ add_partial_tags=config.add_partial_tags,
464
+ )
450
465
  ):
451
466
  logger.warning("No release tags found.")
452
467
  return
@@ -467,10 +482,14 @@ def version( # noqa: C901
467
482
  major_on_zero = runtime.major_on_zero
468
483
  no_verify = runtime.no_git_verify
469
484
  opts = runtime.global_cli_options
485
+ add_partial_tags = config.add_partial_tags
470
486
  gha_output = VersionGitHubActionsOutput(
471
- hvcs_client
472
- if isinstance(hvcs_client, Github)
473
- else Github(hvcs_client.remote_url(use_token=False)),
487
+ gh_client=hvcs_client if isinstance(hvcs_client, Github) else None,
488
+ mode=(
489
+ PersistenceMode.TEMPORARY
490
+ if opts.noop or (not commit_changes and not create_tag)
491
+ else PersistenceMode.PERMANENT
492
+ ),
474
493
  released=False,
475
494
  )
476
495
 
@@ -485,6 +504,17 @@ def version( # noqa: C901
485
504
  logger.info("Forcing use of %s as the prerelease token", prerelease_token)
486
505
  translator.prerelease_token = prerelease_token
487
506
 
507
+ # Check if the repository is shallow and unshallow it if necessary
508
+ # This ensures we have the full history for commit analysis
509
+ project = GitProject(
510
+ directory=runtime.repo_dir,
511
+ commit_author=runtime.commit_author,
512
+ credential_masker=runtime.masker,
513
+ )
514
+ if project.is_shallow_clone():
515
+ logger.info("Repository is a shallow clone, converting to full clone...")
516
+ project.git_unshallow(noop=opts.noop)
517
+
488
518
  # Only push if we're committing changes
489
519
  if push_changes and not commit_changes and not create_tag:
490
520
  logger.info("changes will not be pushed because --no-commit disables pushing")
@@ -543,7 +573,8 @@ def version( # noqa: C901
543
573
 
544
574
  # Update GitHub Actions output value with new version & set delayed write
545
575
  gha_output.version = new_version
546
- ctx.call_on_close(gha_output.write_if_possible)
576
+ if isinstance(hvcs_client, Github):
577
+ ctx.call_on_close(gha_output.write_if_possible)
547
578
 
548
579
  # Make string variant of version or appropriate tag as necessary
549
580
  version_to_print = str(new_version) if not print_only_tag else new_version.as_tag()
@@ -676,12 +707,6 @@ def version( # noqa: C901
676
707
  license_name="" if not isinstance(license_cfg, str) else license_cfg,
677
708
  )
678
709
 
679
- project = GitProject(
680
- directory=runtime.repo_dir,
681
- commit_author=runtime.commit_author,
682
- credential_masker=runtime.masker,
683
- )
684
-
685
710
  # Preparing for committing changes; we always stage files even if we're not committing them in order to support a two-stage commit
686
711
  project.git_add(paths=all_paths_to_add, noop=opts.noop)
687
712
  if commit_changes:
@@ -697,13 +722,14 @@ def version( # noqa: C901
697
722
  )
698
723
  except GitCommitEmptyIndexError:
699
724
  logger.info("No local changes to add to any commit, skipping")
725
+ commit_changes = False
700
726
 
701
727
  # Tag the version after potentially creating a new HEAD commit.
702
728
  # This way if no source code is modified, i.e. all metadata updates
703
729
  # are disabled, and the changelog generation is disabled or it's not
704
730
  # modified, then the HEAD commit will be tagged as a release commit
705
731
  # despite not being made by PSR
706
- if commit_changes or create_tag:
732
+ if create_tag:
707
733
  project.git_tag(
708
734
  tag_name=new_version.as_tag(),
709
735
  message=new_version.as_tag(),
@@ -720,6 +746,33 @@ def version( # noqa: C901
720
746
  )
721
747
 
722
748
  if commit_changes:
749
+ # Verify that the upstream branch has not changed before pushing
750
+ # This prevents conflicts if another commit was pushed while we were preparing the release
751
+ # We check HEAD~1 because we just made a release commit
752
+ try:
753
+ project.verify_upstream_unchanged(
754
+ local_ref="HEAD~1",
755
+ upstream_ref=config.remote.name,
756
+ noop=opts.noop,
757
+ )
758
+ except UpstreamBranchChangedError as exc:
759
+ click.echo(str(exc), err=True)
760
+ click.echo(
761
+ "Upstream branch has changed. Please pull the latest changes and try again.",
762
+ err=True,
763
+ )
764
+ ctx.exit(1)
765
+ except (
766
+ DetachedHeadGitError,
767
+ GitCommandError,
768
+ UnknownUpstreamBranchError,
769
+ GitFetchError,
770
+ LocalGitError,
771
+ ) as exc:
772
+ click.echo(str(exc), err=True)
773
+ click.echo("Unable to verify upstream due to error!", err=True)
774
+ ctx.exit(1)
775
+
723
776
  # TODO: integrate into push branch
724
777
  with Repo(str(runtime.repo_dir)) as git_repo:
725
778
  active_branch = git_repo.active_branch.name
@@ -737,6 +790,27 @@ def version( # noqa: C901
737
790
  tag=new_version.as_tag(),
738
791
  noop=opts.noop,
739
792
  )
793
+ # Create or update partial tags for releases
794
+ if add_partial_tags and not prerelease:
795
+ partial_tags = [new_version.as_major_tag(), new_version.as_minor_tag()]
796
+ # If build metadata is set, also retag the version without the metadata
797
+ if build_metadata:
798
+ partial_tags.append(new_version.as_patch_tag())
799
+
800
+ for partial_tag in partial_tags:
801
+ project.git_tag(
802
+ tag_name=partial_tag,
803
+ message=f"{partial_tag} => {new_version.as_tag()}",
804
+ isotimestamp=commit_date.isoformat(),
805
+ noop=opts.noop,
806
+ force=True,
807
+ )
808
+ project.git_push_tag(
809
+ remote_url=remote_url,
810
+ tag=partial_tag,
811
+ noop=opts.noop,
812
+ force=True,
813
+ )
740
814
 
741
815
  # Update GitHub Actions output value now that release has occurred
742
816
  gha_output.released = True
@@ -39,6 +39,7 @@ from semantic_release.cli.masking_filter import MaskingFilter
39
39
  from semantic_release.commit_parser import (
40
40
  AngularCommitParser,
41
41
  CommitParser,
42
+ ConventionalCommitMonorepoParser,
42
43
  ConventionalCommitParser,
43
44
  EmojiCommitParser,
44
45
  ParseResult,
@@ -71,9 +72,10 @@ class HvcsClient(str, Enum):
71
72
  GITEA = "gitea"
72
73
 
73
74
 
74
- _known_commit_parsers: Dict[str, type[CommitParser]] = {
75
- "conventional": ConventionalCommitParser,
75
+ _known_commit_parsers: dict[str, type[CommitParser[Any, Any]]] = {
76
76
  "angular": AngularCommitParser,
77
+ "conventional": ConventionalCommitParser,
78
+ "conventional-monorepo": ConventionalCommitMonorepoParser,
77
79
  "emoji": EmojiCommitParser,
78
80
  "scipy": ScipyCommitParser,
79
81
  "tag": TagCommitParser,
@@ -364,6 +366,7 @@ class RawConfig(BaseModel):
364
366
  remote: RemoteConfig = RemoteConfig()
365
367
  no_git_verify: bool = False
366
368
  tag_format: str = "v{version}"
369
+ add_partial_tags: bool = False
367
370
  publish: PublishConfig = PublishConfig()
368
371
  version_toml: Optional[Tuple[str, ...]] = None
369
372
  version_variables: Optional[Tuple[str, ...]] = None
@@ -825,7 +828,9 @@ class RuntimeContext:
825
828
 
826
829
  # version_translator
827
830
  version_translator = VersionTranslator(
828
- tag_format=raw.tag_format, prerelease_token=branch_config.prerelease_token
831
+ tag_format=raw.tag_format,
832
+ prerelease_token=branch_config.prerelease_token,
833
+ add_partial_tags=raw.add_partial_tags,
829
834
  )
830
835
 
831
836
  build_cmd_env = {}
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
+ from enum import Enum
4
5
  from re import compile as regexp
5
6
  from typing import TYPE_CHECKING
6
7
 
@@ -13,12 +14,18 @@ if TYPE_CHECKING:
13
14
  from semantic_release.hvcs.github import Github
14
15
 
15
16
 
17
+ class PersistenceMode(Enum):
18
+ TEMPORARY = "temporary"
19
+ PERMANENT = "permanent"
20
+
21
+
16
22
  class VersionGitHubActionsOutput:
17
23
  OUTPUT_ENV_VAR = "GITHUB_OUTPUT"
18
24
 
19
25
  def __init__(
20
26
  self,
21
- gh_client: Github,
27
+ gh_client: Github | None = None,
28
+ mode: PersistenceMode = PersistenceMode.PERMANENT,
22
29
  released: bool | None = None,
23
30
  version: Version | None = None,
24
31
  commit_sha: str | None = None,
@@ -26,6 +33,7 @@ class VersionGitHubActionsOutput:
26
33
  prev_version: Version | None = None,
27
34
  ) -> None:
28
35
  self._gh_client = gh_client
36
+ self._mode = mode
29
37
  self._released = released
30
38
  self._version = version
31
39
  self._commit_sha = commit_sha
@@ -98,16 +106,23 @@ class VersionGitHubActionsOutput:
98
106
  raise TypeError("output 'prev_version' should be a Version")
99
107
  self._prev_version = value
100
108
 
109
+ @property
110
+ def gh_client(self) -> Github:
111
+ if not self._gh_client:
112
+ raise ValueError("GitHub client not set, cannot create links")
113
+ return self._gh_client
114
+
101
115
  def to_output_text(self) -> str:
102
116
  missing: set[str] = set()
103
117
  if self.version is None:
104
118
  missing.add("version")
105
119
  if self.released is None:
106
120
  missing.add("released")
107
- if self.released and self.commit_sha is None:
108
- missing.add("commit_sha")
109
- if self.released and self.release_notes is None:
110
- missing.add("release_notes")
121
+ if self.released:
122
+ if self.release_notes is None:
123
+ missing.add("release_notes")
124
+ if self._mode is PersistenceMode.PERMANENT and self.commit_sha is None:
125
+ missing.add("commit_sha")
111
126
 
112
127
  if missing:
113
128
  raise ValueError(
@@ -119,7 +134,7 @@ class VersionGitHubActionsOutput:
119
134
  "version": str(self.version),
120
135
  "tag": self.tag,
121
136
  "is_prerelease": str(self.is_prerelease).lower(),
122
- "link": self._gh_client.create_release_url(self.tag) if self.tag else "",
137
+ "link": self.gh_client.create_release_url(self.tag) if self.tag else "",
123
138
  "previous_version": str(self.prev_version) if self.prev_version else "",
124
139
  "commit_sha": self.commit_sha if self.commit_sha else "",
125
140
  }
@@ -62,9 +62,7 @@ class MaskingFilter(LoggingFilter):
62
62
 
63
63
  def mask(self, msg: str) -> str:
64
64
  if not isinstance(msg, str):
65
- logger.debug( # type: ignore[unreachable]
66
- "cannot mask object of type %s", type(msg)
67
- )
65
+ logger.debug("cannot mask object of type %s", type(msg))
68
66
  return msg
69
67
  for mask, values in self._redact_patterns.items():
70
68
  repl_string = (
@@ -7,6 +7,8 @@ from semantic_release.commit_parser.angular import (
7
7
  AngularParserOptions,
8
8
  )
9
9
  from semantic_release.commit_parser.conventional import (
10
+ ConventionalCommitMonorepoParser,
11
+ ConventionalCommitMonorepoParserOptions,
10
12
  ConventionalCommitParser,
11
13
  ConventionalCommitParserOptions,
12
14
  )
@@ -28,3 +30,24 @@ from semantic_release.commit_parser.token import (
28
30
  ParseResult,
29
31
  ParseResultType,
30
32
  )
33
+
34
+ __all__ = [
35
+ "CommitParser",
36
+ "ParserOptions",
37
+ "AngularCommitParser",
38
+ "AngularParserOptions",
39
+ "ConventionalCommitParser",
40
+ "ConventionalCommitParserOptions",
41
+ "ConventionalCommitMonorepoParser",
42
+ "ConventionalCommitMonorepoParserOptions",
43
+ "EmojiCommitParser",
44
+ "EmojiParserOptions",
45
+ "ScipyCommitParser",
46
+ "ScipyParserOptions",
47
+ "TagCommitParser",
48
+ "TagParserOptions",
49
+ "ParsedCommit",
50
+ "ParseError",
51
+ "ParseResult",
52
+ "ParseResultType",
53
+ ]
@@ -0,0 +1,17 @@
1
+ from semantic_release.commit_parser.conventional.options import (
2
+ ConventionalCommitParserOptions,
3
+ )
4
+ from semantic_release.commit_parser.conventional.options_monorepo import (
5
+ ConventionalCommitMonorepoParserOptions,
6
+ )
7
+ from semantic_release.commit_parser.conventional.parser import ConventionalCommitParser
8
+ from semantic_release.commit_parser.conventional.parser_monorepo import (
9
+ ConventionalCommitMonorepoParser,
10
+ )
11
+
12
+ __all__ = [
13
+ "ConventionalCommitParser",
14
+ "ConventionalCommitParserOptions",
15
+ "ConventionalCommitMonorepoParser",
16
+ "ConventionalCommitMonorepoParserOptions",
17
+ ]
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ from itertools import zip_longest
4
+ from typing import Tuple
5
+
6
+ from pydantic.dataclasses import dataclass
7
+
8
+ from semantic_release.commit_parser._base import ParserOptions
9
+ from semantic_release.enums import LevelBump
10
+
11
+
12
+ @dataclass
13
+ class ConventionalCommitParserOptions(ParserOptions):
14
+ """Options dataclass for the ConventionalCommitParser."""
15
+
16
+ minor_tags: Tuple[str, ...] = ("feat",)
17
+ """Commit-type prefixes that should result in a minor release bump."""
18
+
19
+ patch_tags: Tuple[str, ...] = ("fix", "perf")
20
+ """Commit-type prefixes that should result in a patch release bump."""
21
+
22
+ other_allowed_tags: Tuple[str, ...] = (
23
+ "build",
24
+ "chore",
25
+ "ci",
26
+ "docs",
27
+ "style",
28
+ "refactor",
29
+ "test",
30
+ )
31
+ """Commit-type prefixes that are allowed but do not result in a version bump."""
32
+
33
+ allowed_tags: Tuple[str, ...] = (
34
+ *minor_tags,
35
+ *patch_tags,
36
+ *other_allowed_tags,
37
+ )
38
+ """
39
+ All commit-type prefixes that are allowed.
40
+
41
+ These are used to identify a valid commit message. If a commit message does not start with
42
+ one of these prefixes, it will not be considered a valid commit message.
43
+ """
44
+
45
+ default_bump_level: LevelBump = LevelBump.NO_RELEASE
46
+ """The minimum bump level to apply to valid commit message."""
47
+
48
+ parse_squash_commits: bool = True
49
+ """Toggle flag for whether or not to parse squash commits"""
50
+
51
+ ignore_merge_commits: bool = True
52
+ """Toggle flag for whether or not to ignore merge commits"""
53
+
54
+ @property
55
+ def tag_to_level(self) -> dict[str, LevelBump]:
56
+ """A mapping of commit tags to the level bump they should result in."""
57
+ return self._tag_to_level
58
+
59
+ def __post_init__(self) -> None:
60
+ self._tag_to_level: dict[str, LevelBump] = {
61
+ str(tag): level
62
+ for tag, level in [
63
+ # we have to do a type ignore as zip_longest provides a type that is not specific enough
64
+ # for our expected output. Due to the empty second array, we know the first is always longest
65
+ # and that means no values in the first entry of the tuples will ever be a LevelBump. We
66
+ # apply a str() to make mypy happy although it will never happen.
67
+ *zip_longest(self.allowed_tags, (), fillvalue=self.default_bump_level),
68
+ *zip_longest(self.patch_tags, (), fillvalue=LevelBump.PATCH),
69
+ *zip_longest(self.minor_tags, (), fillvalue=LevelBump.MINOR),
70
+ ]
71
+ if "|" not in str(tag)
72
+ }