From 752a6a68fd18f44245772299739d24f509b32c14 Mon Sep 17 00:00:00 2001 From: "Axel H." Date: Wed, 17 Apr 2024 18:58:58 +0200 Subject: [PATCH] feat(bump): `version_files` now support glob patterns (fix #1067) --- commitizen/bump.py | 39 +++++++---- commitizen/commands/bump.py | 65 +++++++------------ commitizen/commands/changelog.py | 6 +- commitizen/git.py | 4 +- docs/bump.md | 4 ++ tests/commands/test_bump_command.py | 10 +++ tests/test_bump_update_version_in_files.py | 18 +++++ .../test_update_version_in_globbed_files.toml | 3 + 8 files changed, 92 insertions(+), 57 deletions(-) create mode 100644 tests/test_bump_update_version_in_files/test_update_version_in_globbed_files.toml diff --git a/commitizen/bump.py b/commitizen/bump.py index f0e45e3432..2351dbd7ec 100644 --- a/commitizen/bump.py +++ b/commitizen/bump.py @@ -3,6 +3,7 @@ import os import re from collections import OrderedDict +from glob import iglob from string import Template from typing import cast @@ -53,23 +54,20 @@ def update_version_in_files( *, check_consistency: bool = False, encoding: str = encoding, -) -> None: +) -> list[str]: """Change old version to the new one in every file given. Note that this version is not the tag formatted one. So for example, your tag could look like `v1.0.0` while your version in the package like `1.0.0`. + + Returns the list of updated files. """ # TODO: separate check step and write step - for location in files: - drive, tail = os.path.splitdrive(location) - path, _, regex = tail.partition(":") - filepath = drive + path - if not regex: - regex = _version_to_regex(current_version) - + updated = [] + for path, regex in files_and_regexs(files, current_version): current_version_found, version_file = _bump_with_regex( - filepath, + path, current_version, new_version, regex, @@ -78,14 +76,33 @@ def update_version_in_files( if check_consistency and not current_version_found: raise CurrentVersionNotFoundError( - f"Current version {current_version} is not found in {location}.\n" + f"Current version {current_version} is not found in {path}.\n" "The version defined in commitizen configuration and the ones in " "version_files are possibly inconsistent." ) # Write the file out again - with smart_open(filepath, "w", encoding=encoding) as file: + with smart_open(path, "w", encoding=encoding) as file: file.write(version_file) + updated.append(path) + return updated + + +def files_and_regexs(patterns: list[str], version: str) -> list[tuple[str, str]]: + """ + Resolve all distinct files with their regexp from a list of glob patterns with optional regexp + """ + out = [] + for pattern in patterns: + drive, tail = os.path.splitdrive(pattern) + path, _, regex = tail.partition(":") + filepath = drive + path + if not regex: + regex = _version_to_regex(version) + + for path in iglob(filepath): + out.append((path, regex)) + return sorted(list(set(out))) def _bump_with_regex( diff --git a/commitizen/commands/bump.py b/commitizen/commands/bump.py index efbe1e0c3c..554e3c800b 100644 --- a/commitizen/commands/bump.py +++ b/commitizen/commands/bump.py @@ -1,12 +1,11 @@ from __future__ import annotations -import os import warnings from logging import getLogger import questionary -from commitizen import bump, cmd, factory, git, hooks, out +from commitizen import bump, factory, git, hooks, out from commitizen.commands.changelog import Changelog from commitizen.config import BaseConfig from commitizen.exceptions import ( @@ -286,56 +285,39 @@ def __call__(self) -> None: # noqa: C901 "The commits found are not eligible to be bumped" ) + files: list[str] = [] if self.changelog: + args = { + "unreleased_version": new_tag_version, + "template": self.template, + "extras": self.extras, + "incremental": True, + "dry_run": dry_run, + } if self.changelog_to_stdout: - changelog_cmd = Changelog( - self.config, - { - "unreleased_version": new_tag_version, - "template": self.template, - "extras": self.extras, - "incremental": True, - "dry_run": True, - }, - ) + changelog_cmd = Changelog(self.config, {**args, "dry_run": True}) try: changelog_cmd() except DryRunExit: pass - changelog_cmd = Changelog( - self.config, - { - "unreleased_version": new_tag_version, - "incremental": True, - "dry_run": dry_run, - "template": self.template, - "extras": self.extras, - "file_name": self.file_name, - }, - ) + + args["file_name"] = self.file_name + changelog_cmd = Changelog(self.config, args) changelog_cmd() - file_names = [] - for file_name in version_files: - drive, tail = os.path.splitdrive(file_name) - path, _, regex = tail.partition(":") - path = drive + path if path != "" else drive + regex - file_names.append(path) - git_add_changelog_and_version_files_command = ( - f"git add {changelog_cmd.file_name} " - f"{' '.join(name for name in file_names)}" - ) - c = cmd.run(git_add_changelog_and_version_files_command) + files.append(changelog_cmd.file_name) # Do not perform operations over files or git. if dry_run: raise DryRunExit() - bump.update_version_in_files( - str(current_version), - str(new_version), - version_files, - check_consistency=self.check_consistency, - encoding=self.encoding, + files.extend( + bump.update_version_in_files( + str(current_version), + str(new_version), + version_files, + check_consistency=self.check_consistency, + encoding=self.encoding, + ) ) provider.set_version(str(new_version)) @@ -358,12 +340,13 @@ def __call__(self) -> None: # noqa: C901 raise ExpectedExit() # FIXME: check if any changes have been staged + git.add(*files) c = git.commit(message, args=self._get_commit_args()) if self.retry and c.return_code != 0 and self.changelog: # Maybe pre-commit reformatted some files? Retry once logger.debug("1st git.commit error: %s", c.err) logger.info("1st commit attempt failed; retrying once") - cmd.run(git_add_changelog_and_version_files_command) + git.add(*files) c = git.commit(message, args=self._get_commit_args()) if c.return_code != 0: err = c.err.strip() or c.out diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index 3fc204eba9..f1b69598f1 100644 --- a/commitizen/commands/changelog.py +++ b/commitizen/commands/changelog.py @@ -4,7 +4,7 @@ from difflib import SequenceMatcher from operator import itemgetter from pathlib import Path -from typing import Callable +from typing import Callable, cast from commitizen import bump, changelog, defaults, factory, git, out @@ -38,8 +38,8 @@ def __init__(self, config: BaseConfig, args): self.start_rev = args.get("start_rev") or self.config.settings.get( "changelog_start_rev" ) - self.file_name = args.get("file_name") or self.config.settings.get( - "changelog_file" + self.file_name = args.get("file_name") or cast( + str, self.config.settings.get("changelog_file") ) if not isinstance(self.file_name, str): raise NotAllowed( diff --git a/commitizen/git.py b/commitizen/git.py index 53e7335a3f..1f758889ed 100644 --- a/commitizen/git.py +++ b/commitizen/git.py @@ -98,8 +98,8 @@ def tag( return c -def add(args: str = "") -> cmd.Command: - c = cmd.run(f"git add {args}") +def add(*args: str) -> cmd.Command: + c = cmd.run(f"git add {' '.join(args)}") return c diff --git a/docs/bump.md b/docs/bump.md index 0e35083b0a..05843eeab5 100644 --- a/docs/bump.md +++ b/docs/bump.md @@ -474,6 +474,10 @@ In the example above, we can see the reference `"setup.py:version"`. This means that it will find a file `setup.py` and will only make a change in a line containing the `version` substring. +!!! note + Files can be specified using relative (to the execution) paths, absolute paths + or glob patterns. + --- ### `bump_message` diff --git a/tests/commands/test_bump_command.py b/tests/commands/test_bump_command.py index 6cf8a8d00c..84faf9ee82 100644 --- a/tests/commands/test_bump_command.py +++ b/tests/commands/test_bump_command.py @@ -812,6 +812,16 @@ def test_bump_with_git_to_stdout_arg(mocker: MockFixture, capsys, changelog_path """, id="version in __init__.py with regex", ), + pytest.param( + "pyproject.toml", + "*.toml:^version", + """ +[tool.poetry] +name = "my_package" +version = "0.1.0" +""", + id="version in pyproject.toml with glob and regex", + ), ], ) @pytest.mark.parametrize( diff --git a/tests/test_bump_update_version_in_files.py b/tests/test_bump_update_version_in_files.py index 9a80d4cfd9..850b59c166 100644 --- a/tests/test_bump_update_version_in_files.py +++ b/tests/test_bump_update_version_in_files.py @@ -207,3 +207,21 @@ def test_multiplt_versions_to_bump( bump.update_version_in_files(old_version, new_version, [location], encoding="utf-8") with open(multiple_versions_to_update_poetry_lock, encoding="utf-8") as f: file_regression.check(f.read(), extension=".toml") + + +def test_update_version_in_globbed_files(commitizen_config_file, file_regression): + old_version = "1.2.3" + new_version = "2.0.0" + other = commitizen_config_file.dirpath("other.toml") + print(commitizen_config_file, other) + copyfile(commitizen_config_file, other) + + # Prepend full ppath as test assume absolute paths or cwd-relative + version_files = [commitizen_config_file.dirpath("*.toml")] + + bump.update_version_in_files( + old_version, new_version, version_files, encoding="utf-8" + ) + + for file in commitizen_config_file, other: + file_regression.check(file.read_text("utf-8"), extension=".toml") diff --git a/tests/test_bump_update_version_in_files/test_update_version_in_globbed_files.toml b/tests/test_bump_update_version_in_files/test_update_version_in_globbed_files.toml new file mode 100644 index 0000000000..bf82cfe859 --- /dev/null +++ b/tests/test_bump_update_version_in_files/test_update_version_in_globbed_files.toml @@ -0,0 +1,3 @@ +[tool.poetry] +name = "commitizen" +version = "2.0.0"