Repository: relekang/python-semantic-release (2021-06-25 ~ 2021-08-10)

problem_statement: Gitlab issue references in commits can be rendered as merge-requests
## The problem

If a commit message such as:-

```
fix: Check if issue integration works (#3)
```

where `#3` links to an issue - as per https://docs.gitlab.com/ee/user/markdown.html#gitlab-specific-references - and shows as an issue link when viewed on gitlab.

This is rendered in the changelog (with added newlines) as:-

```markdown
* Check if issue integration works 
  ([#3](https://gitlab.local/fred/project/-/merge_requests/3)) 
  ([`c3f8abd`](https://gitlab.local/fred/project/-/commit/c3f8abd9acaff597e6eae67c61e218556078b8cd))
```

Which links to `merge-request 3` - which would be `!3` as a gitlab link.

## Expected behavior

The changelog render should be:-

```markdown
* Check if issue integration works 
  ([#3](https://gitlab.local/fred/project/-/issues/3)) 
  ([`c3f8abd`](https://gitlab.local/fred/project/-/commit/c3f8abd9acaff597e6eae67c61e218556078b8cd))
```

However I see that `#3` can be either a github (not gitlab) issue or PR.  Helpful!

## Additional context

The other Changelog enhancements that are in the queue might well fix this in passing...

The specific problem is in https://github.com/relekang/python-semantic-release/blob/2111d1e81d3f6210ad00289a788ede199a17ee33/semantic_release/changelog/changelog.py#L19-L23
date: 2021-06-25
patch: diff --git a/CHANGELOG.md b/CHANGELOG.md
index f5c3290..d766b8e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 <!--next-version-placeholder-->
 
+## v7.16.3 (2021-07-29)
+### Fix
+* Print right info if token is not set (#360) ([#361](https://github.com/relekang/python-semantic-release/issues/361)) ([`a275a7a`](https://github.com/relekang/python-semantic-release/commit/a275a7a17def85ff0b41d254e4ee42772cce1981))
+
 ## v7.16.2 (2021-06-25)
 ### Fix
 * Use release-api for gitlab ([`1ef5cab`](https://github.com/relekang/python-semantic-release/commit/1ef5caba2d8dd0f2647bc51ede0ef7152d8b7b8d))
diff --git a/semantic_release/__init__.py b/semantic_release/__init__.py
index a2d81f7..0cc6668 100644
--- a/semantic_release/__init__.py
+++ b/semantic_release/__init__.py
@@ -1,6 +1,6 @@
 """Semantic Release
 """
-__version__ = "7.16.2"
+__version__ = "7.16.3"
 
 
 from .errors import UnknownCommitMessageStyleError  # noqa; noqa
diff --git a/semantic_release/changelog/changelog.py b/semantic_release/changelog/changelog.py
index 2f973f4..7259537 100644
--- a/semantic_release/changelog/changelog.py
+++ b/semantic_release/changelog/changelog.py
@@ -17,7 +17,7 @@ def add_pr_link(owner: str, repo_name: str, message: str) -> str:
     if match:
         pr_number = match.group(1)
         url = (
-            f"https://{Gitlab.domain()}/{owner}/{repo_name}/-/merge_requests/{pr_number}"
+            f"https://{Gitlab.domain()}/{owner}/{repo_name}/-/issues/{pr_number}"
             if config.get("hvcs") == "gitlab"
             else f"https://{Github.domain()}/{owner}/{repo_name}/issues/{pr_number}"
         )
diff --git a/semantic_release/cli.py b/semantic_release/cli.py
index 4495dd4..e2282f0 100644
--- a/semantic_release/cli.py
+++ b/semantic_release/cli.py
@@ -318,14 +318,20 @@ def publish(**kwargs):
             logger.warning("Missing token: cannot post changelog to HVCS")
 
         # Upload to GitHub Releases
-        if upload_release and check_token():
-            logger.info("Uploading to HVCS release")
-            upload_to_release(owner, name, new_version, dist_path)
+        if upload_release:
+            if check_token():
+                logger.info("Uploading to HVCS release")
+                upload_to_release(owner, name, new_version, dist_path)
+                logger.info("Upload to HVCS is complete")
+            else:
+                logger.warning("Missing token: cannot upload to HVCS")
+
         # Remove distribution files as they are no longer needed
         if should_remove_dist():
+            logger.info("Removing distribution files")
             remove_dists(dist_path)
 
-        logger.info("New release published")
+        logger.info("Publish has finished")
 
     # else: Since version shows a message on failure, we do not need to print another.
 

test_patch: diff --git a/tests/test_changelog.py b/tests/test_changelog.py
index 75e3eb0..5834f80 100644
--- a/tests/test_changelog.py
+++ b/tests/test_changelog.py
@@ -74,14 +74,14 @@ def test_markdown_changelog_gitlab():
             # Expected output with the default configuration
             "### Feature\n"
             "* Add non-breaking super-feature ([#1](https://gitlab.com/owner/"
-            "repo_name/-/merge_requests/1)) ([`145`](https://gitlab.com/owner/"
+            "repo_name/-/issues/1)) ([`145`](https://gitlab.com/owner/"
             "repo_name/-/commit/145))\n"
             "* Add super-feature ([`134`](https://gitlab.com/owner/repo_name/-/"
             "commit/134))\n"
             "\n"
             "### Documentation\n"
             "* Document super-feature ([#189](https://gitlab.com/owner/repo_name/"
-            "-/merge_requests/189)) ([`0`](https://gitlab.com/owner/repo_name/"
+            "-/issues/189)) ([`0`](https://gitlab.com/owner/repo_name/"
             "-/commit/0))"
         )
 
@@ -158,7 +158,7 @@ def test_get_changelog_sections():
         (
             "test (#123)",
             "gitlab",
-            "test ([#123](https://gitlab.com/owner/name/-/merge_requests/123))",
+            "test ([#123](https://gitlab.com/owner/name/-/issues/123))",
         ),
         ("test without commit", "gitlab", "test without commit"),
     ],


----

problem_statement: Support parsing tags without "v" prefix
## Description

As from https://github.com/relekang/python-semantic-release/blob/master/semantic_release/vcs_helpers.py#L72 it seems the tool expects tags to be named like "v1.2.3". 

However I use tags like "1.2.3" and I don't see reasons to add "v". I would expect the tool should be smart enough to find the version out of such tags as well


## Use cases

Repos having `1.2.3` as tags.

## Possible implementation

Well, just removing `v` from  https://github.com/relekang/python-semantic-release/blob/master/semantic_release/vcs_helpers.py#L72 should do the trick. 

date: 2021-08-03
patch: diff --git a/CHANGELOG.md b/CHANGELOG.md
index d766b8e..39c9e6c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 <!--next-version-placeholder-->
 
+## v7.17.0 (2021-08-07)
+### Feature
+* **parser:** Add scipy style parser ([#369](https://github.com/relekang/python-semantic-release/issues/369)) ([`51a3921`](https://github.com/relekang/python-semantic-release/commit/51a39213ea120c4bbd7a57b74d4f0cc3103da9f5))
+
 ## v7.16.3 (2021-07-29)
 ### Fix
 * Print right info if token is not set (#360) ([#361](https://github.com/relekang/python-semantic-release/issues/361)) ([`a275a7a`](https://github.com/relekang/python-semantic-release/commit/a275a7a17def85ff0b41d254e4ee42772cce1981))
diff --git a/docs/commit-log-parsing.rst b/docs/commit-log-parsing.rst
index b7b88e7..ebe36f3 100644
--- a/docs/commit-log-parsing.rst
+++ b/docs/commit-log-parsing.rst
@@ -45,8 +45,8 @@ of the output should be a :py:class:`semantic_release.history.parser_helpers.Par
 object with the following parameters::
 
     ParsedCommit(
-      level to bump: major=3 minor=2 patch=1 none=0,
-      type of change,
+      version level to bump: major=3 minor=2 patch=1 none=0,
+      changelog section (see: :ref:`config-changelog_sections`),
       scope of change: can be None,
       (subject, descriptions...),
       (breaking change descriptions...)
diff --git a/semantic_release/__init__.py b/semantic_release/__init__.py
index 0cc6668..33fa7a7 100644
--- a/semantic_release/__init__.py
+++ b/semantic_release/__init__.py
@@ -1,6 +1,6 @@
 """Semantic Release
 """
-__version__ = "7.16.3"
+__version__ = "7.17.0"
 
 
 from .errors import UnknownCommitMessageStyleError  # noqa; noqa
diff --git a/semantic_release/history/__init__.py b/semantic_release/history/__init__.py
index a069fc0..60a96e2 100644
--- a/semantic_release/history/__init__.py
+++ b/semantic_release/history/__init__.py
@@ -19,6 +19,7 @@ from .logs import evaluate_version_bump  # noqa
 
 from .parser_angular import parse_commit_message as angular_parser  # noqa isort:skip
 from .parser_emoji import parse_commit_message as emoji_parser  # noqa isort:skip
+from .parser_scipy import parse_commit_message as scipy_parser  # noqa isort:skip
 from .parser_tag import parse_commit_message as tag_parser  # noqa isort:skip
 
 logger = logging.getLogger(__name__)
diff --git a/semantic_release/history/parser_scipy.py b/semantic_release/history/parser_scipy.py
new file mode 100644
index 0000000..1342412
--- /dev/null
+++ b/semantic_release/history/parser_scipy.py
@@ -0,0 +1,167 @@
+"""
+Scipy Style Parser
+------------------
+
+Parses commit messages using `scipy tags <scipy-style>`_ of the form::
+
+    <tag>(<scope>): <subject>
+
+    <body>
+
+
+The elements <tag>, <scope> and <body> are optional. If no tag is present, the
+commit will be added to the changelog section "None" and no version increment
+will be performed.
+
+To communicate a breaking change add "BREAKING CHANGE" into the body at the
+beginning of a paragraph. Fill this paragraph with information how to migrate
+from the broken behavior to the new behavior. This section will be added to the
+"Breaking" section of the changelog.
+
+Supported Tags::
+
+    API, DEP, ENH, REV, BUG, MAINT, BENCH, BLD,
+    DEV, DOC, STY, TST, REL, FEAT, TEST
+
+Supported Changelog Sections::
+
+    breaking, feature, fix, Other, None
+
+.. note::
+    While <scope> is supported here it isn't actually part of the scipy style.
+    If it is missing, parentheses around it are too. The commit should then be
+    of the form::
+
+        <tag>: <subject>
+
+        <body>
+
+.. _`scipy-style`: https://docs.scipy.org/doc/scipy/reference/dev/contributor/development_workflow.html#writing-the-commit-message
+"""
+
+import logging
+import re
+
+from ..errors import UnknownCommitMessageStyleError
+from ..helpers import LoggedFunction
+from .parser_helpers import ParsedCommit
+
+logger = logging.getLogger(__name__)
+
+
+class ChangeType:
+    def __init__(self, tag, section) -> None:
+        self.tag: str = tag
+        self.section: str = section
+        self.bump_level: int = 0
+
+    def make_breaking(self):
+        self.bump_level = 3
+
+
+class Breaking(ChangeType):
+    def __init__(self, tag, section) -> None:
+        super().__init__(tag, section)
+        self.bump_level: int = 3
+
+
+class Compatible(ChangeType):
+    def __init__(self, tag, section) -> None:
+        super().__init__(tag, section)
+        self.bump_level: int = 2
+
+
+class Patch(ChangeType):
+    def __init__(self, tag, section) -> None:
+        super().__init__(tag, section)
+        self.bump_level: int = 1
+
+
+class Ignore(ChangeType):
+    def __init__(self, tag, section) -> None:
+        super().__init__(tag, section)
+        self.bump_level: int = 0
+
+
+COMMIT_TYPES = [
+    Breaking("API", "breaking"),
+    Ignore("BENCH", "None"),
+    Patch("BLD", "fix"),
+    Patch("BUG", "fix"),
+    Compatible("DEP", "breaking"),
+    Compatible("DEV", "None"),
+    Ignore("DOC", "documentation"),
+    Compatible("ENH", "feature"),
+    Patch("MAINT", "fix"),
+    Compatible("REV", "Other"),
+    Ignore("STY", "None"),
+    Ignore("TST", "None"),
+    Ignore("REL", "None"),
+    # strictly speaking not part of the standard
+    Compatible("FEAT", "feature"),
+    Ignore("TEST", "None"),
+]
+
+_commit_filter = "|".join(c.tag for c in COMMIT_TYPES)
+re_parser = re.compile(
+    fr"(?P<tag>{_commit_filter})?"
+    r"(?:\((?P<scope>[^\n]+)\))?"
+    r":? "
+    r"(?P<subject>[^\n]+):?"
+    r"(\n\n(?P<text>.*))?",
+    re.DOTALL,
+)
+
+
+@LoggedFunction(logger)
+def parse_commit_message(message: str) -> ParsedCommit:
+    """
+    Parse a scipy-style commit message
+
+    :param message: A string of a commit message.
+    :return: A tuple of (level to bump, type of change, scope of change, a tuple
+    with descriptions)
+    :raises UnknownCommitMessageStyleError: if regular expression matching fails
+    """
+
+    parsed = re_parser.match(message)
+    if not parsed:
+        raise UnknownCommitMessageStyleError(
+            f"Unable to parse the given commit message: {message}"
+        )
+
+    if parsed.group("subject"):
+        subject = parsed.group("subject")
+    else:
+        raise UnknownCommitMessageStyleError(f"The commit has no subject {message}")
+
+    if parsed.group("text"):
+        blocks = parsed.group("text").split("\n\n")
+        blocks = [x for x in blocks if not x == ""]
+        blocks.insert(0, subject)
+    else:
+        blocks = [subject]
+
+    msg_type: ChangeType
+    for msg_type in COMMIT_TYPES:
+        if msg_type.tag == parsed.group("tag"):
+            break
+    else:
+        # some commits may not have a tag, e.g. if they belong to a PR that
+        # wasn't squashed (for maintainability) ignore them
+        msg_type = Ignore("", "None")
+
+    # Look for descriptions of breaking changes
+    migration_instructions = [
+        block for block in blocks if block.startswith("BREAKING CHANGE")
+    ]
+    if migration_instructions:
+        msg_type.make_breaking()
+
+    return ParsedCommit(
+        msg_type.bump_level,
+        msg_type.section,
+        parsed.group("scope"),
+        blocks,
+        migration_instructions,
+    )
diff --git a/semantic_release/vcs_helpers.py b/semantic_release/vcs_helpers.py
index 181423c..6ca139f 100644
--- a/semantic_release/vcs_helpers.py
+++ b/semantic_release/vcs_helpers.py
@@ -69,10 +69,9 @@ def get_last_version(skip_tags=None) -> Optional[str]:
         return tag.commit.committed_date
 
     for i in sorted(repo.tags, reverse=True, key=version_finder):
-        if re.match(r"v\d+\.\d+\.\d+", i.name):  # Matches vX.X.X
-            if i.name in skip_tags:
-                continue
-            return i.name[1:]  # Strip off 'v'
+        match = re.search(r"\d+\.\d+\.\d+", i.name)
+        if match and i.name not in skip_tags:
+            return match.group(0)  # Return only numeric vesion like 1.2.3
 
     return None
 

test_patch: diff --git a/tests/parsers/conftest.py b/tests/parsers/conftest.py
new file mode 100644
index 0000000..cd1ef4b
--- /dev/null
+++ b/tests/parsers/conftest.py
@@ -0,0 +1,72 @@
+import pytest
+
+from semantic_release.history.parser_scipy import COMMIT_TYPES, ChangeType
+
+
+@pytest.fixture(params=COMMIT_TYPES)
+def scipy_type(request):
+    return request.param
+
+
+@pytest.fixture(
+    params=[
+        "scipy.stats.qmc: centered-discrepancy optimization of a Latin hypercube",
+        "inverse missing in idstn, idctn (#14479)",
+        "Merge pull request #14447 from AnirudhDagar/rename_ndimage_modules",
+        "Add tests for args kwarg in quad_vec",
+        "badge with version of the doc in the navbar (#14132)",
+        "Bump scipy from 1.7.0 to 1.7.1 (#28)",
+        "avoid nan if angle=0 in RotvecRotation",
+    ]
+)
+def subject(request):
+    return request.param
+
+
+@pytest.fixture(
+    params=[
+        # a squash merge that preserved PR commit messages
+        (
+            "DOC: import ropy.transform to test for numpy error",
+            "DOC: lower numpy version",
+            "DOC: lower numpy version further",
+            "MAINT: remove debugging import",
+        ),
+        # empty body
+        list(),
+        # formatted body
+        (
+            """Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.5.3 to 4.1.1.
+            - [Release notes](https://github.com/sphinx-doc/sphinx/releases)
+            - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES)
+            - [Commits](https://github.com/sphinx-doc/sphinx/commits/v4.1.1)""",
+            """---
+            updated-dependencies:
+            - dependency-name: sphinx
+            dependency-type: direct:development
+            update-type: version-update:semver-major""",
+        ),
+        (
+            "Bug spotted on Fedora, see https://src.fedoraproject.org/rpms/scipy/pull-request/22",
+            "The `int[::]` annotation is used to accept non-contiguous views.",
+        ),
+        ("[skip azp] [skip actions]",),
+    ]
+)
+def body_parts(request):
+    return request.param
+
+
+@pytest.fixture()
+def expected_response_scipy(scipy_type: ChangeType, subject, body_parts):
+    bump_level = scipy_type.bump_level
+    topic = scipy_type.section
+    changelog_body = (subject, *body_parts)
+    return (bump_level, topic, None, changelog_body)
+
+
+@pytest.fixture()
+def valid_scipy_commit(scipy_type: ChangeType, subject, body_parts):
+    body = "\n\n".join(body_parts)
+    commit_msg = f"{scipy_type.tag}: {subject}\n\n{body}"
+    return commit_msg
diff --git a/tests/parsers/test_scipy.py b/tests/parsers/test_scipy.py
new file mode 100644
index 0000000..e338533
--- /dev/null
+++ b/tests/parsers/test_scipy.py
@@ -0,0 +1,14 @@
+import pytest
+
+from semantic_release.errors import UnknownCommitMessageStyleError
+from semantic_release.history import scipy_parser
+
+
+def test_valid_scipy_commit(valid_scipy_commit, expected_response_scipy):
+    (commit_tag, subject, _, body_parts) = expected_response_scipy
+    result = scipy_parser(valid_scipy_commit)
+
+    assert result[0] == commit_tag
+    assert result[1] == subject
+    assert len(result[3]) == len(body_parts)
+    assert all(a == b for a, b in zip(result[3], body_parts))


----

problem_statement: Configurable tag names
## Description

Currently when a commit is tagged, the tag name and message are both fixed to the version number.

https://github.com/relekang/python-semantic-release/blob/1dd84847a9eaf8d2467ae5b4ee82492ef563612f/semantic_release/vcs_helpers.py#L198-L204

It would be nice if the user were able to configure a template for the tag name or message.

## Use cases

One major use case would be when `python-semantic-release` is being used in a staging environment. The tags could take the form `vX.Y.X-staging`. Note that this would not be a prerelease specification. `python-semantic-release` only supports versioning at the `major`, `minor`, and `patch` levels so this would template would live outside the scope of semantic versioning. It would exist solely as git tags.



## Possible implementation

https://github.com/relekang/python-semantic-release/blob/18e1d70138125f93664f3c9b3c1e08c57f392977/semantic_release/cli.py#L173-L187

The calling function already has access to the config variables, so it would be straightforward to add a `tag_template` configuration variable.

date: 2021-08-10
patch: diff --git a/docs/configuration.rst b/docs/configuration.rst
index cb5eb22..2a13859 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -435,3 +435,17 @@ If enabled, the status of the head commit will be checked and a release will onl
 if the status is success.
 
 Default: `false`
+
+.. _config-tag_format:
+
+``tag_format``
+------------------
+Git tag format. Accepts the following variables as format fields:
+
+================  ========
+Variable          Contents
+================  ========
+``{version}``     The new version number in the format ``X.Y.Z``.
+================  ========
+
+Default: ``v{version}``
diff --git a/semantic_release/defaults.cfg b/semantic_release/defaults.cfg
index f45e8a7..6ceee2b 100644
--- a/semantic_release/defaults.cfg
+++ b/semantic_release/defaults.cfg
@@ -24,3 +24,4 @@ changelog_file=CHANGELOG.md
 changelog_placeholder=<!--next-version-placeholder-->
 changelog_scope=true
 tag_commit=true
+tag_format=v{version}
diff --git a/semantic_release/vcs_helpers.py b/semantic_release/vcs_helpers.py
index 6ca139f..31d2a36 100644
--- a/semantic_release/vcs_helpers.py
+++ b/semantic_release/vcs_helpers.py
@@ -196,11 +196,13 @@ def update_changelog_file(version: str, content_to_add: str):
 @LoggedFunction(logger)
 def tag_new_version(version: str):
     """
-    Create a new tag with the version number, prefixed with v.
+    Create a new tag with the version number, prefixed with v by default.
 
     :param version: The version number used in the tag as a string.
     """
-    return repo.git.tag("-a", f"v{version}", m=f"v{version}")
+    tag_format = config.get("tag_format")
+    tag = tag_format.format(version=version)
+    return repo.git.tag("-a", tag, m=tag)
 
 
 @check_repo

test_patch: diff --git a/tests/test_vcs_helpers.py b/tests/test_vcs_helpers.py
index 6bc5420..969a0be 100644
--- a/tests/test_vcs_helpers.py
+++ b/tests/test_vcs_helpers.py
@@ -103,9 +103,13 @@ def test_add_and_commit(mock_git, mocker, params):
     mock_git.commit.assert_called_once_with(**params["commit_args"])
 
 
-def test_tag_new_version(mock_git):
+def test_tag_new_version(mock_git, mocker):
+    mocker.patch(
+        "semantic_release.vcs_helpers.config.get",
+        return_value="ver{version}",
+    )
     tag_new_version("1.0.0")
-    mock_git.tag.assert_called_with("-a", "v1.0.0", m="v1.0.0")
+    mock_git.tag.assert_called_with("-a", "ver1.0.0", m="ver1.0.0")
 
 
 def test_push_new_version(mock_git):