From 754d4982295300a4ed8e0fafdc497a7fe56fca3e Mon Sep 17 00:00:00 2001 From: Rishi Maharaj Date: Wed, 17 Jan 2024 09:47:08 -0800 Subject: [PATCH] feat(provider): add new maven provider for handling versions from pom.xml --- commitizen/commands/init.py | 7 +++ commitizen/providers/__init__.py | 2 + commitizen/providers/mvn_provider.py | 53 +++++++++++++++++++++ pyproject.toml | 1 + tests/data/sample_pom.xml | 69 ++++++++++++++++++++++++++++ tests/data/sample_pom_snapshot.xml | 69 ++++++++++++++++++++++++++++ tests/providers/test_mvn_provider.py | 50 ++++++++++++++++++++ 7 files changed, 251 insertions(+) create mode 100644 commitizen/providers/mvn_provider.py create mode 100644 tests/data/sample_pom.xml create mode 100644 tests/data/sample_pom_snapshot.xml create mode 100644 tests/providers/test_mvn_provider.py diff --git a/commitizen/commands/init.py b/commitizen/commands/init.py index 62b3aec971..39efdea814 100644 --- a/commitizen/commands/init.py +++ b/commitizen/commands/init.py @@ -46,6 +46,10 @@ def is_python(self) -> bool: def is_rust_cargo(self) -> bool: return os.path.isfile("Cargo.toml") + @property + def is_maven_project(self) -> bool: + return os.path.isfile("pom.xml") + @property def is_npm_package(self) -> bool: return os.path.isfile("package.json") @@ -222,6 +226,7 @@ def _ask_version_provider(self) -> str: "commitizen": "commitizen: Fetch and set version in commitizen config (default)", "cargo": "cargo: Get and set version from Cargo.toml:project.version field", "composer": "composer: Get and set version from composer.json:project.version field", + "mvn": "mvn: Get and set version from pom.xml:project.version field", "npm": "npm: Get and set version from package.json:project.version field", "pep621": "pep621: Get and set version from pyproject.toml:project.version field", "poetry": "poetry: Get and set version from pyproject.toml:tool.poetry.version field", @@ -236,6 +241,8 @@ def _ask_version_provider(self) -> str: default_val = "pep621" elif self.project_info.is_rust_cargo: default_val = "cargo" + elif self.project_info.is_maven_project: + default_val = "mvn" elif self.project_info.is_npm_package: default_val = "npm" elif self.project_info.is_php_composer: diff --git a/commitizen/providers/__init__.py b/commitizen/providers/__init__.py index 51302d2b37..5d992ee816 100644 --- a/commitizen/providers/__init__.py +++ b/commitizen/providers/__init__.py @@ -11,6 +11,7 @@ from commitizen.providers.cargo_provider import CargoProvider from commitizen.providers.commitizen_provider import CommitizenProvider from commitizen.providers.composer_provider import ComposerProvider +from commitizen.providers.mvn_provider import MavenProvider from commitizen.providers.npm_provider import NpmProvider from commitizen.providers.pep621_provider import Pep621Provider from commitizen.providers.poetry_provider import PoetryProvider @@ -21,6 +22,7 @@ "CargoProvider", "CommitizenProvider", "ComposerProvider", + "MavenProvider", "NpmProvider", "Pep621Provider", "PoetryProvider", diff --git a/commitizen/providers/mvn_provider.py b/commitizen/providers/mvn_provider.py new file mode 100644 index 0000000000..d7363914f6 --- /dev/null +++ b/commitizen/providers/mvn_provider.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +import subprocess + +from commitizen.providers.base_provider import VersionProvider + + +class MavenProvider(VersionProvider): + """ + Maven version management + + ref: https://octopus.com/blog/maven-versioning-explained + + Major.Minor.Patch-BuildNumber-Qualifier + + Precedence: + - alpha or a + - beta or b + - milestone or m + - rc or cr + - snapshot + - (the empty string) or ga or final or release + - sp + """ + + FULL_VERSION_REGEX = r"(?P\d+)\.(?P\d+)\.(?P\d+)[\.-]?(?P\d+)?[\.-]?(?P\w+)?" + + TAG_FORMAT_REGEXS = { + "$version": r"(?P.+)", + "$major": r"(?P\d+)", + "$minor": r"(?P\d+)", + "$patch": r"(?P\d+)", + "$buildnumber": r"(?P\d+)?", + "$qualifier": r"(?P\w+)?", + } + + filename = "./pom.xml" + + def __run_cmd(self, cmd) -> str: + return ( + subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) + .stdout.read() # type: ignore + .decode("utf-8") + .strip() + ) + + def get_version(self, file: str = filename) -> str: + return self.__run_cmd( + f"mvn help:evaluate -Dexpression=project.version -q -DforceStdout -f {file}" + ) + + def set_version(self, version: str, file: str = filename) -> None: + self.__run_cmd(f"mvn versions:set -DnewVersion={version} -f {file}") diff --git a/pyproject.toml b/pyproject.toml index b644753b1b..251a957f5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,6 +93,7 @@ restructuredtext = "commitizen.changelog_formats.restructuredtext:RestructuredTe cargo = "commitizen.providers:CargoProvider" commitizen = "commitizen.providers:CommitizenProvider" composer = "commitizen.providers:ComposerProvider" +mvn = "commitizen.providers:MavenProvider" npm = "commitizen.providers:NpmProvider" pep621 = "commitizen.providers:Pep621Provider" poetry = "commitizen.providers:PoetryProvider" diff --git a/tests/data/sample_pom.xml b/tests/data/sample_pom.xml new file mode 100644 index 0000000000..50b9f372bd --- /dev/null +++ b/tests/data/sample_pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + + + org.springframework.boot + spring-boot-starter-parent + 3.1.0 + + + + + io.github.commitizen-tools + sample-maven-pom + 3.2.1 + pom + + + Sample Maven POM + This is a sample POM + + + ${app.url} + v@{project.version} + + + + + + hthttps://github.com/commitizen-tools/commitizen + + + 17 + + + 3.1.0 + + + + + + + org.springframework.boot + spring-boot-devtools + ${spring-boot.version} + true + + + org.springframework.boot + spring-boot-starter-actuator + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-parent + ${spring-boot.version} + pom + import + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + + + diff --git a/tests/data/sample_pom_snapshot.xml b/tests/data/sample_pom_snapshot.xml new file mode 100644 index 0000000000..694683b0fa --- /dev/null +++ b/tests/data/sample_pom_snapshot.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + + + org.springframework.boot + spring-boot-starter-parent + 3.1.0 + + + + + io.github.commitizen-tools + sample-maven-pom + 3.2.1-SNAPSHOT + pom + + + Sample Maven POM + This is a sample POM + + + ${app.url} + v@{project.version} + + + + + + hthttps://github.com/commitizen-tools/commitizen + + + 17 + + + 3.1.0 + + + + + + + org.springframework.boot + spring-boot-devtools + ${spring-boot.version} + true + + + org.springframework.boot + spring-boot-starter-actuator + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-parent + ${spring-boot.version} + pom + import + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + + + diff --git a/tests/providers/test_mvn_provider.py b/tests/providers/test_mvn_provider.py new file mode 100644 index 0000000000..014831de6a --- /dev/null +++ b/tests/providers/test_mvn_provider.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import pytest + +import os + +from commitizen.config.base_config import BaseConfig +from commitizen.providers.mvn_provider import MavenProvider + + +def test_can_run_subcommand(config: BaseConfig): + provider = MavenProvider(config) + got = provider._MavenProvider__run_cmd("echo 'hi'") # type: ignore + expected = "hi" + assert got == expected + + +@pytest.mark.parametrize( + "file, expected", + ( + ("./tests/data/sample_pom.xml", "3.2.1"), + ("./tests/data/sample_pom_snapshot.xml", "3.2.1-SNAPSHOT"), + ), +) +def test_get_version(config: BaseConfig, file: str, expected: str): + provider = MavenProvider(config) + got = provider.get_version(file) + assert got == expected + + +def test_set_version(config: BaseConfig): + provider = MavenProvider(config) + file = "./tests/data/sample_pom.xml" + expected = "3.2.2" + provider.set_version(expected, file) + got = provider.get_version(file) + assert got == expected + + # rollback changes + expected = "3.2.1" + provider.set_version(expected, file) + got = provider.get_version(file) + assert got == expected + + # delete backup file created + backup_file = file + ".versionsBackup" + assert os.path.exists(backup_file) + if os.path.exists(backup_file): + os.remove(backup_file) + assert not os.path.exists(backup_file)