Repository: pypa/setuptools_scm

Issue:
tags with dashes get mangled
I want to be able to tag my git repo with "1.0.0-rc1".  That version should be acceptable to setuptools_scm independently of "distance", "hash", and "dirty" suffixes.

Doing so results in an error (sample tag is "3.3.1-rc26"):

```
looking for ep setuptools_scm.parse_scm_fallback .
root '/Users/twall/plex/rd'
looking for ep setuptools_scm.parse_scm /Users/twall/plex/rd
found ep .git = setuptools_scm.git:parse
cmd 'git rev-parse --show-toplevel'
out b'/Users/twall/plex/rd\n'
real root /Users/twall/plex/rd
cmd 'git describe --dirty --tags --long --match *.*'
out b'3.3.1-rc26-0-g9df187b\n'
cmd 'git rev-parse --abbrev-ref HEAD'
out b'pyplex\n'
tag 3.3.1-rc26
version <LegacyVersion('rc26')>
version None
Traceback (most recent call last):
  File "setup.py", line 46, in <module>
    entry_points = {
  File "/Users/twall/plex/rd/search/.venv-3.6/lib/python3.6/site-packages/setuptools/__init__.py", line 129, in setup
    return distutils.core.setup(**attrs)
  File "/anaconda3/lib/python3.6/distutils/core.py", line 108, in setup
    _setup_distribution = dist = klass(attrs)
  File "/Users/twall/plex/rd/search/.venv-3.6/lib/python3.6/site-packages/setuptools/dist.py", line 370, in __init__
    k: v for k, v in attrs.items()
  File "/anaconda3/lib/python3.6/distutils/dist.py", line 281, in __init__
    self.finalize_options()
  File "/Users/twall/plex/rd/search/.venv-3.6/lib/python3.6/site-packages/setuptools/dist.py", line 529, in finalize_options
    ep.load()(self, ep.name, value)
  File "/Users/twall/plex/rd/search/.venv-3.6/lib/python3.6/site-packages/setuptools_scm/integration.py", line 22, in version_keyword
    dist.metadata.version = get_version(**value)
  File "/Users/twall/plex/rd/search/.venv-3.6/lib/python3.6/site-packages/setuptools_scm/__init__.py", line 119, in get_version
    parsed_version = _do_parse(root, parse)
  File "/Users/twall/plex/rd/search/.venv-3.6/lib/python3.6/site-packages/setuptools_scm/__init__.py", line 83, in _do_parse
    version = version_from_scm(root) or _version_from_entrypoint(
  File "/Users/twall/plex/rd/search/.venv-3.6/lib/python3.6/site-packages/setuptools_scm/__init__.py", line 31, in version_from_scm
    return _version_from_entrypoint(root, 'setuptools_scm.parse_scm')
  File "/Users/twall/plex/rd/search/.venv-3.6/lib/python3.6/site-packages/setuptools_scm/__init__.py", line 36, in _version_from_entrypoint
    version = ep.load()(root)
  File "/Users/twall/plex/rd/search/.venv-3.6/lib/python3.6/site-packages/setuptools_scm/git.py", line 128, in parse
    return meta(tag, node=node, dirty=dirty, branch=branch)
  File "/Users/twall/plex/rd/search/.venv-3.6/lib/python3.6/site-packages/setuptools_scm/version.py", line 135, in meta
    assert tag is not None, 'cant parse version %s' % tag
AssertionError: cant parse version None
```

The `SemVer` description seems to allow nearly arbitrary strings for `www` in `x.y.z-www`, so I would expect such tags to work.

Note that "3.3.1.rc26" sort of works, resulting in a modified version of "3.3.1rc26".

Discussion:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index adc33c1..1548d32 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,6 +6,8 @@ v3.0.0
 * switch to src layout (breaking change)
 * no longer alias tag and parsed_version in order to support understanding a version parse failure
 * require parse results to be ScmVersion or None (breaking change)
+* fix #266 by requirin the prefix word to be a word again
+  (breaking change as the bug allowed arbitrary prefixes while the original feature only allowed words")
 
 v2.1.0
 ======
diff --git a/src/setuptools_scm/git.py b/src/setuptools_scm/git.py
index 6ff2c0d..7c45a84 100644
--- a/src/setuptools_scm/git.py
+++ b/src/setuptools_scm/git.py
@@ -111,19 +111,26 @@ def parse(root, describe_command=DEFAULT_DESCRIBE, pre_parse=warn_on_shallow):
             dirty=dirty,
             branch=wd.get_branch(),
         )
+    else:
+        tag, number, node, dirty = _git_parse_describe(out)
+
+        branch = wd.get_branch()
+        if number:
+            return meta(tag, distance=number, node=node, dirty=dirty, branch=branch)
+        else:
+            return meta(tag, node=node, dirty=dirty, branch=branch)
+
 
-    # 'out' looks e.g. like 'v1.5.0-0-g4060507' or
+def _git_parse_describe(describe_output):
+    # 'describe_output' looks e.g. like 'v1.5.0-0-g4060507' or
     # 'v1.15.1rc1-37-g9bd1298-dirty'.
-    if out.endswith("-dirty"):
+
+    if describe_output.endswith("-dirty"):
         dirty = True
-        out = out[:-6]
+        describe_output = describe_output[:-6]
     else:
         dirty = False
 
-    tag, number, node = out.rsplit("-", 2)
+    tag, number, node = describe_output.rsplit("-", 2)
     number = int(number)
-    branch = wd.get_branch()
-    if number:
-        return meta(tag, distance=number, node=node, dirty=dirty, branch=branch)
-    else:
-        return meta(tag, node=node, dirty=dirty, branch=branch)
+    return tag, number, node, dirty
diff --git a/src/setuptools_scm/version.py b/src/setuptools_scm/version.py
index aba231d..5799f1a 100644
--- a/src/setuptools_scm/version.py
+++ b/src/setuptools_scm/version.py
@@ -13,6 +13,7 @@ from pkg_resources import parse_version as pkg_parse_version
 SEMVER_MINOR = 2
 SEMVER_PATCH = 3
 SEMVER_LEN = 3
+TAG_PREFIX = re.compile(r"^\w+-(.*)")
 
 
 def _pad(iterable, size, padding=None):
@@ -56,14 +57,21 @@ def callable_or_entrypoint(group, callable_or_name):
 
 
 def tag_to_version(tag):
+    """
+    take a tag that might be prefixed with a keyword and return only the version part
+    """
     trace("tag", tag)
     if "+" in tag:
         warnings.warn("tag %r will be stripped of the local component" % tag)
         tag = tag.split("+")[0]
     # lstrip the v because of py2/py3 differences in setuptools
     # also required for old versions of setuptools
-
-    version = tag.rsplit("-", 1)[-1].lstrip("v")
+    prefix_match = TAG_PREFIX.match(tag)
+    if prefix_match is not None:
+        version = prefix_match.group(1)
+    else:
+        version = tag
+    trace("version pre parse", version)
     if VERSION_CLASS is None:
         return version
     version = pkg_parse_version(version)


Test patch:
diff --git a/testing/test_functions.py b/testing/test_functions.py
index 0c817b8..db573ac 100644
--- a/testing/test_functions.py
+++ b/testing/test_functions.py
@@ -2,7 +2,12 @@ import pytest
 import sys
 import pkg_resources
 from setuptools_scm import dump_version, get_version, PRETEND_KEY
-from setuptools_scm.version import guess_next_version, meta, format_version
+from setuptools_scm.version import (
+    guess_next_version,
+    meta,
+    format_version,
+    tag_to_version,
+)
 from setuptools_scm.utils import has_command
 
 PY3 = sys.version_info > (2,)
@@ -77,3 +82,16 @@ def test_has_command(recwarn):
     assert not has_command("yadayada_setuptools_aint_ne")
     msg = recwarn.pop()
     assert "yadayada" in str(msg.message)
+
+
+@pytest.mark.parametrize(
+    "tag, expected_version",
+    [
+        ("1.1", "1.1"),
+        ("release-1.1", "1.1"),
+        pytest.param("3.3.1-rc26", "3.3.1rc26", marks=pytest.mark.issue(266)),
+    ],
+)
+def test_tag_to_version(tag, expected_version):
+    version = str(tag_to_version(tag))
+    assert version == expected_version
diff --git a/testing/test_git.py b/testing/test_git.py
index be7e0a4..9530400 100644
--- a/testing/test_git.py
+++ b/testing/test_git.py
@@ -16,6 +16,15 @@ def wd(wd):
     return wd
 
 
+@pytest.mark.parametrize(
+    "given, tag, number, node, dirty",
+    [("3.3.1-rc26-0-g9df187b", "3.3.1-rc26", 0, "g9df187b", False)],
+)
+def test_parse_describe_output(given, tag, number, node, dirty):
+    parsed = git._git_parse_describe(given)
+    assert parsed == (tag, number, node, dirty)
+
+
 def test_version_from_git(wd):
     assert wd.version == "0.1.dev0"