From bca897506ad1242431ac6365347f62fd694015e0 Mon Sep 17 00:00:00 2001 From: ychampion Date: Sun, 12 Jul 2026 13:59:31 +0000 Subject: [PATCH] fix(version): recognize PEP 440 development tags Keep development-release tags discoverable across sequential bumps while preserving custom tag embedding and existing suffix behavior. Constraint: Preserve custom tag formats and SemVer-compatible parsing. Rejected: Replace the shared parser with the anchored PEP 440 reference regex | it breaks embedded tag formats and unrelated schemes. Confidence: high Scope-risk: narrow Directive: Keep generated version forms round-trippable through TagRules. Tested: uv run poe all; 446 focused tag, version, bump, and changelog tests; exact uv-provider sequential bump regression; git diff --check. Not-tested: Full tox matrix across every supported Python version and operating system. --- commitizen/version_schemes.py | 2 +- tests/commands/test_bump_command.py | 49 +++++++++++++++++++++++++++++ tests/test_tags.py | 22 +++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/commitizen/version_schemes.py b/commitizen/version_schemes.py index 14a55d655..736d655b5 100644 --- a/commitizen/version_schemes.py +++ b/commitizen/version_schemes.py @@ -34,7 +34,7 @@ Increment: TypeAlias = Literal["MAJOR", "MINOR", "PATCH"] # TODO: deprecate Prerelease: TypeAlias = Literal["alpha", "beta", "rc"] _DEFAULT_VERSION_PARSER = re.compile( - r"v?(?P([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.]+)?(\w+)?)" + r"v?(?P([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\w+)?(?:\.dev\d+)?(?:\+[0-9A-Za-z.]+)?)" ) diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index b26f095c9..e1d52af29 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -237,6 +237,55 @@ def test_bump_command_prerelease(util: UtilFixture): assert git.tag_exist("0.2.0") is True +def test_bump_devrelease_with_incremental_changelog( + tmp_commitizen_project: Path, util: UtilFixture, changelog_path: Path +): + config_path = tmp_commitizen_project / "pyproject.toml" + config_path.write_text( + dedent( + """\ + [project] + name = "dev-project" + version = "0.1.0" + + [tool.commitizen] + version_provider = "uv" + update_changelog_on_bump = true + changelog_incremental = true + """ + ), + encoding="utf-8", + ) + lock_path = tmp_commitizen_project / "uv.lock" + lock_path.write_text( + dedent( + """\ + version = 1 + revision = 1 + + [[package]] + name = "dev-project" + version = "0.1.0" + source = { virtual = "." } + """ + ), + encoding="utf-8", + ) + + util.create_file_and_commit("feat: first development release") + util.run_cli("bump", "--devrelease", "0", "--yes") + assert git.tag_exist("0.2.0.dev0") is True + + util.create_file_and_commit("feat: next development release") + util.run_cli("bump", "--devrelease", "1", "--yes") + assert git.tag_exist("0.2.0.dev1") is True + changelog = changelog_path.read_text(encoding="utf-8") + assert "0.2.0.dev1" in changelog + assert "next development release" in changelog + assert 'version = "0.2.0.dev1"' in config_path.read_text(encoding="utf-8") + assert 'version = "0.2.0.dev1"' in lock_path.read_text(encoding="utf-8") + + @pytest.mark.usefixtures("tmp_commitizen_project") def test_bump_command_prerelease_increment(util: UtilFixture): # FINAL RELEASE diff --git a/tests/test_tags.py b/tests/test_tags.py index 3d5f8fc35..eaf851eb5 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -140,3 +140,25 @@ def test_is_version_tag_accepts_dotless_devrelease_in_custom_tag_format(): extracted = rules.extract_version(_git_tag("version-1.2.3dev1")) assert str(extracted) == "1.2.3.dev1" + + +def test_is_version_tag_accepts_pep440_devrelease(): + rules = TagRules() + + versions = [ + ("0.2.0.dev0", "0.2.0.dev0"), + ("0.2.0dev0", "0.2.0.dev0"), + ("0.2.0.dev0+build.1", "0.2.0.dev0+build.1"), + ("0.2.0a1.dev0", "0.2.0a1.dev0"), + ("0.2.0a1.dev0+build.1", "0.2.0a1.dev0+build.1"), + ] + for tag, expected in versions: + assert rules.is_version_tag(tag) is True + assert str(rules.extract_version(_git_tag(tag))) == expected + + assert rules.is_version_tag("0.2.0.not-a-release") is False + + custom_rules = TagRules(tag_format="release-$version-final") + custom_tag = _git_tag("release-0.2.0.dev0-final") + assert custom_rules.is_version_tag(custom_tag) is True + assert str(custom_rules.extract_version(custom_tag)) == "0.2.0.dev0"