python-semantic-release 9.17.0__py3-none-any.whl → 9.18.0__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.
- {python_semantic_release-9.17.0.dist-info → python_semantic_release-9.18.0.dist-info}/METADATA +1 -1
- {python_semantic_release-9.17.0.dist-info → python_semantic_release-9.18.0.dist-info}/RECORD +34 -34
- semantic_release/__init__.py +1 -1
- semantic_release/changelog/context.py +17 -1
- semantic_release/changelog/release_history.py +39 -5
- semantic_release/cli/changelog_writer.py +4 -0
- semantic_release/cli/commands/changelog.py +45 -1
- semantic_release/cli/commands/version.py +20 -2
- semantic_release/cli/config.py +19 -2
- semantic_release/commit_parser/angular.py +33 -13
- semantic_release/commit_parser/emoji.py +34 -12
- semantic_release/commit_parser/token.py +19 -6
- semantic_release/commit_parser/util.py +11 -3
- semantic_release/const.py +2 -0
- semantic_release/data/templates/angular/md/.components/changes.md.j2 +59 -14
- semantic_release/data/templates/angular/md/.components/first_release.md.j2 +11 -4
- semantic_release/data/templates/angular/md/.components/macros.md.j2 +66 -0
- semantic_release/data/templates/angular/md/.components/versioned_changes.md.j2 +13 -7
- semantic_release/data/templates/angular/md/.release_notes.md.j2 +25 -21
- semantic_release/data/templates/angular/rst/.components/changes.rst.j2 +68 -23
- semantic_release/data/templates/angular/rst/.components/first_release.rst.j2 +2 -0
- semantic_release/data/templates/angular/rst/.components/macros.rst.j2 +66 -0
- semantic_release/data/templates/angular/rst/.components/versioned_changes.rst.j2 +2 -0
- semantic_release/helpers.py +8 -1
- semantic_release/hvcs/bitbucket.py +15 -0
- semantic_release/hvcs/gitea.py +21 -0
- semantic_release/hvcs/github.py +21 -0
- semantic_release/hvcs/gitlab.py +20 -0
- semantic_release/version/algorithm.py +48 -27
- {python_semantic_release-9.17.0.dist-info → python_semantic_release-9.18.0.dist-info}/AUTHORS.rst +0 -0
- {python_semantic_release-9.17.0.dist-info → python_semantic_release-9.18.0.dist-info}/LICENSE +0 -0
- {python_semantic_release-9.17.0.dist-info → python_semantic_release-9.18.0.dist-info}/WHEEL +0 -0
- {python_semantic_release-9.17.0.dist-info → python_semantic_release-9.18.0.dist-info}/entry_points.txt +0 -0
- {python_semantic_release-9.17.0.dist-info → python_semantic_release-9.18.0.dist-info}/top_level.txt +0 -0
{python_semantic_release-9.17.0.dist-info → python_semantic_release-9.18.0.dist-info}/RECORD
RENAMED
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
semantic_release/__init__.py,sha256
|
|
1
|
+
semantic_release/__init__.py,sha256=-F25Nu3RzbeFLRgFP4ms_VHphEUTK1SdYz0y3LLOJB8,1229
|
|
2
2
|
semantic_release/__main__.py,sha256=KOIBOvLruqfi5ArXcWK3ucIZ7NB55kfCbycJaxx6aQg,1485
|
|
3
|
-
semantic_release/const.py,sha256=
|
|
3
|
+
semantic_release/const.py,sha256=wInJR7vcOgT1ysm5VuJQ6lD_ZGYnCwRVKz7Uz3htQc4,861
|
|
4
4
|
semantic_release/enums.py,sha256=vrEw1UNRcNrFjPqOFnuUzfeoqKj0ChixVVlyk5fqbng,1744
|
|
5
5
|
semantic_release/errors.py,sha256=PY9rmviSFBZkqawW6VXbUfmF9C_RNOIObcmeGxLefMo,2904
|
|
6
6
|
semantic_release/gitproject.py,sha256=G4XrucN-ZwT1Kj4RMrABcr1vWb0bjKgurEeJjcL-61c,9422
|
|
7
7
|
semantic_release/globals.py,sha256=imI9WKGa6MS2pTRAZiWZ2qIJup2eWnBz3OZmIj2YIHM,158
|
|
8
|
-
semantic_release/helpers.py,sha256=
|
|
8
|
+
semantic_release/helpers.py,sha256=8yQTYUS3InvEnEqqhzJPM_R-69Pk6k9gF1NgPlgQx1Q,9774
|
|
9
9
|
semantic_release/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
semantic_release/changelog/__init__.py,sha256=Bg6Xe5Vt32rWoMscW-hd4sUwiZqzWmsg4CD1EhMesMY,262
|
|
11
|
-
semantic_release/changelog/context.py,sha256=
|
|
12
|
-
semantic_release/changelog/release_history.py,sha256=
|
|
11
|
+
semantic_release/changelog/context.py,sha256=WeLQ2BvYEWunIF8XEl6ldQE4IRg0d7r8mYSBi-TqK7o,5988
|
|
12
|
+
semantic_release/changelog/release_history.py,sha256=epxN_tFGk3H3OGjiMsvwEVp5wgrTIKn2WRmjK5ZJ6SM,10578
|
|
13
13
|
semantic_release/changelog/template.py,sha256=R3V5m-7kv9ES23e_g37fe17tk-ESgvwV0C9rp5uoeOE,5691
|
|
14
14
|
semantic_release/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
semantic_release/cli/changelog_writer.py,sha256=
|
|
15
|
+
semantic_release/cli/changelog_writer.py,sha256=92DntVFFGdzFc5eBUgrRXbSOSV9KVvV769NyhLOTirg,9281
|
|
16
16
|
semantic_release/cli/cli_context.py,sha256=Nop71LdVCJOeSUHgTXunMyK3xAu_QKQC2cRp1QBVkX0,4134
|
|
17
|
-
semantic_release/cli/config.py,sha256=
|
|
17
|
+
semantic_release/cli/config.py,sha256=rLva6QIB5DJIYtWCnC1GHiHMWbnEkWg_YxIVVhznjnk,33138
|
|
18
18
|
semantic_release/cli/const.py,sha256=h7XE2D0D__TAZSrUUtVszwvzpkHTMOiQCf97XQNbEvA,163
|
|
19
19
|
semantic_release/cli/github_actions_output.py,sha256=6oNwjnQBg9XF5QgGc4TgbwX_-W0aj65VwGSL4ALvqVg,2296
|
|
20
20
|
semantic_release/cli/masking_filter.py,sha256=ric34rnXfN5RiAVVaKnhiMJOxTnEl26kI06jQqZPZoQ,3072
|
|
21
21
|
semantic_release/cli/util.py,sha256=FyXaBkeL7nXKjy3X9rQLEwvn7p46xPekp2V8Z-5MVrk,3755
|
|
22
22
|
semantic_release/cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
-
semantic_release/cli/commands/changelog.py,sha256=
|
|
23
|
+
semantic_release/cli/commands/changelog.py,sha256=i3j6jhcfhFo3d7Bgrp1tD01vJkuTZvtaWX7bPteLQbA,5345
|
|
24
24
|
semantic_release/cli/commands/generate_config.py,sha256=2xZOu3NpyhBp0pWr7d8ugKl_kjqQgpSsSMHq5wHTfrE,1699
|
|
25
25
|
semantic_release/cli/commands/main.py,sha256=237rn_Od4LOWfjUjiUKI_jSV820MfcCtRpwPjxjLbyU,4312
|
|
26
26
|
semantic_release/cli/commands/publish.py,sha256=y_LalPti_kZeQJzl2CR2pTZUK8DCMvNSTe4NaMC5TJA,2875
|
|
27
|
-
semantic_release/cli/commands/version.py,sha256=
|
|
27
|
+
semantic_release/cli/commands/version.py,sha256=BMAOXToqOC_U0nh3d-QszAOS7xhN86aGhFiaePSdfws,25287
|
|
28
28
|
semantic_release/commit_parser/__init__.py,sha256=cv5HFBdw7OJd4Laj4Ex8ZZ5Tml8GwXgQcXW6Pasr2Ao,615
|
|
29
29
|
semantic_release/commit_parser/_base.py,sha256=oDifeTmFDpS238cp_DDrGzfidaKeAD5olCB5IM4Q6z8,3058
|
|
30
|
-
semantic_release/commit_parser/angular.py,sha256=
|
|
31
|
-
semantic_release/commit_parser/emoji.py,sha256=
|
|
30
|
+
semantic_release/commit_parser/angular.py,sha256=UM88ethWT34__kDgsBXXb26d0V8FN1ZFjcU7psuOhgY,19259
|
|
31
|
+
semantic_release/commit_parser/emoji.py,sha256=T3kw8jyq1zYME3f6JJ6O3hVJ2IpRk3DD5LB0VOlcbMM,18194
|
|
32
32
|
semantic_release/commit_parser/scipy.py,sha256=0rYZglJ7uib-1Deu4J30wHh7AZS8KfO0eND82bPtDQ8,4526
|
|
33
33
|
semantic_release/commit_parser/tag.py,sha256=oGB3lgyp2Eu3Tg3jjxqNzN86N6bokSaFu6f4Ir6IS_k,3546
|
|
34
|
-
semantic_release/commit_parser/token.py,sha256=
|
|
35
|
-
semantic_release/commit_parser/util.py,sha256=
|
|
36
|
-
semantic_release/data/templates/angular/md/.release_notes.md.j2,sha256=
|
|
34
|
+
semantic_release/commit_parser/token.py,sha256=ECgi7eeSgk3Biq1Y_ChbFJZQLkrUpNvGhIaEOXrNC4M,7904
|
|
35
|
+
semantic_release/commit_parser/util.py,sha256=_ACiopznjwINn4t1zPHl8bxZEc0zOAURTycNU-sXs3M,3959
|
|
36
|
+
semantic_release/data/templates/angular/md/.release_notes.md.j2,sha256=3d4ihVVY1u2rAm7XgydRKQq92YfQkFWanpEP5uQ8xmM,2512
|
|
37
37
|
semantic_release/data/templates/angular/md/CHANGELOG.md.j2,sha256=FZmrQ-qOIoSoJmAa_NFaRelfmqUpypU2xlDeScdGOf4,729
|
|
38
38
|
semantic_release/data/templates/angular/md/.components/changelog_header.md.j2,sha256=qNxTuSr59CV_yyimVU_RYp5azCnK0l6nJ03Zf0u5Ugg,166
|
|
39
39
|
semantic_release/data/templates/angular/md/.components/changelog_init.md.j2,sha256=MyEQdWUemGXKWDhvpTmubZdozd3iaLECZAI1VRlE7mg,862
|
|
40
40
|
semantic_release/data/templates/angular/md/.components/changelog_update.md.j2,sha256=uVF4wbbjTMvl6Kbsq9xy3YIrj-uhBnHylEfA7S76aHI,2606
|
|
41
|
-
semantic_release/data/templates/angular/md/.components/changes.md.j2,sha256=
|
|
42
|
-
semantic_release/data/templates/angular/md/.components/first_release.md.j2,sha256
|
|
43
|
-
semantic_release/data/templates/angular/md/.components/macros.md.j2,sha256=
|
|
41
|
+
semantic_release/data/templates/angular/md/.components/changes.md.j2,sha256=NIdXNNao4Oq9nWHaq6X4ZbGMMJ2H-7yxqAP5HmBmSV8,5965
|
|
42
|
+
semantic_release/data/templates/angular/md/.components/first_release.md.j2,sha256=-S2aJV3ka4YicLs8UF6BEV-CDnL8iXLcNRRIiENFrYE,421
|
|
43
|
+
semantic_release/data/templates/angular/md/.components/macros.md.j2,sha256=RzBVwKSCb-WmRIMFVQL9U157mAih-PzZ_iRcfyWkG20,8196
|
|
44
44
|
semantic_release/data/templates/angular/md/.components/unreleased_changes.md.j2,sha256=HRLj6cyRfPZXC0s-0Av6s0Gp3jKxWg9AIEtIXBVqJuY,177
|
|
45
|
-
semantic_release/data/templates/angular/md/.components/versioned_changes.md.j2,sha256=
|
|
45
|
+
semantic_release/data/templates/angular/md/.components/versioned_changes.md.j2,sha256=KbU7D_n_VqET7q2CLwBQ7931dvO7xQklADw-oY6ujlY,502
|
|
46
46
|
semantic_release/data/templates/angular/rst/CHANGELOG.rst.j2,sha256=VmkXEMHiPBdZ1Z47QMxnJBZA0NbFSbKenUbThQVFAGY,731
|
|
47
47
|
semantic_release/data/templates/angular/rst/.components/changelog_header.rst.j2,sha256=c9xN1SEYLFwMvPYXYKt-ZbYPn2-Ss0V7zepEtFFj3Os,200
|
|
48
48
|
semantic_release/data/templates/angular/rst/.components/changelog_init.rst.j2,sha256=XD0l3eTyz1yydLKsmSqBk-u8RnO-RdQ2Q8uWezHMAWw,866
|
|
49
49
|
semantic_release/data/templates/angular/rst/.components/changelog_update.rst.j2,sha256=x23-qk9owJrOQaHx8SgSnIZECITjPf1R2awfv9EOHN0,2604
|
|
50
|
-
semantic_release/data/templates/angular/rst/.components/changes.rst.j2,sha256=
|
|
51
|
-
semantic_release/data/templates/angular/rst/.components/first_release.rst.j2,sha256=
|
|
52
|
-
semantic_release/data/templates/angular/rst/.components/macros.rst.j2,sha256=
|
|
50
|
+
semantic_release/data/templates/angular/rst/.components/changes.rst.j2,sha256=IZdUJ5tt-8P5AyPgS1hsTuu2vg10GDINJ9wLJ4CpP68,7352
|
|
51
|
+
semantic_release/data/templates/angular/rst/.components/first_release.rst.j2,sha256=huaO-B3BRs7h2LRks2a6-656W2qzURkzLiyuKvYxMTg,462
|
|
52
|
+
semantic_release/data/templates/angular/rst/.components/macros.rst.j2,sha256=WLNUD2H2V-5vrwT7TKelwQ2wclLcZxFs0E2Lk3Ld10U,9096
|
|
53
53
|
semantic_release/data/templates/angular/rst/.components/unreleased_changes.rst.j2,sha256=ARBhc1ZpKwehGKDvOMqukmN59mTJiHzHsS7rOfKYCt8,202
|
|
54
|
-
semantic_release/data/templates/angular/rst/.components/versioned_changes.rst.j2,sha256=
|
|
54
|
+
semantic_release/data/templates/angular/rst/.components/versioned_changes.rst.j2,sha256=5THfdfYRjHNEQZVUDt112124hFEZxu7J4qIdgn54O1Q,553
|
|
55
55
|
semantic_release/hvcs/__init__.py,sha256=JwoaLOF-12L-OBo_9-tOXXhdiHKeVungA9865to2oZk,494
|
|
56
56
|
semantic_release/hvcs/_base.py,sha256=ycHg0WljEVTqjFXoGRrHMvbyqWqPs4HziB090IcPZSE,2664
|
|
57
|
-
semantic_release/hvcs/bitbucket.py,sha256=
|
|
58
|
-
semantic_release/hvcs/gitea.py,sha256=
|
|
59
|
-
semantic_release/hvcs/github.py,sha256=
|
|
60
|
-
semantic_release/hvcs/gitlab.py,sha256=
|
|
57
|
+
semantic_release/hvcs/bitbucket.py,sha256=5Z6q_ySwLZ-repf5EH0e8VVvk4fkWvdKCp3sIw4cBBs,10162
|
|
58
|
+
semantic_release/hvcs/gitea.py,sha256=wETF7JDR2xmcyR355faVx7GAtee35gdx7u27Si0prz8,13423
|
|
59
|
+
semantic_release/hvcs/github.py,sha256=nks7cEyp9usw8e-aZ6V2SXsDHu6nPk5haM92LkQ8UBk,20649
|
|
60
|
+
semantic_release/hvcs/gitlab.py,sha256=bKRIzzdAcFmXT_PixadSbpKn6icfLu8KQ7HIHc_uKkU,10623
|
|
61
61
|
semantic_release/hvcs/remote_hvcs_base.py,sha256=cV8qYHtP47bmfIZqV4K2EiMHskFEoIo6agW_iU6wfVE,6105
|
|
62
62
|
semantic_release/hvcs/token_auth.py,sha256=ZjT56-NIPB4OKIt1qwHCu1TavXnrWFIBl9ARlg56hgU,663
|
|
63
63
|
semantic_release/hvcs/util.py,sha256=guxisysY_IW5tv7aaV-iVPEVJzgbOs375kiRRpSquTI,2879
|
|
64
64
|
semantic_release/version/__init__.py,sha256=CLhtGQry9dLIij5XyRa9ZevxU_1p8tjMTSQ-K_GMpWM,270
|
|
65
|
-
semantic_release/version/algorithm.py,sha256
|
|
65
|
+
semantic_release/version/algorithm.py,sha256=s5lso4Py-PiVzuPhgeJOz6IDzIdqc6EeoUirmBZ8IXY,16696
|
|
66
66
|
semantic_release/version/declaration.py,sha256=f6Ld7hIhrqvDrRBapJHr-KDimuyo-4IG8009Zu9BIgU,7357
|
|
67
67
|
semantic_release/version/translator.py,sha256=P1noIsVBn8u6zNOFjG0xKYOWapxqf_PHSMvMeLJ9kXg,3050
|
|
68
68
|
semantic_release/version/version.py,sha256=6PCtSbLP88U1daoxnCwHc--YguZo4waGNLqJ5JfeczE,14175
|
|
69
|
-
python_semantic_release-9.
|
|
70
|
-
python_semantic_release-9.
|
|
71
|
-
python_semantic_release-9.
|
|
72
|
-
python_semantic_release-9.
|
|
73
|
-
python_semantic_release-9.
|
|
74
|
-
python_semantic_release-9.
|
|
75
|
-
python_semantic_release-9.
|
|
69
|
+
python_semantic_release-9.18.0.dist-info/AUTHORS.rst,sha256=XOReVvpymEFUPsS2QPH97jlfJBVrxwS2eu8-jVAe4gk,230
|
|
70
|
+
python_semantic_release-9.18.0.dist-info/LICENSE,sha256=NE85nszX252sdQdu0xgS9qwfYES0k8qS6gW3uO4jRGE,1083
|
|
71
|
+
python_semantic_release-9.18.0.dist-info/METADATA,sha256=G6MEURvvUmQP39A6ksc-bSvIS4XeKxbP3iFNDF9gytE,3812
|
|
72
|
+
python_semantic_release-9.18.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
73
|
+
python_semantic_release-9.18.0.dist-info/entry_points.txt,sha256=r2Jql3GTQyugQnvf34l2eXk1O_Qx6llR_xixG1ZWgD0,105
|
|
74
|
+
python_semantic_release-9.18.0.dist-info/top_level.txt,sha256=qYA24nyg3eP-ti5UW7Vuj2aXVmM0wqVHx4mREdRZNAA,17
|
|
75
|
+
python_semantic_release-9.18.0.dist-info/RECORD,,
|
semantic_release/__init__.py
CHANGED
|
@@ -4,10 +4,13 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from enum import Enum
|
|
7
|
-
from pathlib import Path
|
|
7
|
+
from pathlib import Path, PurePosixPath
|
|
8
8
|
from re import compile as regexp
|
|
9
9
|
from typing import TYPE_CHECKING, Any, Callable, Literal
|
|
10
10
|
|
|
11
|
+
from urllib3.util import Url
|
|
12
|
+
|
|
13
|
+
from semantic_release.const import PYPI_WEB_DOMAIN
|
|
11
14
|
from semantic_release.helpers import sort_numerically
|
|
12
15
|
|
|
13
16
|
if TYPE_CHECKING: # pragma: no cover
|
|
@@ -26,6 +29,7 @@ class ReleaseNotesContext:
|
|
|
26
29
|
version: Version
|
|
27
30
|
release: Release
|
|
28
31
|
mask_initial_release: bool
|
|
32
|
+
license_name: str
|
|
29
33
|
filters: tuple[Callable[..., Any], ...] = ()
|
|
30
34
|
|
|
31
35
|
def bind_to_environment(self, env: Environment) -> Environment:
|
|
@@ -86,6 +90,7 @@ def make_changelog_context(
|
|
|
86
90
|
hvcs_type=hvcs_client.__class__.__name__.lower(),
|
|
87
91
|
filters=(
|
|
88
92
|
*hvcs_client.get_changelog_context_filters(),
|
|
93
|
+
create_pypi_url,
|
|
89
94
|
read_file,
|
|
90
95
|
convert_md_to_rst,
|
|
91
96
|
autofit_text_width,
|
|
@@ -94,6 +99,17 @@ def make_changelog_context(
|
|
|
94
99
|
)
|
|
95
100
|
|
|
96
101
|
|
|
102
|
+
def create_pypi_url(package_name: str, version: str = "") -> str:
|
|
103
|
+
project_name = package_name.strip("/").strip()
|
|
104
|
+
if not project_name:
|
|
105
|
+
raise ValueError("package_name must not be empty!")
|
|
106
|
+
return Url(
|
|
107
|
+
scheme="https",
|
|
108
|
+
host=PYPI_WEB_DOMAIN,
|
|
109
|
+
path=str(PurePosixPath("project", project_name, version.strip("/").strip())),
|
|
110
|
+
).url.rstrip("/")
|
|
111
|
+
|
|
112
|
+
|
|
97
113
|
def read_file(filepath: str) -> str:
|
|
98
114
|
try:
|
|
99
115
|
if not filepath:
|
|
@@ -9,7 +9,9 @@ from git.objects.tag import TagObject
|
|
|
9
9
|
|
|
10
10
|
from semantic_release.commit_parser import ParseError
|
|
11
11
|
from semantic_release.commit_parser.token import ParsedCommit
|
|
12
|
+
from semantic_release.commit_parser.util import force_str
|
|
12
13
|
from semantic_release.enums import LevelBump
|
|
14
|
+
from semantic_release.helpers import validate_types_in_sequence
|
|
13
15
|
from semantic_release.version.algorithm import tags_and_versions
|
|
14
16
|
|
|
15
17
|
if TYPE_CHECKING: # pragma: no cover
|
|
@@ -50,6 +52,12 @@ class ReleaseHistory:
|
|
|
50
52
|
for tag, version in all_git_tags_and_versions
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
ignore_merge_commits = bool(
|
|
56
|
+
hasattr(commit_parser, "options")
|
|
57
|
+
and hasattr(commit_parser.options, "ignore_merge_commits")
|
|
58
|
+
and getattr(commit_parser.options, "ignore_merge_commits") # noqa: B009
|
|
59
|
+
)
|
|
60
|
+
|
|
53
61
|
# Strategy:
|
|
54
62
|
# Loop through commits in history, parsing as we go.
|
|
55
63
|
# Add these commits to `unreleased` as a key-value mapping
|
|
@@ -110,14 +118,36 @@ class ReleaseHistory:
|
|
|
110
118
|
# returns a ParseResult or list of ParseResult objects,
|
|
111
119
|
# it is usually one, but we split a commit if a squashed merge is detected
|
|
112
120
|
parse_results = commit_parser.parse(commit)
|
|
113
|
-
if not isinstance(parse_results, list):
|
|
114
|
-
parse_results = [parse_results]
|
|
115
121
|
|
|
116
|
-
|
|
122
|
+
if not any(
|
|
123
|
+
(
|
|
124
|
+
isinstance(parse_results, (ParseError, ParsedCommit)),
|
|
125
|
+
(
|
|
126
|
+
(
|
|
127
|
+
isinstance(parse_results, list)
|
|
128
|
+
or type(parse_results) == tuple
|
|
129
|
+
)
|
|
130
|
+
and validate_types_in_sequence(
|
|
131
|
+
parse_results, (ParseError, ParsedCommit)
|
|
132
|
+
)
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
):
|
|
136
|
+
raise TypeError("Unexpected type returned from commit_parser.parse")
|
|
137
|
+
|
|
138
|
+
results: list[ParseResult] = [
|
|
139
|
+
*(
|
|
140
|
+
[parse_results]
|
|
141
|
+
if isinstance(parse_results, (ParseError, ParsedCommit))
|
|
142
|
+
else parse_results
|
|
143
|
+
),
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
is_squash_commit = bool(len(results) > 1)
|
|
117
147
|
|
|
118
148
|
# iterate through parsed commits to add to changelog definition
|
|
119
|
-
for parsed_result in
|
|
120
|
-
commit_message =
|
|
149
|
+
for parsed_result in results:
|
|
150
|
+
commit_message = force_str(parsed_result.commit.message)
|
|
121
151
|
commit_type = (
|
|
122
152
|
"unknown"
|
|
123
153
|
if isinstance(parsed_result, ParseError)
|
|
@@ -135,6 +165,10 @@ class ReleaseHistory:
|
|
|
135
165
|
else parsed_result.bump
|
|
136
166
|
)
|
|
137
167
|
|
|
168
|
+
if ignore_merge_commits and parsed_result.is_merge_commit():
|
|
169
|
+
log.info("Excluding merge commit[%s]", parsed_result.short_hash)
|
|
170
|
+
continue
|
|
171
|
+
|
|
138
172
|
# Skip excluded commits except for any commit causing a version bump
|
|
139
173
|
# Reasoning: if a commit causes a version bump, and no other commits
|
|
140
174
|
# are included, then the changelog will be empty. Even if ther was other
|
|
@@ -13,6 +13,7 @@ import semantic_release
|
|
|
13
13
|
from semantic_release.changelog.context import (
|
|
14
14
|
ReleaseNotesContext,
|
|
15
15
|
autofit_text_width,
|
|
16
|
+
create_pypi_url,
|
|
16
17
|
make_changelog_context,
|
|
17
18
|
)
|
|
18
19
|
from semantic_release.changelog.template import environment, recursive_render
|
|
@@ -229,6 +230,7 @@ def generate_release_notes(
|
|
|
229
230
|
history: ReleaseHistory,
|
|
230
231
|
style: str,
|
|
231
232
|
mask_initial_release: bool,
|
|
233
|
+
license_name: str = "",
|
|
232
234
|
) -> str:
|
|
233
235
|
users_tpl_file = template_dir / DEFAULT_RELEASE_NOTES_TPL_FILE
|
|
234
236
|
|
|
@@ -255,8 +257,10 @@ def generate_release_notes(
|
|
|
255
257
|
version=release["version"],
|
|
256
258
|
release=release,
|
|
257
259
|
mask_initial_release=mask_initial_release,
|
|
260
|
+
license_name=license_name,
|
|
258
261
|
filters=(
|
|
259
262
|
*hvcs_client.get_changelog_context_filters(),
|
|
263
|
+
create_pypi_url,
|
|
260
264
|
autofit_text_width,
|
|
261
265
|
sort_numerically,
|
|
262
266
|
),
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
from contextlib import suppress
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
from typing import TYPE_CHECKING
|
|
5
7
|
|
|
6
8
|
import click
|
|
7
|
-
|
|
9
|
+
import tomlkit
|
|
10
|
+
from git import GitCommandError, Repo
|
|
8
11
|
|
|
9
12
|
from semantic_release.changelog.release_history import ReleaseHistory
|
|
10
13
|
from semantic_release.cli.changelog_writer import (
|
|
@@ -21,6 +24,43 @@ if TYPE_CHECKING: # pragma: no cover
|
|
|
21
24
|
log = logging.getLogger(__name__)
|
|
22
25
|
|
|
23
26
|
|
|
27
|
+
def get_license_name_for_release(tag_name: str, project_root: Path) -> str:
|
|
28
|
+
# Retrieve the license name at the time of the specific release tag
|
|
29
|
+
project_metadata: dict[str, str] = {}
|
|
30
|
+
curr_dir = Path.cwd().resolve()
|
|
31
|
+
allowed_directories = [
|
|
32
|
+
dir_path
|
|
33
|
+
for dir_path in [curr_dir, *curr_dir.parents]
|
|
34
|
+
if str(project_root) in str(dir_path)
|
|
35
|
+
]
|
|
36
|
+
for allowed_dir in allowed_directories:
|
|
37
|
+
proj_toml = allowed_dir.joinpath("pyproject.toml")
|
|
38
|
+
with Repo(project_root) as git_repo, suppress(GitCommandError):
|
|
39
|
+
toml_contents = git_repo.git.show(
|
|
40
|
+
f"{tag_name}:{proj_toml.relative_to(project_root)}"
|
|
41
|
+
)
|
|
42
|
+
config_toml = tomlkit.parse(toml_contents)
|
|
43
|
+
project_metadata = config_toml.unwrap().get("project", project_metadata)
|
|
44
|
+
break
|
|
45
|
+
|
|
46
|
+
license_cfg = project_metadata.get(
|
|
47
|
+
"license-expression",
|
|
48
|
+
project_metadata.get(
|
|
49
|
+
"license",
|
|
50
|
+
"",
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if not isinstance(license_cfg, (str, dict)) or license_cfg is None:
|
|
55
|
+
return ""
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
license_cfg.get("text", "") # type: ignore[attr-defined]
|
|
59
|
+
if isinstance(license_cfg, dict)
|
|
60
|
+
else license_cfg or ""
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
24
64
|
def post_release_notes(
|
|
25
65
|
release_tag: str,
|
|
26
66
|
release_notes: str,
|
|
@@ -120,6 +160,10 @@ def changelog(cli_ctx: CliContextObj, release_tag: str | None) -> None:
|
|
|
120
160
|
release_history,
|
|
121
161
|
style=runtime.changelog_style,
|
|
122
162
|
mask_initial_release=runtime.changelog_mask_initial_release,
|
|
163
|
+
license_name=get_license_name_for_release(
|
|
164
|
+
tag_name=release_tag,
|
|
165
|
+
project_root=runtime.repo_dir,
|
|
166
|
+
),
|
|
123
167
|
)
|
|
124
168
|
|
|
125
169
|
try:
|
|
@@ -699,13 +699,31 @@ def version( # noqa: C901
|
|
|
699
699
|
log.info("Remote does not support releases. Skipping release creation...")
|
|
700
700
|
return
|
|
701
701
|
|
|
702
|
+
license_cfg = runtime.project_metadata.get(
|
|
703
|
+
"license-expression",
|
|
704
|
+
runtime.project_metadata.get(
|
|
705
|
+
"license",
|
|
706
|
+
"",
|
|
707
|
+
),
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
if not isinstance(license_cfg, (str, dict)) or license_cfg is None:
|
|
711
|
+
license_cfg = ""
|
|
712
|
+
|
|
713
|
+
license_name = (
|
|
714
|
+
license_cfg.get("text", "")
|
|
715
|
+
if isinstance(license_cfg, dict)
|
|
716
|
+
else license_cfg or ""
|
|
717
|
+
)
|
|
718
|
+
|
|
702
719
|
release_notes = generate_release_notes(
|
|
703
720
|
hvcs_client,
|
|
704
|
-
release_history.released[new_version],
|
|
705
|
-
runtime.template_dir,
|
|
721
|
+
release=release_history.released[new_version],
|
|
722
|
+
template_dir=runtime.template_dir,
|
|
706
723
|
history=release_history,
|
|
707
724
|
style=runtime.changelog_style,
|
|
708
725
|
mask_initial_release=runtime.changelog_mask_initial_release,
|
|
726
|
+
license_name=license_name,
|
|
709
727
|
)
|
|
710
728
|
|
|
711
729
|
exception: Exception | None = None
|
semantic_release/cli/config.py
CHANGED
|
@@ -15,6 +15,8 @@ from re import (
|
|
|
15
15
|
)
|
|
16
16
|
from typing import Any, ClassVar, Dict, List, Literal, Optional, Tuple, Type, Union
|
|
17
17
|
|
|
18
|
+
# typing_extensions is for Python 3.8, 3.9, 3.10 compatibility
|
|
19
|
+
import tomlkit
|
|
18
20
|
from git import Actor, InvalidGitRepositoryError
|
|
19
21
|
from git.repo.base import Repo
|
|
20
22
|
from jinja2 import Environment
|
|
@@ -26,8 +28,6 @@ from pydantic import (
|
|
|
26
28
|
field_validator,
|
|
27
29
|
model_validator,
|
|
28
30
|
)
|
|
29
|
-
|
|
30
|
-
# typing_extensions is for Python 3.8, 3.9, 3.10 compatibility
|
|
31
31
|
from typing_extensions import Annotated, Self
|
|
32
32
|
from urllib3.util.url import parse_url
|
|
33
33
|
|
|
@@ -523,6 +523,7 @@ def _recursive_getattr(obj: Any, path: str) -> Any:
|
|
|
523
523
|
class RuntimeContext:
|
|
524
524
|
_mask_attrs_: ClassVar[List[str]] = ["hvcs_client.token"]
|
|
525
525
|
|
|
526
|
+
project_metadata: dict[str, Any]
|
|
526
527
|
repo_dir: Path
|
|
527
528
|
commit_parser: CommitParser[ParseResult, ParserOptions]
|
|
528
529
|
version_translator: VersionTranslator
|
|
@@ -599,6 +600,21 @@ class RuntimeContext:
|
|
|
599
600
|
# credentials masking for logging
|
|
600
601
|
masker = MaskingFilter(_use_named_masks=raw.logging_use_named_masks)
|
|
601
602
|
|
|
603
|
+
# TODO: move to config if we change how the generated config is constructed
|
|
604
|
+
# Retrieve project metadata from pyproject.toml
|
|
605
|
+
project_metadata: dict[str, str] = {}
|
|
606
|
+
curr_dir = Path.cwd().resolve()
|
|
607
|
+
allowed_directories = [
|
|
608
|
+
dir_path
|
|
609
|
+
for dir_path in [curr_dir, *curr_dir.parents]
|
|
610
|
+
if str(raw.repo_dir) in str(dir_path)
|
|
611
|
+
]
|
|
612
|
+
for allowed_dir in allowed_directories:
|
|
613
|
+
if (proj_toml := allowed_dir.joinpath("pyproject.toml")).exists():
|
|
614
|
+
config_toml = tomlkit.parse(proj_toml.read_text())
|
|
615
|
+
project_metadata = config_toml.unwrap().get("project", project_metadata)
|
|
616
|
+
break
|
|
617
|
+
|
|
602
618
|
# Retrieve details from repository
|
|
603
619
|
with Repo(str(raw.repo_dir)) as git_repo:
|
|
604
620
|
try:
|
|
@@ -825,6 +841,7 @@ class RuntimeContext:
|
|
|
825
841
|
# )
|
|
826
842
|
|
|
827
843
|
self = cls(
|
|
844
|
+
project_metadata=project_metadata,
|
|
828
845
|
repo_dir=raw.repo_dir,
|
|
829
846
|
commit_parser=commit_parser,
|
|
830
847
|
version_translator=version_translator,
|
|
@@ -101,6 +101,10 @@ class AngularParserOptions(ParserOptions):
|
|
|
101
101
|
parse_squash_commits: bool = False
|
|
102
102
|
"""Toggle flag for whether or not to parse squash commits"""
|
|
103
103
|
|
|
104
|
+
# TODO: breaking change v10, change default to True
|
|
105
|
+
ignore_merge_commits: bool = False
|
|
106
|
+
"""Toggle flag for whether or not to ignore merge commits"""
|
|
107
|
+
|
|
104
108
|
@property
|
|
105
109
|
def tag_to_level(self) -> dict[str, LevelBump]:
|
|
106
110
|
"""A mapping of commit tags to the level bump they should result in."""
|
|
@@ -188,6 +192,7 @@ class AngularCommitParser(CommitParser[ParseResult, AngularParserOptions]):
|
|
|
188
192
|
),
|
|
189
193
|
flags=re.MULTILINE | re.IGNORECASE,
|
|
190
194
|
)
|
|
195
|
+
self.notice_selector = regexp(r"^NOTICE: (?P<notice>.+)$")
|
|
191
196
|
self.filters = {
|
|
192
197
|
"typo-extra-spaces": (regexp(r"(\S) +(\S)"), r"\1 \2"),
|
|
193
198
|
"git-header-commit": (
|
|
@@ -232,11 +237,18 @@ class AngularCommitParser(CommitParser[ParseResult, AngularParserOptions]):
|
|
|
232
237
|
def commit_body_components_separator(
|
|
233
238
|
self, accumulator: dict[str, list[str]], text: str
|
|
234
239
|
) -> dict[str, list[str]]:
|
|
235
|
-
if match := breaking_re.match(text):
|
|
236
|
-
accumulator["breaking_descriptions"].append(
|
|
240
|
+
if (match := breaking_re.match(text)) and (brk_desc := match.group(1)):
|
|
241
|
+
accumulator["breaking_descriptions"].append(brk_desc)
|
|
237
242
|
# TODO: breaking change v10, removes breaking change footers from descriptions
|
|
238
243
|
# return accumulator
|
|
239
244
|
|
|
245
|
+
elif (match := self.notice_selector.match(text)) and (
|
|
246
|
+
notice := match.group("notice")
|
|
247
|
+
):
|
|
248
|
+
accumulator["notices"].append(notice)
|
|
249
|
+
# TODO: breaking change v10, removes notice footers from descriptions
|
|
250
|
+
# return accumulator
|
|
251
|
+
|
|
240
252
|
elif match := self.issue_selector.search(text):
|
|
241
253
|
# if match := self.issue_selector.search(text):
|
|
242
254
|
predicate = regexp(r",? and | *[,;/& ] *").sub(
|
|
@@ -252,11 +264,12 @@ class AngularCommitParser(CommitParser[ParseResult, AngularParserOptions]):
|
|
|
252
264
|
predicate.split(","),
|
|
253
265
|
)
|
|
254
266
|
)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
267
|
+
if new_issue_refs:
|
|
268
|
+
accumulator["linked_issues"] = sort_numerically(
|
|
269
|
+
set(accumulator["linked_issues"]).union(new_issue_refs)
|
|
270
|
+
)
|
|
271
|
+
# TODO: breaking change v10, removes resolution footers from descriptions
|
|
272
|
+
# return accumulator
|
|
260
273
|
|
|
261
274
|
# Prevent appending duplicate descriptions
|
|
262
275
|
if text not in accumulator["descriptions"]:
|
|
@@ -291,6 +304,7 @@ class AngularCommitParser(CommitParser[ParseResult, AngularParserOptions]):
|
|
|
291
304
|
{
|
|
292
305
|
"breaking_descriptions": [],
|
|
293
306
|
"descriptions": [],
|
|
307
|
+
"notices": [],
|
|
294
308
|
"linked_issues": [],
|
|
295
309
|
},
|
|
296
310
|
)
|
|
@@ -311,10 +325,15 @@ class AngularCommitParser(CommitParser[ParseResult, AngularParserOptions]):
|
|
|
311
325
|
scope=parsed_scope,
|
|
312
326
|
descriptions=tuple(body_components["descriptions"]),
|
|
313
327
|
breaking_descriptions=tuple(body_components["breaking_descriptions"]),
|
|
328
|
+
release_notices=tuple(body_components["notices"]),
|
|
314
329
|
linked_issues=tuple(body_components["linked_issues"]),
|
|
315
330
|
linked_merge_request=linked_merge_request,
|
|
316
331
|
)
|
|
317
332
|
|
|
333
|
+
@staticmethod
|
|
334
|
+
def is_merge_commit(commit: Commit) -> bool:
|
|
335
|
+
return len(commit.parents) > 1
|
|
336
|
+
|
|
318
337
|
def parse_commit(self, commit: Commit) -> ParseResult:
|
|
319
338
|
if not (parsed_msg_result := self.parse_message(force_str(commit.message))):
|
|
320
339
|
return _logged_parse_error(
|
|
@@ -336,6 +355,11 @@ class AngularCommitParser(CommitParser[ParseResult, AngularParserOptions]):
|
|
|
336
355
|
multiple commits, each of which will be parsed separately. Single commits
|
|
337
356
|
will be returned as a list of a single ParseResult.
|
|
338
357
|
"""
|
|
358
|
+
if self.options.ignore_merge_commits and self.is_merge_commit(commit):
|
|
359
|
+
return _logged_parse_error(
|
|
360
|
+
commit, "Ignoring merge commit: %s" % commit.hexsha[:8]
|
|
361
|
+
)
|
|
362
|
+
|
|
339
363
|
separate_commits: list[Commit] = (
|
|
340
364
|
self.unsquash_commit(commit)
|
|
341
365
|
if self.options.parse_squash_commits
|
|
@@ -442,7 +466,7 @@ class AngularCommitParser(CommitParser[ParseResult, AngularParserOptions]):
|
|
|
442
466
|
[],
|
|
443
467
|
)
|
|
444
468
|
|
|
445
|
-
return separate_commit_msgs
|
|
469
|
+
return list(filter(None, separate_commit_msgs))
|
|
446
470
|
|
|
447
471
|
def _find_squashed_commits_in_str(self, message: str) -> list[str]:
|
|
448
472
|
separate_commit_msgs: list[str] = []
|
|
@@ -477,8 +501,4 @@ class AngularCommitParser(CommitParser[ParseResult, AngularParserOptions]):
|
|
|
477
501
|
|
|
478
502
|
current_msg = clean_paragraph
|
|
479
503
|
|
|
480
|
-
|
|
481
|
-
if current_msg:
|
|
482
|
-
separate_commit_msgs.append(current_msg)
|
|
483
|
-
|
|
484
|
-
return separate_commit_msgs
|
|
504
|
+
return [*separate_commit_msgs, current_msg]
|