Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 3a1af6a

Browse files
noirbizarrewoile
authored andcommitted
feat(providers): add a scm version provider
Reads version from the repository last tag matching `tag_format` Fixes #641
1 parent 6955cb5 commit 3a1af6a

File tree

4 files changed

+130
-2
lines changed

4 files changed

+130
-2
lines changed

‎commitizen/providers.py‎

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
from __future__ import annotations
22

33
import json
4+
import re
45
from abc import ABC, abstractmethod
56
from pathlib import Path
6-
from typing import Any, ClassVar, cast
7+
from typing import Any, Callable, ClassVar, Optional, cast
78

89
import importlib_metadata as metadata
910
import tomlkit
11+
from packaging.version import VERSION_PATTERN, Version
1012

1113
from commitizen.config.base_config import BaseConfig
1214
from commitizen.exceptions import VersionProviderUnknown
15+
from commitizen.git import get_tags
1316

1417
PROVIDER_ENTRYPOINT = "commitizen.provider"
1518
DEFAULT_PROVIDER = "commitizen"
@@ -163,6 +166,65 @@ class ComposerProvider(JsonProvider):
163166
indent = 4
164167

165168

169+
class ScmProvider(VersionProvider):
170+
"""
171+
A provider fetching the current/last version from the repository history
172+
173+
The version is fetched using `git describe` and is never set.
174+
175+
It is meant for `setuptools-scm` or any package manager `*-scm` provider.
176+
"""
177+
178+
TAG_FORMAT_REGEXS = {
179+
"$version": r"(?P<version>.+)",
180+
"$major": r"(?P<major>\d+)",
181+
"$minor": r"(?P<minor>\d+)",
182+
"$patch": r"(?P<patch>\d+)",
183+
"$prerelease": r"(?P<prerelease>\w+\d+)?",
184+
"$devrelease": r"(?P<devrelease>\.dev\d+)?",
185+
}
186+
187+
def _tag_format_matcher(self) -> Callable[[str], Optional[str]]:
188+
pattern = self.config.settings.get("tag_format") or VERSION_PATTERN
189+
for var, tag_pattern in self.TAG_FORMAT_REGEXS.items():
190+
pattern = pattern.replace(var, tag_pattern)
191+
192+
regex = re.compile(f"^{pattern}$", re.VERBOSE)
193+
194+
def matcher(tag: str) -> Optional[str]:
195+
match = regex.match(tag)
196+
if not match:
197+
return None
198+
groups = match.groupdict()
199+
if "version" in groups:
200+
return groups["version"]
201+
elif "major" in groups:
202+
return "".join(
203+
(
204+
groups["major"],
205+
f".{groups['minor']}" if groups.get("minor") else "",
206+
f".{groups['patch']}" if groups.get("patch") else "",
207+
groups["prerelease"] if groups.get("prerelease") else "",
208+
groups["devrelease"] if groups.get("devrelease") else "",
209+
)
210+
)
211+
elif pattern == VERSION_PATTERN:
212+
return str(Version(tag))
213+
return None
214+
215+
return matcher
216+
217+
def get_version(self) -> str:
218+
matcher = self._tag_format_matcher()
219+
return next(
220+
(cast(str, matcher(t.name)) for t in get_tags() if matcher(t.name)), "0.0.0"
221+
)
222+
223+
def set_version(self, version: str):
224+
# Not necessary
225+
pass
226+
227+
166228
def get_provider(config: BaseConfig) -> VersionProvider:
167229
"""
168230
Get the version provider as defined in the configuration

‎docs/config.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,16 @@ Commitizen provides some version providers for some well known formats:
124124
| name | description |
125125
| ---- | ----------- |
126126
| `commitizen` | Default version provider: Fetch and set version in commitizen config. |
127+
| `scm` | Fetch the version from git and does not need to set it back |
127128
| `pep621` | Get and set version from `pyproject.toml` `project.version` field |
128129
| `poetry` | Get and set version from `pyproject.toml` `tool.poetry.version` field |
129130
| `cargo` | Get and set version from `Cargo.toml` `project.version` field |
130131
| `npm` | Get and set version from `package.json` `project.version` field |
131132
| `composer` | Get and set version from `composer.json` `project.version` field |
132133

134+
!!! note
135+
The `scm` provider is meant to be used with `setuptools-scm` or any packager `*-scm` plugin.
136+
133137
### Custom version provider
134138

135139
You can add you own version provider by extending `VersionProvider` and exposing it on the `commitizen.provider` entrypoint.

‎pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ composer = "commitizen.providers:ComposerProvider"
110110
npm = "commitizen.providers:NpmProvider"
111111
pep621 = "commitizen.providers:Pep621Provider"
112112
poetry = "commitizen.providers:PoetryProvider"
113+
scm = "commitizen.providers:ScmProvider"
113114

114115
[tool.isort]
115116
profile = "black"

‎tests/test_version_providers.py‎

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os
44
from pathlib import Path
55
from textwrap import dedent
6-
from typing import TYPE_CHECKING, Iterator
6+
from typing import TYPE_CHECKING, Iterator, Optional
77

88
import pytest
99

@@ -16,8 +16,10 @@
1616
NpmProvider,
1717
Pep621Provider,
1818
PoetryProvider,
19+
ScmProvider,
1920
get_provider,
2021
)
22+
from tests.utils import create_file_and_commit, create_tag
2123

2224
if TYPE_CHECKING:
2325
from pytest_mock import MockerFixture
@@ -185,3 +187,62 @@ def test_composer_provider(config: BaseConfig, chdir: Path):
185187
}
186188
"""
187189
)
190+
191+
192+
@pytest.mark.parametrize(
193+
"tag_format,tag,version",
194+
(
195+
(None, "0.1.0", "0.1.0"),
196+
(None, "v0.1.0", "0.1.0"),
197+
("v$version", "v0.1.0", "0.1.0"),
198+
("version-$version", "version-0.1.0", "0.1.0"),
199+
("version-$version", "version-0.1", "0.1"),
200+
("version-$version", "version-0.1.0rc1", "0.1.0rc1"),
201+
("v$minor.$major.$patch", "v1.0.0", "0.1.0"),
202+
("version-$major.$minor.$patch", "version-0.1.0", "0.1.0"),
203+
("v$major.$minor$prerelease$devrelease", "v1.0rc1", "1.0rc1"),
204+
("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0", "0.1.0"),
205+
("v$major.$minor.$patch$prerelease$devrelease", "v0.1.0rc1", "0.1.0rc1"),
206+
("v$major.$minor.$patch$prerelease$devrelease", "v1.0.0.dev0", "1.0.0.dev0"),
207+
),
208+
)
209+
@pytest.mark.usefixtures("tmp_git_project")
210+
def test_scm_provider(
211+
config: BaseConfig, tag_format: Optional[str], tag: str, version: str
212+
):
213+
create_file_and_commit("test: fake commit")
214+
create_tag(tag)
215+
create_file_and_commit("test: fake commit")
216+
create_tag("should-not-match")
217+
218+
config.settings["version_provider"] = "scm"
219+
config.settings["tag_format"] = tag_format
220+
221+
provider = get_provider(config)
222+
assert isinstance(provider, ScmProvider)
223+
assert provider.get_version() == version
224+
225+
# Should not fail on set_version()
226+
provider.set_version("43.1")
227+
228+
229+
@pytest.mark.usefixtures("tmp_git_project")
230+
def test_scm_provider_default_without_matching_tag(config: BaseConfig):
231+
create_file_and_commit("test: fake commit")
232+
create_tag("should-not-match")
233+
create_file_and_commit("test: fake commit")
234+
235+
config.settings["version_provider"] = "scm"
236+
237+
provider = get_provider(config)
238+
assert isinstance(provider, ScmProvider)
239+
assert provider.get_version() == "0.0.0"
240+
241+
242+
@pytest.mark.usefixtures("tmp_git_project")
243+
def test_scm_provider_default_without_commits_and_tags(config: BaseConfig):
244+
config.settings["version_provider"] = "scm"
245+
246+
provider = get_provider(config)
247+
assert isinstance(provider, ScmProvider)
248+
assert provider.get_version() == "0.0.0"

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /