diff --git a/bumpversion/cli.py b/bumpversion/cli.py index 37064f58..daee9878 100644 --- a/bumpversion/cli.py +++ b/bumpversion/cli.py @@ -163,14 +163,14 @@ def cli(ctx: Context) -> None: ), ) @click.option( - "--ignore-missing-files", - is_flag=True, + "--ignore-missing-files/--no-ignore-missing-files", + default=None, envvar="BUMPVERSION_IGNORE_MISSING_FILES", help="Ignore any missing files when searching and replacing in files.", ) @click.option( - "--ignore-missing-version", - is_flag=True, + "--ignore-missing-version/--no-ignore-missing-version", + default=None, envvar="BUMPVERSION_IGNORE_MISSING_VERSION", help="Ignore any Version Not Found errors when searching and replacing in files.", ) diff --git a/bumpversion/config/__init__.py b/bumpversion/config/__init__.py index 98718d19..d1498094 100644 --- a/bumpversion/config/__init__.py +++ b/bumpversion/config/__init__.py @@ -17,7 +17,7 @@ DEFAULTS = { "current_version": None, "parse": r"(?P\d+)\.(?P\d+)\.(?P\d+)", - "serialize": ["{major}.{minor}.{patch}"], + "serialize": ("{major}.{minor}.{patch}",), "search": "{current_version}", "replace": "{new_version}", "regex": False, diff --git a/tests/test_config/test_create.py b/tests/test_config/test_create.py index 1e269565..b366fe22 100644 --- a/tests/test_config/test_create.py +++ b/tests/test_config/test_create.py @@ -105,7 +105,27 @@ def test_destination_without_prompt_returns_default_config(self, tmp_path: Path, default_config["current_version"] = "0.1.0" default_config["commit_args"] = "" - assert destination_config["tool"]["bumpversion"] == default_config + assert destination_config["tool"]["bumpversion"]["allow_dirty"] == default_config["allow_dirty"] + assert destination_config["tool"]["bumpversion"]["commit"] == default_config["commit"] + assert destination_config["tool"]["bumpversion"]["commit_args"] == default_config["commit_args"] + assert destination_config["tool"]["bumpversion"]["current_version"] == default_config["current_version"] + assert ( + destination_config["tool"]["bumpversion"]["ignore_missing_files"] == default_config["ignore_missing_files"] + ) + assert ( + destination_config["tool"]["bumpversion"]["ignore_missing_version"] + == default_config["ignore_missing_version"] + ) + assert destination_config["tool"]["bumpversion"]["message"] == default_config["message"] + assert destination_config["tool"]["bumpversion"]["parse"] == default_config["parse"] + assert destination_config["tool"]["bumpversion"]["regex"] == default_config["regex"] + assert destination_config["tool"]["bumpversion"]["replace"] == default_config["replace"] + assert destination_config["tool"]["bumpversion"]["search"] == default_config["search"] + assert destination_config["tool"]["bumpversion"]["serialize"] == list(default_config["serialize"]) + assert destination_config["tool"]["bumpversion"]["sign_tags"] == default_config["sign_tags"] + assert destination_config["tool"]["bumpversion"]["tag"] == default_config["tag"] + assert destination_config["tool"]["bumpversion"]["tag_message"] == default_config["tag_message"] + assert destination_config["tool"]["bumpversion"]["tag_name"] == default_config["tag_name"] def test_prompt_true_asks_questions_and_updates_result(self, tmp_path: Path, default_config: dict, mocker): """If prompt is True, the user should be prompted for the configuration.""" @@ -123,5 +143,25 @@ def test_prompt_true_asks_questions_and_updates_result(self, tmp_path: Path, def default_config.update(answer) - assert destination_config["tool"]["bumpversion"] == default_config + assert destination_config["tool"]["bumpversion"]["allow_dirty"] == default_config["allow_dirty"] + assert destination_config["tool"]["bumpversion"]["commit"] == default_config["commit"] + assert destination_config["tool"]["bumpversion"]["commit_args"] == default_config["commit_args"] + assert destination_config["tool"]["bumpversion"]["current_version"] == default_config["current_version"] + assert ( + destination_config["tool"]["bumpversion"]["ignore_missing_files"] == default_config["ignore_missing_files"] + ) + assert ( + destination_config["tool"]["bumpversion"]["ignore_missing_version"] + == default_config["ignore_missing_version"] + ) + assert destination_config["tool"]["bumpversion"]["message"] == default_config["message"] + assert destination_config["tool"]["bumpversion"]["parse"] == default_config["parse"] + assert destination_config["tool"]["bumpversion"]["regex"] == default_config["regex"] + assert destination_config["tool"]["bumpversion"]["replace"] == default_config["replace"] + assert destination_config["tool"]["bumpversion"]["search"] == default_config["search"] + assert destination_config["tool"]["bumpversion"]["serialize"] == list(default_config["serialize"]) + assert destination_config["tool"]["bumpversion"]["sign_tags"] == default_config["sign_tags"] + assert destination_config["tool"]["bumpversion"]["tag"] == default_config["tag"] + assert destination_config["tool"]["bumpversion"]["tag_message"] == default_config["tag_message"] + assert destination_config["tool"]["bumpversion"]["tag_name"] == default_config["tag_name"] assert mocked_questionary.form.return_value.ask.call_count == 1 diff --git a/tests/test_config/test_utils.py b/tests/test_config/test_utils.py new file mode 100644 index 00000000..2e3d8551 --- /dev/null +++ b/tests/test_config/test_utils.py @@ -0,0 +1,115 @@ +"""Tests of the configuration utilities.""" + +from pathlib import Path +from typing import Any + +import tomlkit +import pytest +from pytest import param + +from bumpversion.config.utils import get_all_file_configs, get_all_part_configs, resolve_glob_files +from bumpversion.config.models import FileChange +from bumpversion.config import DEFAULTS +from tests.conftest import inside_dir + + +def write_config(tmp_path: Path, overrides: dict) -> Path: + """Write a configuration file.""" + config_file = tmp_path / ".bumpversion.toml" + defaults = DEFAULTS.copy() + defaults.pop("parts") + defaults.pop("scm_info") + defaults["current_version"] = defaults["current_version"] or "1.2.3" + defaults["commit_args"] = "" + config = {"tool": {"bumpversion": {**defaults, **overrides}}} + config_file.write_text(tomlkit.dumps(config)) + return config_file + + +class TestGetAllFileConfigs: + """Tests for the get_all_file_configs function.""" + + def test_uses_defaults_for_missing_keys(self, tmp_path: Path): + """Test the get_all_file_configs function.""" + config_file = write_config(tmp_path, {"files": [{"filename": "setup.cfg"}]}) + config_dict = tomlkit.loads(config_file.read_text())["tool"]["bumpversion"] + file_configs = get_all_file_configs(config_dict) + + for key in FileChange.model_fields.keys(): + global_key = key if key != "ignore_missing_file" else "ignore_missing_files" + if key not in ["filename", "glob", "key_path"]: + file_val = getattr(file_configs[0], key) + assert file_val == DEFAULTS[global_key] + + @pytest.mark.parametrize( + ["attribute", "global_value", "file_value"], + [ + param("parse", DEFAULTS["parse"], r"v(?P\d+)", id="parse"), + param("serialize", DEFAULTS["serialize"], ("v{major}",), id="serialize"), + param("search", DEFAULTS["search"], "v{current_version}", id="search"), + param("replace", DEFAULTS["replace"], "v{new_version}", id="replace"), + param("ignore_missing_version", True, False, id="ignore_missing_version"), + param("ignore_missing_files", True, False, id="ignore_missing_files"), + param("regex", True, False, id="regex"), + ], + ) + def test_overrides_defaults(self, tmp_path: Path, attribute: str, global_value: Any, file_value: Any): + """Configured attributes in the file should override global options.""" + file_attribute = attribute if attribute != "ignore_missing_files" else "ignore_missing_file" + config_file = write_config( + tmp_path, + { + attribute: global_value, + "files": [ + { + "filename": "setup.cfg", + file_attribute: file_value, + } + ], + }, + ) + config_dict = tomlkit.loads(config_file.read_text())["tool"]["bumpversion"] + file_configs = get_all_file_configs(config_dict) + assert len(file_configs) == 1 + assert file_configs[0].filename == "setup.cfg" + assert getattr(file_configs[0], file_attribute) == file_value + + +class TestResolveGlobFiles: + """Tests for the resolve_glob_files function.""" + + def test_all_attributes_are_copied(self, tmp_path: Path): + """Test that all attributes are copied.""" + file1 = tmp_path.joinpath("setup.cfg") + file2 = tmp_path.joinpath("subdir/setup.cfg") + file1.touch() + file2.parent.mkdir() + file2.touch() + + file_cfg = FileChange( + filename=None, + glob="**/*.cfg", + key_path=None, + parse=r"v(?P\d+)", + serialize=("v{major}",), + search="v{current_version}", + replace="v{new_version}", + ignore_missing_version=True, + ignore_missing_file=True, + regex=True, + ) + with inside_dir(tmp_path): + resolved_files = resolve_glob_files(file_cfg) + + assert len(resolved_files) == 2 + for resolved_file in resolved_files: + assert resolved_file.filename.endswith("setup.cfg") + assert resolved_file.glob is None + assert resolved_file.key_path is None + assert resolved_file.parse == r"v(?P\d+)" + assert resolved_file.serialize == ("v{major}",) + assert resolved_file.search == "v{current_version}" + assert resolved_file.replace == "v{new_version}" + assert resolved_file.ignore_missing_version is True + assert resolved_file.ignore_missing_file is True + assert resolved_file.regex is True