Repository: relekang/python-semantic-release (python)

Issue:
Include the commit range since the last release into changelog
## Description

We could add a section at the bottom of the changelog that links to the commit range on GitHub for a certain release since the previous release with heading like "Latest changes".

I got the idea from [another project](https://github.com/klaussinani/ao/releases/tag/v6.9.0) and find it very useful.

Maybe we would want to introduce a new cli parameter `--include-commit-range` for that to be an optional behaviour.

## Use cases

Including the commit range would allow developers to quickly get hands on the associated commits for a certain release.

## Possible implementation

The GitHub link we get almost as a side effect since it is of the form.

```
https://github.com/<user_or_oranization>/<project>/compare/<previous-version-tag>...<current-version-tag>
```

This we could easily include in the generated changelog.

Pull request description:
Add link to view commit range in changelog
Display a link in the changelog to the page comparing commits between this release and the previous one.

This is configured using the config option `compare_link`, and is off by default since GitHub displays a "compare" menu next to the release for the same purpose:

![image](https://user-images.githubusercontent.com/28959268/81408516-48450b00-9135-11ea-9c45-82a0cbe5224a.png)

Fixes #218

Patch:
diff --git a/docs/configuration.rst b/docs/configuration.rst
index 2d40747..afd4f77 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -136,6 +136,17 @@ The available options are:
 
 Default: `feature, fix, breaking, documentation, performance`
 
+.. _config-compare_link:
+
+``compare_link``
+----------------
+When set to true, add a link to view the commit range since the previous
+release, on GitHub. Only appears when using :ref:`cmd-publish`.
+
+.. note::
+  If you are using a different HVCS, the link will not be included
+  regardless of this option.
+
 Distributions
 =============
 
diff --git a/semantic_release/cli.py b/semantic_release/cli.py
index 3f76753..77f5534 100644
--- a/semantic_release/cli.py
+++ b/semantic_release/cli.py
@@ -248,7 +248,10 @@ def publish(**kwargs):
                     owner,
                     name,
                     new_version,
-                    markdown_changelog(new_version, log, header=False),
+                    markdown_changelog(
+                        new_version, log, header=False,
+                        previous_version=current_version
+                    ),
                 )
             except GitError:
                 logger.error("Posting changelog failed")
diff --git a/semantic_release/defaults.cfg b/semantic_release/defaults.cfg
index 9f054cc..45d76d5 100644
--- a/semantic_release/defaults.cfg
+++ b/semantic_release/defaults.cfg
@@ -15,3 +15,4 @@ remove_dist=true
 build_command=python setup.py sdist bdist_wheel
 branch=master
 changelog_sections=feature,fix,breaking,documentation,performance
+compare_link=false
diff --git a/semantic_release/history/logs.py b/semantic_release/history/logs.py
index ed5b544..961ab34 100644
--- a/semantic_release/history/logs.py
+++ b/semantic_release/history/logs.py
@@ -6,7 +6,7 @@ from typing import Optional
 from ..errors import UnknownCommitMessageStyleError
 from ..helpers import LoggedFunction
 from ..settings import config, current_commit_parser
-from ..vcs_helpers import get_commit_log
+from ..vcs_helpers import get_commit_log, get_repository_owner_and_name
 from .parser_helpers import re_breaking
 
 logger = logging.getLogger(__name__)
@@ -160,12 +160,29 @@ def generate_changelog(from_version: str, to_version: str = None) -> dict:
     return changes
 
 
+def get_github_compare_url(from_version: str, to_version: str) -> str:
+    """
+    Get the GitHub comparison link between two version tags.
+
+    :param from_version: The older version to compare.
+    :param to_version: The newer version to compare.
+    :return: Link to view a comparison between the two versions.
+    """
+    owner, name = get_repository_owner_and_name()
+    return (
+        f"https://github.com/{owner}/{name}"
+        f"/compare/v{from_version}...v{to_version}"
+    )
+
+
 @LoggedFunction(logger)
-def markdown_changelog(version: str, changelog: dict, header: bool = False) -> str:
+def markdown_changelog(version: str, changelog: dict, header: bool = False, previous_version: str = None) -> str:
     """
     Generate a markdown version of the changelog.
 
     :param version: A string with the version number.
+    :param previous_version: A string with the last version number, to
+        use for the comparison URL. If omitted, the URL will not be included.
     :param changelog: A parsed changelog dict from generate_changelog.
     :param header: A boolean that decides whether a version number header should be included.
     :return: The markdown formatted changelog.
@@ -175,6 +192,14 @@ def markdown_changelog(version: str, changelog: dict, header: bool = False) -> s
         # Add a heading with the version number
         output += "## v{0}\n".format(version)
 
+    if (
+        config.getboolean("semantic_release", "compare_link")
+        and config.get("semantic_release", "hvcs").lower() == 'github'
+        and previous_version
+    ):
+        compare_url = get_github_compare_url(previous_version, version)
+        output += f"**[See all commits in this version]({compare_url})**\n"
+
     # Sections which will be shown in the Markdown changelog.
     # This is NOT related to supported commit types.
     changelog_sections = config.get("semantic_release", "changelog_sections")


Test patch:
diff --git a/tests/history/test_markdown_changelog.py b/tests/history/test_markdown_changelog.py
index 47cce37..45166da 100644
--- a/tests/history/test_markdown_changelog.py
+++ b/tests/history/test_markdown_changelog.py
@@ -1,4 +1,17 @@
-from semantic_release.history.logs import markdown_changelog
+import mock
+
+from semantic_release.history.logs import markdown_changelog, get_github_compare_url
+
+
+def test_github_compare_url():
+    with mock.patch(
+        'semantic_release.history.logs.get_repository_owner_and_name',
+        return_value=['owner', 'name']
+    ):
+        assert (
+            get_github_compare_url('1.0.0', '2.0.0') ==
+            "https://github.com/owner/name/compare/v1.0.0...v2.0.0"
+        )
 
 
 def test_should_output_all_sections():
@@ -78,3 +91,31 @@ def test_should_output_heading():
         },
         header=True,
     )
+
+
+def test_compare_url(mocker):
+    mocker.patch(
+        'semantic_release.history.logs.config.getboolean',
+        return_value=True
+    )
+    mocker.patch(
+        'semantic_release.history.logs.get_repository_owner_and_name',
+        return_value=['owner', 'name']
+    )
+
+    assert (
+        "**[See all commits in this version]"
+        "(https://github.com/owner/name/compare/v1.0.0...v2.0.0)**\n"
+        in markdown_changelog(
+            "2.0.0",
+            {
+                "refactor": [],
+                "breaking": [],
+                "feature": [],
+                "fix": [],
+                "documentation": [],
+                "performance": [],
+            },
+            previous_version="1.0.0",
+        )
+    )
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 102541e..72c6f4a 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -514,7 +514,10 @@ def test_publish_giterror_when_posting(mocker):
     )
     mock_check_token.assert_called_once_with()
     mock_generate.assert_called_once_with("current", "new")
-    mock_markdown.assert_called_once_with("new", "super changelog", header=False)
+    mock_markdown.assert_called_once_with(
+        "new", "super changelog",
+        header=False, previous_version='current'
+    )
     mock_post.assert_called_once_with("owner", "name", "new", "super md changelog")