From 6fb977ca9144a590153e779c59c4c788efd1442f Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sat, 8 Mar 2025 07:42:22 -0600 Subject: [PATCH 1/3] Replace `list[str]` with `List[str]` for Python 3.8+ compatibility. Updated all instances of `list[str]` with the generic `List[str]` from the `typing` module to maintain compatibility with older Python versions (3.8 and earlier). This ensures consistent type annotations across the codebase. Fixes #313 --- bumpversion/config/models.py | 2 +- bumpversion/scm/git.py | 12 ++++++------ bumpversion/scm/hg.py | 10 +++++----- bumpversion/scm/models.py | 12 ++++++------ tools/drawioexport.py | 2 +- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bumpversion/config/models.py b/bumpversion/config/models.py index 6cac6688..1927acf6 100644 --- a/bumpversion/config/models.py +++ b/bumpversion/config/models.py @@ -101,7 +101,7 @@ class Config(BaseSettings): pep621_info: Optional[PEP621Info] scm_info: Optional[SCMInfo] parts: Dict[str, VersionComponentSpec] - moveable_tags: list[str] = Field(default_factory=list) + moveable_tags: List[str] = Field(default_factory=list) files: List[FileChange] = Field(default_factory=list) setup_hooks: List[str] = Field(default_factory=list) pre_commit_hooks: List[str] = Field(default_factory=list) diff --git a/bumpversion/scm/git.py b/bumpversion/scm/git.py index e023aaf6..4f715598 100644 --- a/bumpversion/scm/git.py +++ b/bumpversion/scm/git.py @@ -6,7 +6,7 @@ import subprocess from pathlib import Path from tempfile import NamedTemporaryFile -from typing import Any, ClassVar, MutableMapping, Optional +from typing import Any, ClassVar, List, MutableMapping, Optional from bumpversion.exceptions import DirtyWorkingDirectoryError from bumpversion.scm.models import LatestTagInfo, SCMConfig @@ -19,9 +19,9 @@ class Git: """Git implementation.""" - _TEST_AVAILABLE_COMMAND: ClassVar[list[str]] = ["git", "rev-parse", "--git-dir"] - _COMMIT_COMMAND: ClassVar[list[str]] = ["git", "commit", "-F"] - _ALL_TAGS_COMMAND: ClassVar[list[str]] = ["git", "tag", "--list"] + _TEST_AVAILABLE_COMMAND: ClassVar[List[str]] = ["git", "rev-parse", "--git-dir"] + _COMMIT_COMMAND: ClassVar[List[str]] = ["git", "commit", "-F"] + _ALL_TAGS_COMMAND: ClassVar[List[str]] = ["git", "tag", "--list"] def __init__(self, config: SCMConfig): self.config = config @@ -73,7 +73,7 @@ def add_path(self, path: Pathlike) -> None: except subprocess.CalledProcessError as e: format_and_raise_error(e) - def get_all_tags(self) -> list[str]: + def get_all_tags(self) -> List[str]: """Return all tags in git.""" try: result = run_command(self._ALL_TAGS_COMMAND) @@ -86,7 +86,7 @@ def get_all_tags(self) -> list[str]: ): return [] - def commit_and_tag(self, files: list[Pathlike], context: MutableMapping, dry_run: bool = False) -> None: + def commit_and_tag(self, files: List[Pathlike], context: MutableMapping, dry_run: bool = False) -> None: """Commit and tag files to the repository using the configuration.""" if dry_run: return diff --git a/bumpversion/scm/hg.py b/bumpversion/scm/hg.py index 7a9718b2..d091b9f5 100644 --- a/bumpversion/scm/hg.py +++ b/bumpversion/scm/hg.py @@ -1,7 +1,7 @@ """Mercurial source control management.""" import subprocess -from typing import ClassVar, MutableMapping, Optional +from typing import ClassVar, List, MutableMapping, Optional from bumpversion.exceptions import DirtyWorkingDirectoryError, SignedTagsError from bumpversion.scm.models import LatestTagInfo, SCMConfig @@ -14,9 +14,9 @@ class Mercurial: """Mercurial source control management.""" - _TEST_AVAILABLE_COMMAND: ClassVar[list[str]] = ["hg", "root"] - _COMMIT_COMMAND: ClassVar[list[str]] = ["hg", "commit", "--logfile"] - _ALL_TAGS_COMMAND: ClassVar[list[str]] = ["hg", "log", '--rev="tag()"', '--template="{tags}\n"'] + _TEST_AVAILABLE_COMMAND: ClassVar[List[str]] = ["hg", "root"] + _COMMIT_COMMAND: ClassVar[List[str]] = ["hg", "commit", "--logfile"] + _ALL_TAGS_COMMAND: ClassVar[List[str]] = ["hg", "log", '--rev="tag()"', '--template="{tags}\n"'] def __init__(self, config: SCMConfig): self.config = config @@ -54,7 +54,7 @@ def add_path(self, path: Pathlike) -> None: """Add a path to the Source Control Management repository.""" pass - def commit_and_tag(self, files: list[Pathlike], context: MutableMapping, dry_run: bool = False) -> None: + def commit_and_tag(self, files: List[Pathlike], context: MutableMapping, dry_run: bool = False) -> None: """Commit and tag files to the repository using the configuration.""" def tag(self, name: str, sign: bool = False, message: Optional[str] = None) -> None: diff --git a/bumpversion/scm/models.py b/bumpversion/scm/models.py index 0e247c4e..763db599 100644 --- a/bumpversion/scm/models.py +++ b/bumpversion/scm/models.py @@ -88,11 +88,11 @@ def add_path(self, path: Pathlike) -> None: """Add a path to the pending commit.""" ... - def get_all_tags(self) -> list[str]: + def get_all_tags(self) -> List[str]: """Return all tags in the SCM.""" ... - def commit_and_tag(self, files: list[Pathlike], context: MutableMapping, dry_run: bool = False) -> None: + def commit_and_tag(self, files: List[Pathlike], context: MutableMapping, dry_run: bool = False) -> None: """Commit and tag files to the repository using the configuration.""" ... @@ -132,11 +132,11 @@ def add_path(self, path: Pathlike) -> None: """Add a path to the pending commit.""" logger.debug("No source code management system configured. Skipping adding path '%s'.", path) - def get_all_tags(self) -> list[str]: + def get_all_tags(self) -> List[str]: """Return all tags in the SCM.""" return [] - def commit_and_tag(self, files: list[Pathlike], context: MutableMapping, dry_run: bool = False) -> None: + def commit_and_tag(self, files: List[Pathlike], context: MutableMapping, dry_run: bool = False) -> None: """Pretend to commit and tag files to the repository using the configuration.""" logger.debug("No source code management system configured. Skipping committing and tagging.") @@ -210,7 +210,7 @@ def path_in_repo(self, path: Pathlike) -> bool: return True return str(path).startswith(str(self.repository_root)) - def commit_and_tag(self, files: list[Pathlike], context: MutableMapping, dry_run: bool = False) -> None: + def commit_and_tag(self, files: List[Pathlike], context: MutableMapping, dry_run: bool = False) -> None: """Commit the files to the source code management system.""" logger.indent() @@ -227,7 +227,7 @@ def commit_and_tag(self, files: list[Pathlike], context: MutableMapping, dry_run self.tool.commit_and_tag(files=files, context=context, dry_run=dry_run) logger.dedent() - def _commit(self, files: list[Pathlike], context: MutableMapping, dry_run: bool = False) -> None: + def _commit(self, files: List[Pathlike], context: MutableMapping, dry_run: bool = False) -> None: """Commit the files to the source code management system.""" do_commit = not dry_run logger.info("%s the commit", "Preparing" if do_commit else "Would prepare") diff --git a/tools/drawioexport.py b/tools/drawioexport.py index 5b94fed6..a5a1eb58 100644 --- a/tools/drawioexport.py +++ b/tools/drawioexport.py @@ -8,7 +8,7 @@ from typing import List, Optional -def get_executable_paths() -> list[str]: +def get_executable_paths() -> List[str]: """Get the Draw.io executable paths for the platform. Declared as a function to allow us to use API/environment information From 8b52174651e3c02daf3ba00166cd8f054498313d Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sat, 8 Mar 2025 08:20:54 -0600 Subject: [PATCH 2/3] Refactor and improve test structure for file modifications Consolidated and restructured tests for `modify_files` into classes for better organization and clarity. Fixed an issue where empty file configurations were not properly ignored and enhanced filtering logic in configuration handling. Fixes #312 --- bumpversion/config/models.py | 2 + bumpversion/config/utils.py | 2 +- tests/test_bump.py | 43 ++++ tests/test_files.py | 378 ++++++++++++++++++----------------- 4 files changed, 236 insertions(+), 189 deletions(-) diff --git a/bumpversion/config/models.py b/bumpversion/config/models.py index 1927acf6..fe0692ed 100644 --- a/bumpversion/config/models.py +++ b/bumpversion/config/models.py @@ -113,6 +113,8 @@ class Config(BaseSettings): def add_files(self, filename: Union[str, List[str]]) -> None: """Add a filename to the list of files.""" + if not self.files: + return filenames = [filename] if isinstance(filename, str) else filename files = set(self.files) for name in filenames: diff --git a/bumpversion/config/utils.py b/bumpversion/config/utils.py index 1f2eae6a..81461315 100644 --- a/bumpversion/config/utils.py +++ b/bumpversion/config/utils.py @@ -24,7 +24,7 @@ def get_all_file_configs(config_dict: dict) -> List[FileChange]: "regex": config_dict["regex"], "include_bumps": tuple(config_dict["parts"]), } - files = [{k: v for k, v in filecfg.items() if v is not None} for filecfg in config_dict["files"]] + files = [{k: v for k, v in filecfg.items() if v is not None} for filecfg in filter(None, config_dict["files"])] for f in files: f.update({k: v for k, v in defaults.items() if k not in f}) return [FileChange(**f) for f in files] diff --git a/tests/test_bump.py b/tests/test_bump.py index e06806c2..b60737a2 100644 --- a/tests/test_bump.py +++ b/tests/test_bump.py @@ -692,3 +692,46 @@ def test_changes_to_files_are_committed(git_repo: Path, caplog): with inside_dir(git_repo): status = run_command(["git", "status", "--porcelain"]) assert status.stdout == "" + + +def test_empty_files_config_is_ignored(git_repo: Path, caplog): + """An empty files config should be ignored.""" + # Arrange + config_path = git_repo / ".bumpversion.toml" + config_path.write_text( + dedent( + """ + [tool.bumpversion] + current_version = "0.1.26" + tag_name = "{new_version}" + commit = true + + [[tool.bumpversion.files]] + + """ + ), + encoding="utf-8", + ) + with inside_dir(git_repo): + run_command(["git", "add", str(config_path)]) + run_command(["git", "commit", "-m", "Initial commit"]) + run_command(["git", "tag", "0.1.26"]) + + # Act + runner: CliRunner = CliRunner() + with inside_dir(git_repo): + result: Result = runner.invoke( + cli.cli, + ["bump", "-vv", "minor"], + ) + + if result.exit_code != 0: + print(caplog.text) + print("Here is the output:") + print(result.output) + import traceback + + print(traceback.print_exception(*result.exc_info)) + + # Assert + assert result.exit_code == 0 diff --git a/tests/test_files.py b/tests/test_files.py index 7e86d6c9..a1774adc 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -18,27 +18,6 @@ from tests.conftest import get_config_data, inside_dir -def test_modify_files_raises_correct_missing_version_string(tmp_path: Path, fixtures_path: Path): - """When a file is missing the version string, the error should indicate the correct serialization missing.""" - csharp_path = fixtures_path / "csharp" - dst_path: Path = shutil.copytree(csharp_path, tmp_path / "csharp") - - with inside_dir(dst_path): - conf = config.get_configuration(config_file=dst_path.joinpath(".bumpversion.toml")) - version_config = VersionConfig(conf.parse, conf.serialize, conf.search, conf.replace, conf.parts) - current_version = version_config.parse(conf.current_version) - dst_path.joinpath("Version.csv").write_text("1;3-1;0;rc;build.1031", encoding="utf-8") - - major_version = current_version.bump("patch") - ctx = get_context(conf) - - configured_files = [files.ConfiguredFile(file_cfg, version_config) for file_cfg in conf.files] - - with pytest.raises(VersionNotFoundError) as e: - files.modify_files(configured_files, current_version, major_version, ctx) - assert e.message.startswith("Did not find '1;3;1;0;rc;build.1031'") - - def test_do_bump_uses_correct_serialization_for_tag_and_commit_messages(git_repo: Path, fixtures_path: Path, caplog): """The tag and commit messages should use the root configured serialization.""" import subprocess @@ -60,199 +39,222 @@ def test_do_bump_uses_correct_serialization_for_tag_and_commit_messages(git_repo assert "Bump version: 3.1.0-rc+build.1031 -> 3.1.1" in caplog.text -def test_modify_files_non_matching_search_does_not_modify_file(tmp_path: Path): - changelog_path = tmp_path / "CHANGELOG.md" - changelog_content = dedent( - """ - # Unreleased +class TestModifyFiles: + """Tests for the modify_files function.""" - * bullet point A + def test_raises_correct_missing_version_string(self, tmp_path: Path, fixtures_path: Path): + """When a file is missing the version string, the error should indicate the correct serialization missing.""" + csharp_path = fixtures_path / "csharp" + dst_path: Path = shutil.copytree(csharp_path, tmp_path / "csharp") - # Release v'older' (2019-09-17) + with inside_dir(dst_path): + conf = config.get_configuration(config_file=dst_path.joinpath(".bumpversion.toml")) + version_config = VersionConfig(conf.parse, conf.serialize, conf.search, conf.replace, conf.parts) + current_version = version_config.parse(conf.current_version) + dst_path.joinpath("Version.csv").write_text("1;3-1;0;rc;build.1031", encoding="utf-8") - * bullet point B - """ - ) - changelog_path.write_text(changelog_content, encoding="utf-8") - - overrides = { - "current_version": "1.0.3", - "files": [ - { - "filename": str(changelog_path), - "search": "Not-yet-released", - "replace": "Release v{new_version} ({now:%Y-%m-%d})", - } + major_version = current_version.bump("patch") + ctx = get_context(conf) + + configured_files = [files.ConfiguredFile(file_cfg, version_config) for file_cfg in conf.files] + + with pytest.raises(VersionNotFoundError) as e: + files.modify_files(configured_files, current_version, major_version, ctx) + assert e.message.startswith("Did not find '1;3;1;0;rc;build.1031'") + + def test_non_matching_search_does_not_modify_file(self, tmp_path: Path): + """If the search string is not found in the file, do nothing.""" + changelog_path = tmp_path / "CHANGELOG.md" + changelog_content = dedent( + """ + # Unreleased + + * bullet point A + + # Release v'older' (2019-09-17) + + * bullet point B + """ + ) + changelog_path.write_text(changelog_content, encoding="utf-8") + + overrides = { + "current_version": "1.0.3", + "files": [ + { + "filename": str(changelog_path), + "search": "Not-yet-released", + "replace": "Release v{new_version} ({now:%Y-%m-%d})", + } + ], + } + conf, version_config, current_version = get_config_data(overrides) + new_version = current_version.bump("patch") + configured_files = files.resolve_file_config(conf.files, version_config) + with pytest.raises(exceptions.VersionNotFoundError, match="Did not find 'Not-yet-released' in file:"): + files.modify_files(configured_files, current_version, new_version, get_context(conf)) + + assert changelog_path.read_text() == changelog_content + + @pytest.mark.parametrize( + ["global_value", "file_value", "should_raise"], + [ + param(True, True, False, id="ignore global and file"), + param(True, False, True, id="ignore global only"), + param(False, True, False, id="ignore file only"), + param(False, False, True, id="ignore none"), ], - } - conf, version_config, current_version = get_config_data(overrides) - new_version = current_version.bump("patch") - configured_files = files.resolve_file_config(conf.files, version_config) - with pytest.raises(exceptions.VersionNotFoundError, match="Did not find 'Not-yet-released' in file:"): - files.modify_files(configured_files, current_version, new_version, get_context(conf)) - - assert changelog_path.read_text() == changelog_content - - -@pytest.mark.parametrize( - ["global_value", "file_value", "should_raise"], - [ - param(True, True, False, id="ignore global and file"), - param(True, False, True, id="ignore global only"), - param(False, True, False, id="ignore file only"), - param(False, False, True, id="ignore none"), - ], -) -def test_modify_files_ignore_missing_version( - global_value: bool, file_value: bool, should_raise: bool, tmp_path: Path -) -> None: - """If the version is not found in the file, do nothing.""" - # Arrange - version_path = tmp_path / Path("VERSION") - version_path.write_text("1.2.3", encoding="utf-8") - - overrides = { - "current_version": "1.2.5", - "ignore_missing_version": global_value, - "files": [{"filename": str(version_path), "ignore_missing_version": file_value}], - } - with inside_dir(tmp_path): + ) + def test_ignore_missing_version_ignores_correctly( + self, global_value: bool, file_value: bool, should_raise: bool, tmp_path: Path + ) -> None: + """Evaluate the various configurations of ignore_missing_version.""" + # Arrange + version_path = tmp_path / Path("VERSION") + version_path.write_text("1.2.3", encoding="utf-8") + + overrides = { + "current_version": "1.2.5", + "ignore_missing_version": global_value, + "files": [{"filename": str(version_path), "ignore_missing_version": file_value}], + } + with inside_dir(tmp_path): + conf, version_config, current_version = get_config_data(overrides) + new_version = current_version.bump("patch") + cfg_files = [files.ConfiguredFile(file_cfg, version_config) for file_cfg in conf.files] + + # Act + if should_raise: + with pytest.raises(VersionNotFoundError): + files.modify_files(cfg_files, current_version, new_version, get_context(conf)) + else: + files.modify_files(cfg_files, current_version, new_version, get_context(conf)) + + # Assert + assert version_path.read_text() == "1.2.3" + + +class TestModifyFilesRegex: + """Tests of regular expression search and replace in modify_files.""" + + def test_regex_search_string_is_replaced(self, tmp_path: Path) -> None: + """A regex search string is found and replaced.""" + # Arrange + version_path = tmp_path / "VERSION" + version_path.write_text("Release: 1234-56-78 '1.2.3'", encoding="utf-8") + + overrides = { + "regex": True, + "current_version": "1.2.3", + "search": r"Release: \d{{4}}-\d{{2}}-\d{{2}} '{current_version}'", + "replace": r"Release {now:%Y-%m-%d} '{new_version}'", + "files": [{"filename": str(version_path)}], + } conf, version_config, current_version = get_config_data(overrides) new_version = current_version.bump("patch") cfg_files = [files.ConfiguredFile(file_cfg, version_config) for file_cfg in conf.files] # Act - if should_raise: - with pytest.raises(VersionNotFoundError): - files.modify_files(cfg_files, current_version, new_version, get_context(conf)) - else: - files.modify_files(cfg_files, current_version, new_version, get_context(conf)) + files.modify_files(cfg_files, current_version, new_version, get_context(conf)) - # Assert - assert version_path.read_text() == "1.2.3" - - -def test_modify_files_regex_search(tmp_path: Path) -> None: - """A regex search string is found and replaced.""" - # Arrange - version_path = tmp_path / "VERSION" - version_path.write_text("Release: 1234-56-78 '1.2.3'", encoding="utf-8") - - overrides = { - "regex": True, - "current_version": "1.2.3", - "search": r"Release: \d{{4}}-\d{{2}}-\d{{2}} '{current_version}'", - "replace": r"Release {now:%Y-%m-%d} '{new_version}'", - "files": [{"filename": str(version_path)}], - } - conf, version_config, current_version = get_config_data(overrides) - new_version = current_version.bump("patch") - cfg_files = [files.ConfiguredFile(file_cfg, version_config) for file_cfg in conf.files] - - # Act - files.modify_files(cfg_files, current_version, new_version, get_context(conf)) - - # Assert - now = datetime.now().isoformat()[:10] - assert version_path.read_text() == f"Release {now} '1.2.4'" - - -def test_modify_files_regex_search_with_escaped_chars(tmp_path: Path) -> None: - """A search that uses special characters is not treated as a regex.""" - # Arrange - version_path = tmp_path / "VERSION" - version_path.write_text("## [Release] 1.2.3 1234-56-78", encoding="utf-8") - - overrides = { - "regex": True, - "current_version": "1.2.3", - "search": r"## \[Release\] {current_version} \d{{4}}-\d{{2}}-\d{{2}}", - "replace": r"## [Release] {new_version} {now:%Y-%m-%d}", - "files": [{"filename": str(version_path)}], - } - conf, version_config, current_version = get_config_data(overrides) - new_version = current_version.bump("patch") - cfg_files = [files.ConfiguredFile(file_cfg, version_config) for file_cfg in conf.files] - - # Act - files.modify_files(cfg_files, current_version, new_version, get_context(conf)) - - # Assert - now = datetime.now().isoformat()[:10] - assert version_path.read_text() == f"## [Release] 1.2.4 {now}" - - -def test_modify_files_regex_search_in_toml(tmp_path: Path, fixtures_path: Path) -> None: - """Tests how-to doc 'update-a-date.md'.""" - # Arrange - version_path = tmp_path / "VERSION" - version_path.write_text("__date__ = '1234-56-78'\n__version__ = '1.2.3'", encoding="utf-8") - config_path = tmp_path / ".bumpversion.toml" - shutil.copyfile(fixtures_path / "replace-date-config.toml", config_path) - with inside_dir(tmp_path): - conf = config.get_configuration(config_file=config_path) - version_config = VersionConfig(conf.parse, conf.serialize, conf.search, conf.replace, conf.parts) - current_version = version_config.parse(conf.current_version) + # Assert + now = datetime.now().isoformat()[:10] + assert version_path.read_text() == f"Release {now} '1.2.4'" + def test_escaped_chars_are_respected(self, tmp_path: Path) -> None: + """A search that uses escaped characters is respected in a regex search and replaces.""" + # Arrange + version_path = tmp_path / "VERSION" + version_path.write_text("## [Release] 1.2.3 1234-56-78", encoding="utf-8") + + overrides = { + "regex": True, + "current_version": "1.2.3", + "search": r"## \[Release\] {current_version} \d{{4}}-\d{{2}}-\d{{2}}", + "replace": r"## [Release] {new_version} {now:%Y-%m-%d}", + "files": [{"filename": str(version_path)}], + } + conf, version_config, current_version = get_config_data(overrides) new_version = current_version.bump("patch") cfg_files = [files.ConfiguredFile(file_cfg, version_config) for file_cfg in conf.files] # Act files.modify_files(cfg_files, current_version, new_version, get_context(conf)) - # Assert - now = datetime.now().isoformat()[:10] - assert version_path.read_text() == f"__date__ = '{now}'\n__version__ = '1.2.4'" + # Assert + now = datetime.now().isoformat()[:10] + assert version_path.read_text() == f"## [Release] 1.2.4 {now}" + def test_search_in_toml(self, tmp_path: Path, fixtures_path: Path) -> None: + """Tests how-to doc 'update-a-date.md'.""" + # Arrange + version_path = tmp_path / "VERSION" + version_path.write_text("__date__ = '1234-56-78'\n__version__ = '1.2.3'", encoding="utf-8") + config_path = tmp_path / ".bumpversion.toml" + shutil.copyfile(fixtures_path / "replace-date-config.toml", config_path) + with inside_dir(tmp_path): + conf = config.get_configuration(config_file=config_path) + version_config = VersionConfig(conf.parse, conf.serialize, conf.search, conf.replace, conf.parts) + current_version = version_config.parse(conf.current_version) + + new_version = current_version.bump("patch") + cfg_files = [files.ConfiguredFile(file_cfg, version_config) for file_cfg in conf.files] + + # Act + files.modify_files(cfg_files, current_version, new_version, get_context(conf)) -def test_modify_files_regex_search_with_caret(tmp_path: Path, fixtures_path: Path) -> None: - """A search that uses a caret to indicate the beginning of the line works correctly.""" - # Arrange - config_path = tmp_path / ".bumpversion.toml" - thingy_path = tmp_path / "thingy.yaml" - shutil.copyfile(fixtures_path / "regex_with_caret.yaml", thingy_path) - shutil.copyfile(fixtures_path / "regex_with_caret_config.toml", config_path) + # Assert + now = datetime.now().isoformat()[:10] + assert version_path.read_text() == f"__date__ = '{now}'\n__version__ = '1.2.4'" - conf = config.get_configuration(config_file=config_path) - version_config = VersionConfig(conf.parse, conf.serialize, conf.search, conf.replace, conf.parts) - current_version = version_config.parse(conf.current_version) - new_version = current_version.bump("patch") + def test_caret_matches_beginning_of_line(self, tmp_path: Path, fixtures_path: Path) -> None: + """A search that uses a caret to indicate the beginning of the line works correctly.""" + # Arrange + config_path = tmp_path / ".bumpversion.toml" + thingy_path = tmp_path / "thingy.yaml" + shutil.copyfile(fixtures_path / "regex_with_caret.yaml", thingy_path) + shutil.copyfile(fixtures_path / "regex_with_caret_config.toml", config_path) - with inside_dir(tmp_path): + conf = config.get_configuration(config_file=config_path) + version_config = VersionConfig(conf.parse, conf.serialize, conf.search, conf.replace, conf.parts) + current_version = version_config.parse(conf.current_version) + new_version = current_version.bump("patch") + + with inside_dir(tmp_path): + cfg_files = [files.ConfiguredFile(file_cfg, version_config) for file_cfg in conf.files] + + # Act + files.modify_files(cfg_files, current_version, new_version, get_context(conf)) + + # Assert + assert ( + thingy_path.read_text() + == "version: 1.0.1\ndependencies:\n- name: kube-prometheus-stack\n version: 1.0.0\n" + ) + + def test_bad_regex_falls_back_to_string_search(self, tmp_path: Path, caplog) -> None: + """A search string not meant to be a regex is still found and replaced.""" + # Arrange + version_path = tmp_path / "VERSION" + version_path.write_text("Score: A+ ( '1.2.3'", encoding="utf-8") + + overrides = { + "regex": True, + "current_version": "1.2.3", + "search": r"Score: A+ ( '{current_version}'", + "replace": r"Score: A+ ( '{new_version}'", + "files": [{"filename": str(version_path)}], + } + conf, version_config, current_version = get_config_data(overrides) + new_version = current_version.bump("patch") cfg_files = [files.ConfiguredFile(file_cfg, version_config) for file_cfg in conf.files] # Act files.modify_files(cfg_files, current_version, new_version, get_context(conf)) - # Assert - assert ( - thingy_path.read_text() == "version: 1.0.1\ndependencies:\n- name: kube-prometheus-stack\n version: 1.0.0\n" - ) - - -def test_modify_files_bad_regex_search(tmp_path: Path, caplog) -> None: - """A search string not meant to be a regex is still found and replaced.""" - # Arrange - version_path = tmp_path / "VERSION" - version_path.write_text("Score: A+ ( '1.2.3'", encoding="utf-8") - - overrides = { - "regex": True, - "current_version": "1.2.3", - "search": r"Score: A+ ( '{current_version}'", - "replace": r"Score: A+ ( '{new_version}'", - "files": [{"filename": str(version_path)}], - } - conf, version_config, current_version = get_config_data(overrides) - new_version = current_version.bump("patch") - cfg_files = [files.ConfiguredFile(file_cfg, version_config) for file_cfg in conf.files] - - # Act - files.modify_files(cfg_files, current_version, new_version, get_context(conf)) - - # Assert - assert version_path.read_text() == "Score: A+ ( '1.2.4'" - assert "Invalid regex" in caplog.text + # Assert + assert version_path.read_text() == "Score: A+ ( '1.2.4'" + assert "Invalid regex" in caplog.text class TestDataFileUpdater: From 3578c872ef8143f11c22cb5e83765c6e69cf3eef Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sat, 8 Mar 2025 08:36:59 -0600 Subject: [PATCH 3/3] Fix incorrect evaluation. The check for valid files to add should be `filename`, not `self.files` --- bumpversion/config/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bumpversion/config/models.py b/bumpversion/config/models.py index fe0692ed..aee8fe46 100644 --- a/bumpversion/config/models.py +++ b/bumpversion/config/models.py @@ -113,7 +113,7 @@ class Config(BaseSettings): def add_files(self, filename: Union[str, List[str]]) -> None: """Add a filename to the list of files.""" - if not self.files: + if not filename: return filenames = [filename] if isinstance(filename, str) else filename files = set(self.files)