python-semantic-release 9.16.1__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.
Files changed (40) hide show
  1. {python_semantic_release-9.16.1.dist-info → python_semantic_release-9.18.0.dist-info}/METADATA +1 -1
  2. {python_semantic_release-9.16.1.dist-info → python_semantic_release-9.18.0.dist-info}/RECORD +40 -40
  3. semantic_release/__init__.py +1 -1
  4. semantic_release/changelog/context.py +20 -1
  5. semantic_release/changelog/release_history.py +100 -56
  6. semantic_release/changelog/template.py +0 -1
  7. semantic_release/cli/changelog_writer.py +10 -1
  8. semantic_release/cli/commands/changelog.py +45 -1
  9. semantic_release/cli/commands/version.py +21 -3
  10. semantic_release/cli/config.py +25 -3
  11. semantic_release/cli/masking_filter.py +1 -1
  12. semantic_release/commit_parser/_base.py +1 -1
  13. semantic_release/commit_parser/angular.py +240 -21
  14. semantic_release/commit_parser/emoji.py +239 -29
  15. semantic_release/commit_parser/scipy.py +7 -7
  16. semantic_release/commit_parser/tag.py +3 -1
  17. semantic_release/commit_parser/token.py +19 -6
  18. semantic_release/commit_parser/util.py +60 -8
  19. semantic_release/const.py +2 -0
  20. semantic_release/data/templates/angular/md/.components/changes.md.j2 +59 -14
  21. semantic_release/data/templates/angular/md/.components/first_release.md.j2 +11 -4
  22. semantic_release/data/templates/angular/md/.components/macros.md.j2 +66 -0
  23. semantic_release/data/templates/angular/md/.components/versioned_changes.md.j2 +13 -7
  24. semantic_release/data/templates/angular/md/.release_notes.md.j2 +25 -21
  25. semantic_release/data/templates/angular/rst/.components/changes.rst.j2 +68 -23
  26. semantic_release/data/templates/angular/rst/.components/first_release.rst.j2 +2 -0
  27. semantic_release/data/templates/angular/rst/.components/macros.rst.j2 +66 -0
  28. semantic_release/data/templates/angular/rst/.components/versioned_changes.rst.j2 +2 -0
  29. semantic_release/helpers.py +90 -2
  30. semantic_release/hvcs/_base.py +1 -1
  31. semantic_release/hvcs/bitbucket.py +15 -0
  32. semantic_release/hvcs/gitea.py +21 -0
  33. semantic_release/hvcs/github.py +21 -0
  34. semantic_release/hvcs/gitlab.py +20 -0
  35. semantic_release/version/algorithm.py +48 -13
  36. {python_semantic_release-9.16.1.dist-info → python_semantic_release-9.18.0.dist-info}/AUTHORS.rst +0 -0
  37. {python_semantic_release-9.16.1.dist-info → python_semantic_release-9.18.0.dist-info}/LICENSE +0 -0
  38. {python_semantic_release-9.16.1.dist-info → python_semantic_release-9.18.0.dist-info}/WHEEL +0 -0
  39. {python_semantic_release-9.16.1.dist-info → python_semantic_release-9.18.0.dist-info}/entry_points.txt +0 -0
  40. {python_semantic_release-9.16.1.dist-info → python_semantic_release-9.18.0.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.16.1
3
+ Version: 9.18.0
4
4
  Summary: Automatic Semantic Versioning for Python projects
5
5
  Author-email: Rolf Erik Lekang <me@rolflekang.com>
6
6
  License: MIT
@@ -1,75 +1,75 @@
1
- semantic_release/__init__.py,sha256=Uuw6q8uKl2tHg0H9Nmlkb_MgTX6uXnS7ttKTo4pyNc0,1229
1
+ semantic_release/__init__.py,sha256=-F25Nu3RzbeFLRgFP4ms_VHphEUTK1SdYz0y3LLOJB8,1229
2
2
  semantic_release/__main__.py,sha256=KOIBOvLruqfi5ArXcWK3ucIZ7NB55kfCbycJaxx6aQg,1485
3
- semantic_release/const.py,sha256=Z1o2QNh60wSLeF-_1TemMBjU3ZXbV0XghnUFsbTVfOs,831
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=nNm4Pof2nr6D6lixs9CWnwN4bEXCDGgctsT2fNE79hA,7066
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=jyVluJq8Vu6TyyzQQrsBIQRKm7kEnh1GZt8ObwibR5k,5374
12
- semantic_release/changelog/release_history.py,sha256=nbd-WYVfQSWN4SKPPWEGU6QgNZLBrNxKKxfhPGKqnKc,8499
13
- semantic_release/changelog/template.py,sha256=O4EKXVJtN1z6FowcRUiZdZmi9u_TsTiXcHmYJnGyt94,5721
11
+ semantic_release/changelog/context.py,sha256=WeLQ2BvYEWunIF8XEl6ldQE4IRg0d7r8mYSBi-TqK7o,5988
12
+ semantic_release/changelog/release_history.py,sha256=epxN_tFGk3H3OGjiMsvwEVp5wgrTIKn2WRmjK5ZJ6SM,10578
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=RupwYqApOeAMidIvjCttnyyGNRxtLftmxDBmEu5azX0,9049
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=TnvHDaqMbNp2hWeRyi-o3UbZ1_mH766RL76N_ca2Puc,32073
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
- semantic_release/cli/masking_filter.py,sha256=DxqjiJyABlzwwwZ1r8JGQpb6QrF00StJFm0-2-s5Fv0,3071
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=w_sXuFJHIqcueBQdeNeWn6fqbLVPPl_c-dEhv3Pb_BA,3870
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=ztNoAUVFjXE2bjrrKXB1EUIOpwRPpnfR_zYdtnp_uQQ,24813
27
+ semantic_release/cli/commands/version.py,sha256=BMAOXToqOC_U0nh3d-QszAOS7xhN86aGhFiaePSdfws,25287
28
28
  semantic_release/commit_parser/__init__.py,sha256=cv5HFBdw7OJd4Laj4Ex8ZZ5Tml8GwXgQcXW6Pasr2Ao,615
29
- semantic_release/commit_parser/_base.py,sha256=LAscBtS3_28jebRCeR-eGo3UtAsuxCWBzgb7FF4n4Vo,3046
30
- semantic_release/commit_parser/angular.py,sha256=zDYYOK1itsYJ0Ar7-cf29MnfrEpbQTeQCAcExWPH1fM,10486
31
- semantic_release/commit_parser/emoji.py,sha256=6HtvKvJZwPAvY2fNOXU_gpKf1rGrMiDd0rYamyrdQZ8,10002
32
- semantic_release/commit_parser/scipy.py,sha256=Fm_6WUaliLmqD397uVXwpOSDZ7LpMFu59oz-inKeHko,4526
33
- semantic_release/commit_parser/tag.py,sha256=4uwIKBqUM2SE6UTGIw-a7B6Jg1OONXmGwXsTyL3yZBA,3490
34
- semantic_release/commit_parser/token.py,sha256=RXdoCmKMTKMmUJGEUvfCiAOCPRrV0WubXojwa6FbvFg,7363
35
- semantic_release/commit_parser/util.py,sha256=KmJ-M-CJbc7q37GG7o595SmeBwrRrrnjP4q1C05jGYI,2463
36
- semantic_release/data/templates/angular/md/.release_notes.md.j2,sha256=BzpatS7WYBcpHii8qiDemyI1ygXM6Q1nbxdcdcps__U,2107
29
+ semantic_release/commit_parser/_base.py,sha256=oDifeTmFDpS238cp_DDrGzfidaKeAD5olCB5IM4Q6z8,3058
30
+ semantic_release/commit_parser/angular.py,sha256=UM88ethWT34__kDgsBXXb26d0V8FN1ZFjcU7psuOhgY,19259
31
+ semantic_release/commit_parser/emoji.py,sha256=T3kw8jyq1zYME3f6JJ6O3hVJ2IpRk3DD5LB0VOlcbMM,18194
32
+ semantic_release/commit_parser/scipy.py,sha256=0rYZglJ7uib-1Deu4J30wHh7AZS8KfO0eND82bPtDQ8,4526
33
+ semantic_release/commit_parser/tag.py,sha256=oGB3lgyp2Eu3Tg3jjxqNzN86N6bokSaFu6f4Ir6IS_k,3546
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=AnF6sEsdaOeCS6E0_Q1PS430jufC1wg4ZMIJdMuBuNI,4000
42
- semantic_release/data/templates/angular/md/.components/first_release.md.j2,sha256=jMUZiLwhMqAOjdYOCphJxJr8C411cKRzhsabyebj_AY,162
43
- semantic_release/data/templates/angular/md/.components/macros.md.j2,sha256=GEql_lFHWqtfZUMYo8WT3E_YOTf0gvGhjqRQw0c7mlk,5890
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=2Hky2mBrC4jltz3mvaiPDD0KQP0ELe5Ag75HgaLpaIE,257
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=HwuxpKLcRZiMZZafESIfy0zjleAwxrnMV9tRgaF3Mwc,5353
51
- semantic_release/data/templates/angular/rst/.components/first_release.rst.j2,sha256=tChYsdSFMaB0DU9KROYSxijdb7tqaN1psKmDMbrVrJw,443
52
- semantic_release/data/templates/angular/rst/.components/macros.rst.j2,sha256=luNkkvsWeaUx_xQ-D28USFS_Q56kzJcUXiIX2umBK9I,6790
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=NZfn1W14QochiAJ43oNKmcrCn_vgfbkKtvOTAw1jEc8,530
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
- semantic_release/hvcs/_base.py,sha256=OSUpUkxxmhafRqz8y60DdKK8PmudzJ7rEzXgIzo3aXc,2627
57
- semantic_release/hvcs/bitbucket.py,sha256=Hzk27OOcpedFKVX4z3cY8DIUNNxe6D57J0d3sWNPIOY,9687
58
- semantic_release/hvcs/gitea.py,sha256=YTpAnJ4bBdMjIhgAvVMF8vPZ6O07IgWuXeqTbEU-GNE,12715
59
- semantic_release/hvcs/github.py,sha256=PLqbXDpHvXqE-uY8dmv7MuyjjnkMv0Va0vXo3UQUIUA,19937
60
- semantic_release/hvcs/gitlab.py,sha256=6BrhI5k7x5YKKRc9NxvLKEoKq7g3dnpDd2WDe8tuVFs,9964
56
+ semantic_release/hvcs/_base.py,sha256=ycHg0WljEVTqjFXoGRrHMvbyqWqPs4HziB090IcPZSE,2664
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=0cj5LqT8DpBr2zo7Va0-VP8t6a3xAIhFiTLb11xdGpo,15246
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.16.1.dist-info/AUTHORS.rst,sha256=XOReVvpymEFUPsS2QPH97jlfJBVrxwS2eu8-jVAe4gk,230
70
- python_semantic_release-9.16.1.dist-info/LICENSE,sha256=NE85nszX252sdQdu0xgS9qwfYES0k8qS6gW3uO4jRGE,1083
71
- python_semantic_release-9.16.1.dist-info/METADATA,sha256=OocBkzse3jhj4CLVaGPr7y4VwkliK9NZP8xVCluIrxY,3812
72
- python_semantic_release-9.16.1.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
73
- python_semantic_release-9.16.1.dist-info/entry_points.txt,sha256=r2Jql3GTQyugQnvf34l2eXk1O_Qx6llR_xixG1ZWgD0,105
74
- python_semantic_release-9.16.1.dist-info/top_level.txt,sha256=qYA24nyg3eP-ti5UW7Vuj2aXVmM0wqVHx4mREdRZNAA,17
75
- python_semantic_release-9.16.1.dist-info/RECORD,,
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,,
@@ -24,7 +24,7 @@ from semantic_release.version import (
24
24
  tags_and_versions,
25
25
  )
26
26
 
27
- __version__ = "9.16.1"
27
+ __version__ = "9.18.0"
28
28
 
29
29
  __all__ = [
30
30
  "CommitParser",
@@ -4,10 +4,15 @@ 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
14
+ from semantic_release.helpers import sort_numerically
15
+
11
16
  if TYPE_CHECKING: # pragma: no cover
12
17
  from jinja2 import Environment
13
18
 
@@ -24,6 +29,7 @@ class ReleaseNotesContext:
24
29
  version: Version
25
30
  release: Release
26
31
  mask_initial_release: bool
32
+ license_name: str
27
33
  filters: tuple[Callable[..., Any], ...] = ()
28
34
 
29
35
  def bind_to_environment(self, env: Environment) -> Environment:
@@ -84,13 +90,26 @@ def make_changelog_context(
84
90
  hvcs_type=hvcs_client.__class__.__name__.lower(),
85
91
  filters=(
86
92
  *hvcs_client.get_changelog_context_filters(),
93
+ create_pypi_url,
87
94
  read_file,
88
95
  convert_md_to_rst,
89
96
  autofit_text_width,
97
+ sort_numerically,
90
98
  ),
91
99
  )
92
100
 
93
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
+
94
113
  def read_file(filepath: str) -> str:
95
114
  try:
96
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
@@ -102,75 +110,111 @@ class ReleaseHistory:
102
110
 
103
111
  released.setdefault(the_version, release)
104
112
 
105
- # mypy will be happy if we make this an explicit string
106
- commit_message = str(commit.message)
107
-
108
113
  log.info(
109
114
  "parsing commit [%s] %s",
110
115
  commit.hexsha[:8],
111
- commit_message.replace("\n", " ")[:54],
112
- )
113
- parse_result = commit_parser.parse(commit)
114
- commit_type = (
115
- "unknown" if isinstance(parse_result, ParseError) else parse_result.type
116
- )
117
-
118
- has_exclusion_match = any(
119
- pattern.match(commit_message) for pattern in exclude_commit_patterns
120
- )
121
-
122
- commit_level_bump = (
123
- LevelBump.NO_RELEASE
124
- if isinstance(parse_result, ParseError)
125
- else parse_result.bump
116
+ str(commit.message).replace("\n", " ")[:54],
126
117
  )
118
+ # returns a ParseResult or list of ParseResult objects,
119
+ # it is usually one, but we split a commit if a squashed merge is detected
120
+ parse_results = commit_parser.parse(commit)
121
+
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)
147
+
148
+ # iterate through parsed commits to add to changelog definition
149
+ for parsed_result in results:
150
+ commit_message = force_str(parsed_result.commit.message)
151
+ commit_type = (
152
+ "unknown"
153
+ if isinstance(parsed_result, ParseError)
154
+ else parsed_result.type
155
+ )
156
+ log.debug("commit has type '%s'", commit_type)
127
157
 
128
- # Skip excluded commits except for any commit causing a version bump
129
- # Reasoning: if a commit causes a version bump, and no other commits
130
- # are included, then the changelog will be empty. Even if ther was other
131
- # commits included, the true reason for a version bump would be missing.
132
- if has_exclusion_match and commit_level_bump == LevelBump.NO_RELEASE:
133
- log.info(
134
- "Excluding commit [%s] %s",
135
- commit.hexsha[:8],
136
- commit_message.replace("\n", " ")[:50],
158
+ has_exclusion_match = any(
159
+ pattern.match(commit_message) for pattern in exclude_commit_patterns
137
160
  )
138
- continue
139
161
 
140
- if (
141
- isinstance(parse_result, ParsedCommit)
142
- and not parse_result.include_in_changelog
143
- ):
144
- log.info(
145
- str.join(
146
- " ",
147
- [
148
- "Excluding commit %s (%s) because parser determined",
149
- "it should not included in the changelog",
150
- ],
151
- ),
152
- commit.hexsha[:8],
153
- commit_message.replace("\n", " ")[:20],
162
+ commit_level_bump = (
163
+ LevelBump.NO_RELEASE
164
+ if isinstance(parsed_result, ParseError)
165
+ else parsed_result.bump
154
166
  )
155
- continue
156
167
 
157
- if the_version is None:
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
+
172
+ # Skip excluded commits except for any commit causing a version bump
173
+ # Reasoning: if a commit causes a version bump, and no other commits
174
+ # are included, then the changelog will be empty. Even if ther was other
175
+ # commits included, the true reason for a version bump would be missing.
176
+ if has_exclusion_match and commit_level_bump == LevelBump.NO_RELEASE:
177
+ log.info(
178
+ "Excluding %s commit[%s] %s",
179
+ "piece of squashed" if is_squash_commit else "",
180
+ parsed_result.short_hash,
181
+ commit_message.split("\n", maxsplit=1)[0][:20],
182
+ )
183
+ continue
184
+
185
+ if (
186
+ isinstance(parsed_result, ParsedCommit)
187
+ and not parsed_result.include_in_changelog
188
+ ):
189
+ log.info(
190
+ str.join(
191
+ " ",
192
+ [
193
+ "Excluding commit[%s] because parser determined",
194
+ "it should not included in the changelog",
195
+ ],
196
+ ),
197
+ parsed_result.short_hash,
198
+ )
199
+ continue
200
+
201
+ if the_version is None:
202
+ log.info(
203
+ "[Unreleased] adding commit[%s] to unreleased '%s'",
204
+ parsed_result.short_hash,
205
+ commit_type,
206
+ )
207
+ unreleased[commit_type].append(parsed_result)
208
+ continue
209
+
158
210
  log.info(
159
- "[Unreleased] adding '%s' commit(%s) to list",
160
- commit.hexsha[:8],
211
+ "[%s] adding commit[%s] to release '%s'",
212
+ the_version,
213
+ parsed_result.short_hash,
161
214
  commit_type,
162
215
  )
163
- unreleased[commit_type].append(parse_result)
164
- continue
165
-
166
- log.info(
167
- "[%s] adding '%s' commit(%s) to release",
168
- the_version,
169
- commit_type,
170
- commit.hexsha[:8],
171
- )
172
216
 
173
- released[the_version]["elements"][commit_type].append(parse_result)
217
+ released[the_version]["elements"][commit_type].append(parsed_result)
174
218
 
175
219
  return cls(unreleased=unreleased, released=released)
176
220
 
@@ -54,7 +54,6 @@ def environment(
54
54
  autoescape_value = dynamic_import(autoescape)
55
55
  else:
56
56
  autoescape_value = autoescape
57
- log.debug("%s", locals())
58
57
 
59
58
  return ComplexDirectorySandboxedEnvironment(
60
59
  block_start_string=block_start_string,
@@ -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
@@ -24,6 +25,7 @@ from semantic_release.cli.const import (
24
25
  )
25
26
  from semantic_release.cli.util import noop_report
26
27
  from semantic_release.errors import InternalError
28
+ from semantic_release.helpers import sort_numerically
27
29
 
28
30
  if TYPE_CHECKING: # pragma: no cover
29
31
  from jinja2 import Environment
@@ -228,6 +230,7 @@ def generate_release_notes(
228
230
  history: ReleaseHistory,
229
231
  style: str,
230
232
  mask_initial_release: bool,
233
+ license_name: str = "",
231
234
  ) -> str:
232
235
  users_tpl_file = template_dir / DEFAULT_RELEASE_NOTES_TPL_FILE
233
236
 
@@ -254,7 +257,13 @@ def generate_release_notes(
254
257
  version=release["version"],
255
258
  release=release,
256
259
  mask_initial_release=mask_initial_release,
257
- filters=(*hvcs_client.get_changelog_context_filters(), autofit_text_width),
260
+ license_name=license_name,
261
+ filters=(
262
+ *hvcs_client.get_changelog_context_filters(),
263
+ create_pypi_url,
264
+ autofit_text_width,
265
+ sort_numerically,
266
+ ),
258
267
  ).bind_to_environment(
259
268
  # Use a new, non-configurable environment for release notes -
260
269
  # not user-configurable at the moment
@@ -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
- from git import Repo
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:
@@ -65,7 +65,7 @@ def is_forced_prerelease(
65
65
  log.debug(
66
66
  "%s: %s",
67
67
  is_forced_prerelease.__name__,
68
- ", ".join(f"{k} = {v}" for k, v in local_vars),
68
+ str.join(", ", iter(f"{k} = {v}" for k, v in local_vars)),
69
69
  )
70
70
  return (
71
71
  as_prerelease
@@ -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
@@ -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,10 +600,30 @@ 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:
605
- remote_url = raw.remote.url or git_repo.remote(raw.remote.name).url
621
+ # Get the remote url by calling out to `git remote get-url`. This returns
622
+ # the expanded url, taking into account any insteadOf directives
623
+ # in the git configuration.
624
+ remote_url = raw.remote.url or git_repo.git.remote(
625
+ "get-url", raw.remote.name
626
+ )
606
627
  active_branch = git_repo.active_branch.name
607
628
  except ValueError as err:
608
629
  raise MissingGitRemote(
@@ -820,6 +841,7 @@ class RuntimeContext:
820
841
  # )
821
842
 
822
843
  self = cls(
844
+ project_metadata=project_metadata,
823
845
  repo_dir=raw.repo_dir,
824
846
  commit_parser=commit_parser,
825
847
  version_translator=version_translator,
@@ -27,7 +27,7 @@ class MaskingFilter(logging.Filter):
27
27
 
28
28
  def add_mask_for(self, data: str, name: str = "redacted") -> MaskingFilter:
29
29
  if data and data not in self._UNWANTED:
30
- log.debug("Adding redact pattern %r to _redact_patterns", name)
30
+ log.debug("Adding redact pattern '%r' to redact_patterns", name)
31
31
  self._redact_patterns[name].add(data)
32
32
  return self
33
33
 
@@ -81,4 +81,4 @@ class CommitParser(ABC, Generic[_TT, _OPTS]):
81
81
  return self.parser_options() # type: ignore[return-value]
82
82
 
83
83
  @abstractmethod
84
- def parse(self, commit: Commit) -> _TT: ...
84
+ def parse(self, commit: Commit) -> _TT | list[_TT]: ...