Repository: relekang/python-semantic-release
Pull request #281 (2020-12-03 21:17:36)

feat(logs): include scope in changelogs
When the scope is set, include it in changelogs, e.g.,
`"feat(x): some description"` becomes:
`* **x**: some description`

in the changelog. This is similar to how the Node semantic release (and conventional-changelog-generator) generate changelogs. If scope is not given, it's omitted.

Add a new config parameter changelog_scope to disable this behavior when set to `False`

wyardley: Totally open to adjusting test cases, switching the default, or other feedback.

At some point, I'd also like to add a separate change to make PR links clickable in the changelog when present.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d7925eb..df51d2d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 <!--next-version-placeholder-->
 
+## v7.4.1 (2020-12-04)
+### Fix
+* Add "changelog_capitalize" to flags (#279) ([`37716df`](https://github.com/relekang/python-semantic-release/commit/37716dfa78eb3f848f57a5100d01d93f5aafc0bf))
+
 ## v7.4.0 (2020-11-24)
 ### Feature
 * Add changelog_capitalize configuration ([`7cacca1`](https://github.com/relekang/python-semantic-release/commit/7cacca1eb436a7166ba8faf643b53c42bc32a6a7))
diff --git a/docs/configuration.rst b/docs/configuration.rst
index cbb2d9e..d49bcd6 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -297,6 +297,13 @@ will be updated.
 
 Default: ``<!--next-version-placeholder-->``.
 
+``changelog_scope``
+-------------------------
+If set to false, `**scope:**` (when scope is set for a commit) will not be
+prepended to the description when generating the changelog.
+
+Default: ``True``.
+
 ``changelog_capitalize``
 -------------------------
 If set to false commit messages will not be automatically capitalized when generating the changelog.
diff --git a/semantic_release/__init__.py b/semantic_release/__init__.py
index 395c82b..88a5dcd 100644
--- a/semantic_release/__init__.py
+++ b/semantic_release/__init__.py
@@ -1,6 +1,6 @@
 """Semantic Release
 """
-__version__ = "7.4.0"
+__version__ = "7.4.1"
 
 
 from .errors import UnknownCommitMessageStyleError  # noqa; noqa
diff --git a/semantic_release/history/logs.py b/semantic_release/history/logs.py
index 78aef79..596e539 100644
--- a/semantic_release/history/logs.py
+++ b/semantic_release/history/logs.py
@@ -123,6 +123,13 @@ def generate_changelog(from_version: str, to_version: str = None) -> dict:
             if config.get("changelog_capitalize") is False:
                 formatted_message = message.descriptions[0]
 
+            # By default, feat(x): description shows up in changelog with the
+            # scope bolded, like:
+            #
+            # * **x**: description
+            if config.get("changelog_scope", True) and message.scope:
+                formatted_message = f'**{message.scope}:** {formatted_message}'
+
             changes[message.type].append((_hash, formatted_message))
 
             if message.breaking_descriptions:
diff --git a/semantic_release/settings.py b/semantic_release/settings.py
index e3a3f26..0da8e03 100644
--- a/semantic_release/settings.py
+++ b/semantic_release/settings.py
@@ -36,12 +36,14 @@ def _config_from_ini(paths):
     parser.read(paths)
 
     flags = {
+        "changelog_scope",
         "check_build_status",
         "commit_version_number",
         "remove_dist",
         "upload_to_pypi",
         "upload_to_release",
         "patch_without_tag",
+        "changelog_capitalize",
     }
 
     # Iterate through the sections so that default values are applied


Test patch:
diff --git a/tests/history/test_changelog.py b/tests/history/test_changelog.py
index e412dc8..7adf1ff 100644
--- a/tests/history/test_changelog.py
+++ b/tests/history/test_changelog.py
@@ -75,21 +75,74 @@ def test_should_get_multiple_breaking_descriptions():
         assert changelog["breaking"][1][1] == "Breaking change 2"
 
 
+@pytest.mark.parametrize(
+    "commit,commit_type,expected",
+    [
+        (MAJOR, "feature", "**x:** Add super-feature"),
+        (MINOR, "feature", "**x:** Add non-breaking super-feature"),
+        (PATCH, "fix", "**x:** Fix bug in super-feature"),
+    ],
+)
+def test_scope_is_included_in_changelog(commit, commit_type, expected):
+    with mock.patch(
+        "semantic_release.history.logs.get_commit_log",
+        lambda *a, **kw: [commit],
+    ):
+        changelog = generate_changelog("0.0.0")
+        assert changelog[commit_type][0][1] == expected
+
 @mock.patch(
     "semantic_release.history.logs.get_commit_log",
-    lambda *a, **k: [("23", "fix(x): abCD")],
+    lambda *a, **k: [("24", "fix: Fix another bug")],
+)
+def test_scope_is_omitted_with_empty_scope():
+    changelog = generate_changelog("0.0.0")
+    assert changelog['fix'][0][1] == "Fix another bug"
+
+
+@mock.patch(
+    "semantic_release.history.config.get",
+    wrapped_config_get(changelog_scope=False)
 )
 @pytest.mark.parametrize(
-    "config_setting,expected_description",
+    "commit,commit_type",
     [
-        (True, "AbCD"),
-        (False, "abCD"),
+        (MAJOR, "feature"),
+        (MINOR, "feature"),
+        (PATCH, "fix"),
     ],
 )
-def test_message_capitalization_is_configurable(config_setting, expected_description):
+def test_scope_included_in_changelog_configurable(commit, commit_type):
     with mock.patch(
-        "semantic_release.history.config.get",
-        wrapped_config_get(changelog_capitalize=config_setting),
+        "semantic_release.history.logs.get_commit_log",
+        lambda *a, **kw: [commit],
     ):
         changelog = generate_changelog("0.0.0")
-        assert changelog["fix"][0][1] == expected_description
+        assert "**x:**" not in changelog[commit_type][0][1]
+
+
+@mock.patch(
+    "semantic_release.history.logs.get_commit_log",
+    lambda *a, **k: [("23", "fix(x): abCD"), ("23", "fix: abCD")],
+)
+@pytest.mark.parametrize(
+    "commit,config_setting,expected_description",
+    [
+        (("23", "fix(x): abCD"), True, "**x:** AbCD"),
+        (("23", "fix(x): abCD"), False, "**x:** abCD"),
+        (("23", "fix: abCD"), True, "AbCD"),
+        (("23", "fix: abCD"), False, "abCD"),
+    ],
+)
+def test_message_capitalization_is_configurable(commit, config_setting, expected_description):
+    with mock.patch(
+        "semantic_release.history.logs.get_commit_log",
+        lambda *a, **kw: [commit],
+    ):
+
+        with mock.patch(
+            "semantic_release.history.config.get",
+            wrapped_config_get(changelog_capitalize=config_setting),
+        ):
+            changelog = generate_changelog("0.0.0")
+            assert changelog["fix"][0][1] == expected_description