From 304c59985d5f71f44754615fee2ded600de237b0 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 15 Aug 2024 14:39:22 -0500 Subject: [PATCH 1/9] Add current_tag field to scm_info Updated the scm_info structure to include a new field, current_tag, across various configuration files and source code. This ensures that the current tag is tracked and represented in the output formats correctly. --- bumpversion/scm.py | 5 ++++- tests/fixtures/basic_cfg_expected.txt | 1 + tests/fixtures/basic_cfg_expected.yaml | 1 + tests/fixtures/basic_cfg_expected_full.json | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bumpversion/scm.py b/bumpversion/scm.py index c8cd6d83..ae540248 100644 --- a/bumpversion/scm.py +++ b/bumpversion/scm.py @@ -27,6 +27,7 @@ class SCMInfo: commit_sha: Optional[str] = None distance_to_latest_tag: int = 0 current_version: Optional[str] = None + current_tag: Optional[str] = None branch_name: Optional[str] = None short_branch_name: Optional[str] = None repository_root: Optional[Path] = None @@ -42,6 +43,7 @@ def __repr__(self): f"commit_sha={self.commit_sha}, " f"distance_to_latest_tag={self.distance_to_latest_tag}, " f"current_version={self.current_version}, " + f"current_tag={self.current_tag}, " f"branch_name={self.branch_name}, " f"short_branch_name={self.short_branch_name}, " f"repository_root={self.repository_root}, " @@ -286,7 +288,7 @@ def _commit_info(cls, parse_pattern: str, tag_name: str) -> dict: A dictionary containing information about the latest commit. """ tag_pattern = tag_name.replace("{new_version}", "*") - info = dict.fromkeys(["dirty", "commit_sha", "distance_to_latest_tag", "current_version"]) + info = dict.fromkeys(["dirty", "commit_sha", "distance_to_latest_tag", "current_version", "current_tag"]) info["distance_to_latest_tag"] = 0 try: # get info about the latest tag in git @@ -309,6 +311,7 @@ def _commit_info(cls, parse_pattern: str, tag_name: str) -> dict: info["commit_sha"] = describe_out.pop().lstrip("g") info["distance_to_latest_tag"] = int(describe_out.pop()) + info["current_tag"] = "-".join(describe_out) version = cls.get_version_from_tag("-".join(describe_out), tag_name, parse_pattern) info["current_version"] = version or "-".join(describe_out).lstrip("v") except subprocess.CalledProcessError as e: diff --git a/tests/fixtures/basic_cfg_expected.txt b/tests/fixtures/basic_cfg_expected.txt index 6c738d33..d61fd97f 100644 --- a/tests/fixtures/basic_cfg_expected.txt +++ b/tests/fixtures/basic_cfg_expected.txt @@ -82,6 +82,7 @@ 'replace': '{new_version}', 'scm_info': {'branch_name': None, 'commit_sha': None, + 'current_tag': None, 'current_version': None, 'dirty': None, 'distance_to_latest_tag': 0, diff --git a/tests/fixtures/basic_cfg_expected.yaml b/tests/fixtures/basic_cfg_expected.yaml index 371e8709..fb659f45 100644 --- a/tests/fixtures/basic_cfg_expected.yaml +++ b/tests/fixtures/basic_cfg_expected.yaml @@ -111,6 +111,7 @@ replace: "{new_version}" scm_info: branch_name: null commit_sha: null + current_tag: null current_version: null dirty: null distance_to_latest_tag: 0 diff --git a/tests/fixtures/basic_cfg_expected_full.json b/tests/fixtures/basic_cfg_expected_full.json index 3b4d31ba..db206ff9 100644 --- a/tests/fixtures/basic_cfg_expected_full.json +++ b/tests/fixtures/basic_cfg_expected_full.json @@ -126,6 +126,7 @@ "scm_info": { "branch_name": null, "commit_sha": null, + "current_tag": null, "current_version": null, "dirty": null, "distance_to_latest_tag": 0, From d6b24f01b22fcbfe719ae9954c47e03ec1df3072 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sat, 17 Aug 2024 09:35:57 -0500 Subject: [PATCH 2/9] Add hooks configuration fields Introduced `setup_hooks`, `pre_bump_hooks`, and `post_bump_hooks` fields to configuration models. Updated corresponding test fixtures to verify these new fields. --- bumpversion/config/__init__.py | 3 +++ bumpversion/config/models.py | 3 +++ tests/fixtures/basic_cfg_expected.txt | 3 +++ tests/fixtures/basic_cfg_expected.yaml | 6 ++++++ tests/fixtures/basic_cfg_expected_full.json | 3 +++ 5 files changed, 18 insertions(+) diff --git a/bumpversion/config/__init__.py b/bumpversion/config/__init__.py index 1195b757..3e79334e 100644 --- a/bumpversion/config/__init__.py +++ b/bumpversion/config/__init__.py @@ -34,6 +34,9 @@ "scm_info": None, "parts": {}, "files": [], + "setup_hooks": [], + "pre_bump_hooks": [], + "post_bump_hooks": [], } diff --git a/bumpversion/config/models.py b/bumpversion/config/models.py index 00e630ad..563ef491 100644 --- a/bumpversion/config/models.py +++ b/bumpversion/config/models.py @@ -100,6 +100,9 @@ class Config(BaseSettings): scm_info: Optional["SCMInfo"] parts: Dict[str, VersionComponentSpec] files: List[FileChange] = Field(default_factory=list) + setup_hooks: List[str] = Field(default_factory=list) + pre_bump_hooks: List[str] = Field(default_factory=list) + post_bump_hooks: List[str] = Field(default_factory=list) included_paths: List[str] = Field(default_factory=list) excluded_paths: List[str] = Field(default_factory=list) model_config = SettingsConfigDict(env_prefix="bumpversion_") diff --git a/tests/fixtures/basic_cfg_expected.txt b/tests/fixtures/basic_cfg_expected.txt index d61fd97f..a7cc03cb 100644 --- a/tests/fixtures/basic_cfg_expected.txt +++ b/tests/fixtures/basic_cfg_expected.txt @@ -78,6 +78,8 @@ 'independent': False, 'optional_value': 'gamma', 'values': ['dev', 'gamma']}}, + 'post_bump_hooks': [], + 'pre_bump_hooks': [], 'regex': False, 'replace': '{new_version}', 'scm_info': {'branch_name': None, @@ -91,6 +93,7 @@ 'tool': None}, 'search': '{current_version}', 'serialize': ('{major}.{minor}.{patch}-{release}', '{major}.{minor}.{patch}'), + 'setup_hooks': [], 'sign_tags': False, 'tag': True, 'tag_message': 'Bump version: {current_version} → {new_version}', diff --git a/tests/fixtures/basic_cfg_expected.yaml b/tests/fixtures/basic_cfg_expected.yaml index fb659f45..893d19f5 100644 --- a/tests/fixtures/basic_cfg_expected.yaml +++ b/tests/fixtures/basic_cfg_expected.yaml @@ -106,6 +106,10 @@ parts: values: - "dev" - "gamma" +post_bump_hooks: + +pre_bump_hooks: + regex: false replace: "{new_version}" scm_info: @@ -122,6 +126,8 @@ search: "{current_version}" serialize: - "{major}.{minor}.{patch}-{release}" - "{major}.{minor}.{patch}" +setup_hooks: + sign_tags: false tag: true tag_message: "Bump version: {current_version} → {new_version}" diff --git a/tests/fixtures/basic_cfg_expected_full.json b/tests/fixtures/basic_cfg_expected_full.json index db206ff9..fb1816d5 100644 --- a/tests/fixtures/basic_cfg_expected_full.json +++ b/tests/fixtures/basic_cfg_expected_full.json @@ -121,6 +121,8 @@ ] } }, + "post_bump_hooks": [], + "pre_bump_hooks": [], "regex": false, "replace": "{new_version}", "scm_info": { @@ -139,6 +141,7 @@ "{major}.{minor}.{patch}-{release}", "{major}.{minor}.{patch}" ], + "setup_hooks": [], "sign_tags": false, "tag": true, "tag_message": "Bump version: {current_version} \u2192 {new_version}", From e50e991fe481ff25791a25987e4c4af133f50817 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sat, 17 Aug 2024 11:59:02 -0500 Subject: [PATCH 3/9] Fixed redundant tests for SCM --- tests/test_scm.py | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/tests/test_scm.py b/tests/test_scm.py index d13c72c7..ed0a0af3 100644 --- a/tests/test_scm.py +++ b/tests/test_scm.py @@ -135,37 +135,13 @@ def test_returns_commit_and_tag_info(self, git_repo: Path) -> None: tag_info = scm.Git.latest_tag_info(tag_name, parse_pattern=parse_pattern) assert tag_info.commit_sha is not None assert tag_info.current_version == "0.1.0" + assert tag_info.current_tag == f"{tag_prefix}0.1.0" assert tag_info.distance_to_latest_tag == 0 assert tag_info.branch_name == "master" assert tag_info.short_branch_name == "master" assert tag_info.repository_root == git_repo assert tag_info.dirty is False - def test_git_latest_tag_info(self, git_repo: Path) -> None: - """Should return information about the latest tag.""" - readme = git_repo.joinpath("readme.md") - readme.touch() - tag_prefix = "app/" - parse_pattern = r"(?P\d+)\.(?P\d+)\.(?P\d+)" - tag_name = f"{tag_prefix}{{new_version}}" - with inside_dir(git_repo): - # Add a file and tag - subprocess.run(["git", "add", "readme.md"]) - subprocess.run(["git", "commit", "-m", "first"]) - subprocess.run(["git", "tag", f"{tag_prefix}0.1.0"]) - - # Make it dirty - git_repo.joinpath("something.md").touch() - subprocess.run(["git", "add", "something.md"]) - tag_info = scm.Git.latest_tag_info(tag_name, parse_pattern=parse_pattern) - assert tag_info.commit_sha is not None - assert tag_info.current_version == "0.1.0" - assert tag_info.distance_to_latest_tag == 0 - assert tag_info.branch_name == "master" - assert tag_info.short_branch_name == "master" - assert tag_info.repository_root == git_repo - assert tag_info.dirty is True - def test_git_detects_existing_tag(git_repo: Path, caplog: LogCaptureFixture) -> None: """Attempting to tag when a tag exists will do nothing.""" From 049b4704f245f4ec5c258eb5fc2f10e2b564655a Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sat, 17 Aug 2024 12:03:08 -0500 Subject: [PATCH 4/9] Changed the terminology for hooks. Change pre-bump and post-bump to pre-commit and post-commit to better indicate their order of operations. --- bumpversion/config/__init__.py | 4 +- bumpversion/config/models.py | 4 +- bumpversion/hooks.py | 90 +++++++++++++++++++++ pyproject.toml | 2 +- tests/fixtures/basic_cfg_expected.txt | 4 +- tests/fixtures/basic_cfg_expected.yaml | 4 +- tests/fixtures/basic_cfg_expected_full.json | 4 +- 7 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 bumpversion/hooks.py diff --git a/bumpversion/config/__init__.py b/bumpversion/config/__init__.py index 3e79334e..19eec174 100644 --- a/bumpversion/config/__init__.py +++ b/bumpversion/config/__init__.py @@ -35,8 +35,8 @@ "parts": {}, "files": [], "setup_hooks": [], - "pre_bump_hooks": [], - "post_bump_hooks": [], + "pre_commit_hooks": [], + "post_commit_hooks": [], } diff --git a/bumpversion/config/models.py b/bumpversion/config/models.py index 563ef491..d6ce3ef5 100644 --- a/bumpversion/config/models.py +++ b/bumpversion/config/models.py @@ -101,8 +101,8 @@ class Config(BaseSettings): parts: Dict[str, VersionComponentSpec] files: List[FileChange] = Field(default_factory=list) setup_hooks: List[str] = Field(default_factory=list) - pre_bump_hooks: List[str] = Field(default_factory=list) - post_bump_hooks: List[str] = Field(default_factory=list) + pre_commit_hooks: List[str] = Field(default_factory=list) + post_commit_hooks: List[str] = Field(default_factory=list) included_paths: List[str] = Field(default_factory=list) excluded_paths: List[str] = Field(default_factory=list) model_config = SettingsConfigDict(env_prefix="bumpversion_") diff --git a/bumpversion/hooks.py b/bumpversion/hooks.py new file mode 100644 index 00000000..988da637 --- /dev/null +++ b/bumpversion/hooks.py @@ -0,0 +1,90 @@ +"""Implementation of the hook interface.""" + +import datetime +import os +import subprocess +from typing import Dict, Optional + +from bumpversion.config.models import Config +from bumpversion.ui import get_indented_logger + +PREFIX = "BVHOOK_" + +logger = get_indented_logger(__name__) + + +def run_command(script: str, environment: Optional[dict] = None) -> subprocess.CompletedProcess: + """Runs command-line programs using the shell.""" + if not isinstance(script, str): + raise TypeError(f"`script` must be a string, not {type(script)}") + if environment and not isinstance(environment, dict): + raise TypeError(f"`environment` must be a dict, not {type(environment)}") + return subprocess.run(script, env=environment, encoding="utf-8", shell=True, text=True, capture_output=True) + + +def base_env(config: Config) -> Dict[str, str]: + """Provide the base environment variables.""" + return { + f"{PREFIX}NOW": datetime.datetime.now().isoformat(), + f"{PREFIX}UTCNOW": datetime.datetime.now(datetime.timezone.utc).isoformat(), + **os.environ, + **scm_env(config), + } + + +def scm_env(config: Config) -> Dict[str, str]: + """Provide the scm environment variables.""" + scm = config.scm_info + return { + f"{PREFIX}COMMIT_SHA": scm.commit_sha or "", + f"{PREFIX}DISTANCE_TO_LATEST_TAG": str(scm.distance_to_latest_tag) or "0", + f"{PREFIX}IS_DIRTY": str(scm.dirty), + f"{PREFIX}BRANCH_NAME": scm.branch_name or "", + f"{PREFIX}SHORT_BRANCH_NAME": scm.short_branch_name or "", + f"{PREFIX}CURRENT_VERSION": scm.current_version or "", + f"{PREFIX}CURRENT_TAG": scm.current_tag or "", + } + + +def current_version_env(config: Config) -> Dict[str, str]: + """Provide the current version environment variables.""" + version_str = config.current_version + version = config.version_config.parse(version_str) + + return {f"{PREFIX}CURRENT_{part.upper()}": version[part].value for part in version} + + +def setup_hook_env(config: Config) -> Dict[str, str]: + """Provide the environment dictionary for `setup_hook`s.""" + return {**base_env(config), **scm_env(config), **current_version_env(config)} + + +def run_setup_hooks(config: Config) -> None: + """Run the setup hooks.""" + env = setup_hook_env(config) + if config.setup_hooks: + logger.info("Running setup hooks:") + else: + logger.info("No setup hooks defined") + return + + logger.indent() + for script in config.setup_hooks: + logger.debug(f"Running {script!r}") + logger.indent() + result = run_command(script, env) + logger.debug(result.stdout) + logger.debug(result.stderr) + logger.debug(f"Exited with {result.returncode}") + logger.indent() + logger.dedent() + + +def run_pre_commit_hooks(config: Config) -> None: + """Run the pre-commit hooks.""" + pass + + +def run_post_commit_hooks(config: Config) -> None: + """Run the post-commit hooks.""" + pass diff --git a/pyproject.toml b/pyproject.toml index 32aa90cb..f99076e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -186,7 +186,7 @@ line-length = 119 select = ["E", "W", "F", "I", "N", "B", "BLE", "C", "D", "E", "F", "I", "N", "S", "T", "W", "RUF", "NPY", "PD", "PGH", "ANN", "C90", "PLC", "PLE", "PLW", "TCH"] ignore = [ "ANN002", "ANN003", "ANN101", "ANN102", "ANN204", "ANN401", - "S101", "S104", + "S101", "S104", "S602", "D105", "D106", "D107", "D200", "D212", "PD011", "PLW1510", diff --git a/tests/fixtures/basic_cfg_expected.txt b/tests/fixtures/basic_cfg_expected.txt index a7cc03cb..0e0fb5d4 100644 --- a/tests/fixtures/basic_cfg_expected.txt +++ b/tests/fixtures/basic_cfg_expected.txt @@ -78,8 +78,8 @@ 'independent': False, 'optional_value': 'gamma', 'values': ['dev', 'gamma']}}, - 'post_bump_hooks': [], - 'pre_bump_hooks': [], + 'post_commit_hooks': [], + 'pre_commit_hooks': [], 'regex': False, 'replace': '{new_version}', 'scm_info': {'branch_name': None, diff --git a/tests/fixtures/basic_cfg_expected.yaml b/tests/fixtures/basic_cfg_expected.yaml index 893d19f5..9c35a1d9 100644 --- a/tests/fixtures/basic_cfg_expected.yaml +++ b/tests/fixtures/basic_cfg_expected.yaml @@ -106,9 +106,9 @@ parts: values: - "dev" - "gamma" -post_bump_hooks: +post_commit_hooks: -pre_bump_hooks: +pre_commit_hooks: regex: false replace: "{new_version}" diff --git a/tests/fixtures/basic_cfg_expected_full.json b/tests/fixtures/basic_cfg_expected_full.json index fb1816d5..df4dadeb 100644 --- a/tests/fixtures/basic_cfg_expected_full.json +++ b/tests/fixtures/basic_cfg_expected_full.json @@ -121,8 +121,8 @@ ] } }, - "post_bump_hooks": [], - "pre_bump_hooks": [], + "post_commit_hooks": [], + "pre_commit_hooks": [], "regex": false, "replace": "{new_version}", "scm_info": { From 844656717e3799fc418e76ef2b9e3b12116ca0d5 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sat, 17 Aug 2024 12:03:42 -0500 Subject: [PATCH 5/9] Added tests for hooks --- tests/test_hooks/__init__.py | 0 tests/test_hooks/test_envs.py | 88 ++++++++++++++++++++++++ tests/test_hooks/test_run_command.py | 42 +++++++++++ tests/test_hooks/test_run_setup_hooks.py | 33 +++++++++ 4 files changed, 163 insertions(+) create mode 100644 tests/test_hooks/__init__.py create mode 100644 tests/test_hooks/test_envs.py create mode 100644 tests/test_hooks/test_run_command.py create mode 100644 tests/test_hooks/test_run_setup_hooks.py diff --git a/tests/test_hooks/__init__.py b/tests/test_hooks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_hooks/test_envs.py b/tests/test_hooks/test_envs.py new file mode 100644 index 00000000..536d8854 --- /dev/null +++ b/tests/test_hooks/test_envs.py @@ -0,0 +1,88 @@ +"""Tests for environment generation for hooks.""" + +import datetime +import os +import subprocess +from pathlib import Path + +from bumpversion.hooks import scm_env, PREFIX, base_env, current_version_env +from tests.conftest import inside_dir, get_config_data + + +def test_scm_env_returns_correct_info(git_repo: Path): + """Should return information about the latest tag.""" + readme = git_repo.joinpath("readme.md") + readme.touch() + tag_prefix = "v" + overrides = {"current_version": "0.1.0", "commit": True, "tag": True, "tag_name": f"{tag_prefix}{{new_version}}"} + + with inside_dir(git_repo): + # Add a file and tag + subprocess.run(["git", "add", "readme.md"]) + subprocess.run(["git", "commit", "-m", "first"]) + subprocess.run(["git", "tag", f"{tag_prefix}0.1.0"]) + conf, _, _ = get_config_data(overrides) + + result = scm_env(conf) + assert result[f"{PREFIX}BRANCH_NAME"] == "master" + assert len(result[f"{PREFIX}COMMIT_SHA"]) == 40 + assert result[f"{PREFIX}CURRENT_TAG"] == "v0.1.0" + assert result[f"{PREFIX}CURRENT_VERSION"] == "0.1.0" + assert result[f"{PREFIX}DISTANCE_TO_LATEST_TAG"] == "0" + assert result[f"{PREFIX}IS_DIRTY"] == "False" + assert result[f"{PREFIX}SHORT_BRANCH_NAME"] == "master" + + +class MockDatetime(datetime.datetime): + @classmethod + def now(cls, tz=None): + return cls(2022, 2, 1, 17) if tz else cls(2022, 2, 1, 12) + + +class TestBaseEnv: + """Tests for base_env function.""" + + def test_includes_now_and_utcnow(self, mocker): + """The output includes NOW and UTCNOW.""" + mocker.patch("datetime.datetime", new=MockDatetime) + config, _, _ = get_config_data({"current_version": "0.1.0"}) + result_env = base_env(config) + + assert f"{PREFIX}NOW" in result_env + assert f"{PREFIX}UTCNOW" in result_env + assert result_env[f"{PREFIX}NOW"] == "2022-02-01T12:00:00" + assert result_env[f"{PREFIX}UTCNOW"] == "2022-02-01T17:00:00" + + def test_includes_os_environ(self): + """The output includes the current process' environment.""" + config, _, _ = get_config_data({"current_version": "0.1.0"}) + result_env = base_env(config) + + for var, value in os.environ.items(): + assert var in result_env + assert result_env[var] == value + + def test_includes_scm_info(self): + """The output includes SCM information.""" + config, _, _ = get_config_data({"current_version": "0.1.0"}) + result_env = base_env(config) + + assert f"{PREFIX}COMMIT_SHA" in result_env + assert f"{PREFIX}DISTANCE_TO_LATEST_TAG" in result_env + assert f"{PREFIX}IS_DIRTY" in result_env + assert f"{PREFIX}BRANCH_NAME" in result_env + assert f"{PREFIX}SHORT_BRANCH_NAME" in result_env + assert f"{PREFIX}CURRENT_VERSION" in result_env + assert f"{PREFIX}CURRENT_TAG" in result_env + + +def test_current_version_env_includes_correct_info(): + """pass""" + config, _, _ = get_config_data( + {"current_version": "0.1.0", "parse": r"(?P\d+)\.(?P\d+)\.(?P\d+)"} + ) + result = current_version_env(config) + + assert result[f"{PREFIX}CURRENT_MAJOR"] == "0" + assert result[f"{PREFIX}CURRENT_MINOR"] == "1" + assert result[f"{PREFIX}CURRENT_PATCH"] == "0" diff --git a/tests/test_hooks/test_run_command.py b/tests/test_hooks/test_run_command.py new file mode 100644 index 00000000..0f03d798 --- /dev/null +++ b/tests/test_hooks/test_run_command.py @@ -0,0 +1,42 @@ +import subprocess + +import pytest + +from bumpversion.hooks import run_command + + +class TestRunCommand: + """Test the run_command function.""" + + def test_runs_a_str_command(self): + """Runs the command formatted as a string.""" + result = run_command("echo Hello") + assert isinstance(result, subprocess.CompletedProcess) + assert result.stdout == "Hello\n" + + def test_can_access_env(self): + """The command can access custom environment variables.""" + result = run_command("echo $TEST_ENV", environment={"TEST_ENV": "Hello"}) + assert isinstance(result, subprocess.CompletedProcess) + assert result.stdout == "Hello\n" + + def test_non_zero_exit(self): + """The result shows a non-zero result code.""" + result = run_command("exit 1") + assert result.returncode == 1 + + @pytest.mark.parametrize( + "invalid_script", + [(123,), (None,), (["exit", "1"])], + ) + def test_an_invalid_script_raises_type_error(self, invalid_script): + with pytest.raises(TypeError): + run_command(invalid_script) + + @pytest.mark.parametrize( + "invalid_env", + [("string",), (123,), (None,)], + ) + def test_an_invalid_env_raises_type_error(self, invalid_env): + with pytest.raises(TypeError): + run_command("echo Hello", environment=invalid_env) diff --git a/tests/test_hooks/test_run_setup_hooks.py b/tests/test_hooks/test_run_setup_hooks.py new file mode 100644 index 00000000..060c2cdc --- /dev/null +++ b/tests/test_hooks/test_run_setup_hooks.py @@ -0,0 +1,33 @@ +import subprocess + +from bumpversion.config import Config +from bumpversion.hooks import run_setup_hooks +from tests.conftest import get_config_data + + +def setup_hook_env(config: Config) -> dict: + """Mocked function for the environment setup""" + return {} + + +def run_command(script: str, env: dict) -> subprocess.CompletedProcess: + """Mocked function for command execution""" + return subprocess.CompletedProcess(args=script, returncode=0) + + +def test_run_setup_hooks_calls_each_hook(mocker): + """The run_setup_hooks function runs each hook.""" + # Assemble + setup_hook_env_mock = mocker.patch("bumpversion.hooks.setup_hook_env", side_effect=setup_hook_env) + run_command_mock = mocker.patch("bumpversion.hooks.run_command", side_effect=run_command) + + config, _, _ = get_config_data({"current_version": "0.1.0", "setup_hooks": ["script1", "script2"]}) + + # Act + result = run_setup_hooks(config) + + # Asserts for function's behavior + setup_hook_env_mock.assert_called_once_with(config) + assert run_command_mock.call_count == len(config.setup_hooks) + run_command_mock.assert_any_call("script1", {}) + run_command_mock.assert_any_call("script2", {}) From 3b638e088edee304d2f0a8305e332eff2eba85e0 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sat, 17 Aug 2024 12:05:06 -0500 Subject: [PATCH 6/9] Added hooks to bump command --- bumpversion/bump.py | 12 ++++++ docs/reference/hooks.md | 96 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 docs/reference/hooks.md diff --git a/bumpversion/bump.py b/bumpversion/bump.py index 2d135e08..e194a308 100644 --- a/bumpversion/bump.py +++ b/bumpversion/bump.py @@ -4,6 +4,8 @@ from pathlib import Path from typing import TYPE_CHECKING, List, MutableMapping, Optional +from bumpversion.hooks import run_post_commit_hooks, run_pre_commit_hooks, run_setup_hooks + if TYPE_CHECKING: # pragma: no-coverage from bumpversion.files import ConfiguredFile from bumpversion.versioning.models import Version @@ -75,10 +77,14 @@ def do_bump( logger.indent() ctx = get_context(config) + logger.info("Parsing current version '%s'", config.current_version) logger.indent() version = config.version_config.parse(config.current_version) logger.dedent() + + run_setup_hooks(config) + next_version = get_next_version(version, config, version_part, new_version) next_version_str = config.version_config.serialize(next_version, ctx) logger.info("New version will be '%s'", next_version_str) @@ -109,7 +115,13 @@ def do_bump( ctx = get_context(config, version, next_version) ctx["new_version"] = next_version_str + + run_pre_commit_hooks(config) + commit_and_tag(config, config_file, configured_files, ctx, dry_run) + + run_post_commit_hooks(config) + logger.info("Done.") diff --git a/docs/reference/hooks.md b/docs/reference/hooks.md new file mode 100644 index 00000000..d47943a7 --- /dev/null +++ b/docs/reference/hooks.md @@ -0,0 +1,96 @@ +--- +title: Hooks +description: Details about writing and setting up hooks +icon: +date: 2024-08-15 +comments: true +--- +# Hooks + +- Each global configuration of `setup_hooks`, `pre_commit_hooks`, and `post_commit_hooks` is a list of commands run in a shell +- Explanation of the context passed into the environment +- Run in sequentially + +Order of operations + +- Run setup hooks +- Increment version +- Change files +- Run pre-commit hooks +- commit and tag +- Run post-commit hooks + +## Setup Hooks + +```toml title="Calling individual commands" +[tool.bumpversion] +setup_hooks = [ + "git config --global user.email \"bump-my-version@github.actions\"", + "git config --global user.name \"Testing Git\"", + "git --version", + "git config --list", +] +``` + +or + +```toml title="Calling a shell script" +[tool.bumpversion] +setup_hooks = ["path/to/setup.sh"] +``` + +```bash title="path/to/setup.sh" +#!/usr/bin/env bash + +git config --global user.email "bump-my-version@github.actions" +git config --global user.name "Testing Git" +git --version +git config --list +``` +### Environment + +- The existing OS environment is available + +#### Date and time fields + +::: field-list + + `BVHOOK_NOW` + : The ISO-8601-formatted current local time without a time zone reference. + + `BVHOOK_UTCNOW` + : The ISO-8601-formatted current local time in the UTC time zone. + +#### Source code management fields + +These fields will only have values if the code is in a Git or Mercurial repository. + +::: field-list + + `BVHOOK_COMMIT_SHA` + : The latest commit reference. + + `BHOOK_DISTANCE_TO_LATEST_TAG` + : The number of commits since the latest tag. + + `BVHOOK_IS_DIRTY` + : A boolean indicating if the current repository has pending changes. + + `BVHOOK_BRANCH_NAME` + : The current branch name. + + `BVHOOK_SHORT_BRANCH_NAME` + : The current branch name, converted to lowercase, with non-alphanumeric characters removed and truncated to 20 characters. For example, `feature/MY-long_branch-name` would become `featuremylongbranchn`. + + +#### Version fields + +::: field-list + `BVHOOK_CURRENT_VERSION` + : The current version serialized as a string + + `BVHOOK_CURRENT_TAG` + : The current tag + + `BVHOOK_CURRENT_` + : Each version component defined by the [version configuration parsing regular expression](configuration/global.md#parse). The default configuration would have `current_major`, `current_minor`, and `current_patch` available. From 49f1953c476a09cc9e7332af6347914935ee982c Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sun, 18 Aug 2024 15:04:41 -0500 Subject: [PATCH 7/9] Enhance hook handling and testing across hook types - Introduced unified handling for setup, pre-commit, and post-commit hooks, including dry-run support. - Added comprehensive tests to ensure the correct behavior for all hook phases, including cases where no hooks are specified or in dry run mode. - Updated environment setup to use a common version environment function. --- bumpversion/bump.py | 6 +- bumpversion/hooks.py | 92 ++++++++++++----- tests/conftest.py | 8 ++ tests/test_hooks/test_envs.py | 6 +- tests/test_hooks/test_run_hooks.py | 48 +++++++++ tests/test_hooks/test_run_setup_hooks.py | 120 +++++++++++++++++++---- 6 files changed, 235 insertions(+), 45 deletions(-) create mode 100644 tests/test_hooks/test_run_hooks.py diff --git a/bumpversion/bump.py b/bumpversion/bump.py index e194a308..b386aa9a 100644 --- a/bumpversion/bump.py +++ b/bumpversion/bump.py @@ -83,7 +83,7 @@ def do_bump( version = config.version_config.parse(config.current_version) logger.dedent() - run_setup_hooks(config) + run_setup_hooks(config, version, dry_run) next_version = get_next_version(version, config, version_part, new_version) next_version_str = config.version_config.serialize(next_version, ctx) @@ -116,11 +116,11 @@ def do_bump( ctx = get_context(config, version, next_version) ctx["new_version"] = next_version_str - run_pre_commit_hooks(config) + run_pre_commit_hooks(config, version, next_version, dry_run) commit_and_tag(config, config_file, configured_files, ctx, dry_run) - run_post_commit_hooks(config) + run_post_commit_hooks(config, version, next_version, dry_run) logger.info("Done.") diff --git a/bumpversion/hooks.py b/bumpversion/hooks.py index 988da637..3238c019 100644 --- a/bumpversion/hooks.py +++ b/bumpversion/hooks.py @@ -3,10 +3,11 @@ import datetime import os import subprocess -from typing import Dict, Optional +from typing import Dict, List, Optional from bumpversion.config.models import Config from bumpversion.ui import get_indented_logger +from bumpversion.versioning.models import Version PREFIX = "BVHOOK_" @@ -46,30 +47,43 @@ def scm_env(config: Config) -> Dict[str, str]: } -def current_version_env(config: Config) -> Dict[str, str]: - """Provide the current version environment variables.""" - version_str = config.current_version - version = config.version_config.parse(version_str) +def version_env(version: Version, version_prefix: str) -> Dict[str, str]: + """Provide the environment variables for each version component with a prefix.""" + return {f"{PREFIX}{version_prefix}{part.upper()}": version[part].value for part in version} - return {f"{PREFIX}CURRENT_{part.upper()}": version[part].value for part in version} - -def setup_hook_env(config: Config) -> Dict[str, str]: +def get_setup_hook_env(config: Config, current_version: Version) -> Dict[str, str]: """Provide the environment dictionary for `setup_hook`s.""" - return {**base_env(config), **scm_env(config), **current_version_env(config)} + return {**base_env(config), **scm_env(config), **version_env(current_version, "CURRENT_")} -def run_setup_hooks(config: Config) -> None: - """Run the setup hooks.""" - env = setup_hook_env(config) - if config.setup_hooks: - logger.info("Running setup hooks:") - else: - logger.info("No setup hooks defined") - return +def get_pre_commit_hook_env(config: Config, current_version: Version, new_version: Version) -> Dict[str, str]: + """Provide the environment dictionary for `pre_commit_hook`s.""" + return { + **base_env(config), + **scm_env(config), + **version_env(current_version, "CURRENT_"), + **version_env(new_version, "NEW_"), + } + + +def get_post_commit_hook_env(config: Config, current_version: Version, new_version: Version) -> Dict[str, str]: + """Provide the environment dictionary for `post_commit_hook`s.""" + return { + **base_env(config), + **scm_env(config), + **version_env(current_version, "CURRENT_"), + **version_env(new_version, "NEW_"), + } + +def run_hooks(hooks: List[str], env: Dict[str, str], dry_run: bool = False) -> None: + """Run a list of command-line programs using the shell.""" logger.indent() - for script in config.setup_hooks: + for script in hooks: + if dry_run: + logger.debug(f"Would run {script!r}") + continue logger.debug(f"Running {script!r}") logger.indent() result = run_command(script, env) @@ -80,11 +94,45 @@ def run_setup_hooks(config: Config) -> None: logger.dedent() -def run_pre_commit_hooks(config: Config) -> None: +def run_setup_hooks(config: Config, current_version: Version, dry_run: bool = False) -> None: + """Run the setup hooks.""" + env = get_setup_hook_env(config, current_version) + if config.setup_hooks: + running = "Would run" if dry_run else "Running" + logger.info(f"{running} setup hooks:") + else: + logger.info("No setup hooks defined") + return + + run_hooks(config.setup_hooks, env, dry_run) + + +def run_pre_commit_hooks( + config: Config, current_version: Version, new_version: Version, dry_run: bool = False +) -> None: """Run the pre-commit hooks.""" - pass + env = get_pre_commit_hook_env(config, current_version, new_version) + + if config.pre_commit_hooks: + running = "Would run" if dry_run else "Running" + logger.info(f"{running} pre-commit hooks:") + else: + logger.info("No pre-commit hooks defined") + return + + run_hooks(config.pre_commit_hooks, env, dry_run) -def run_post_commit_hooks(config: Config) -> None: +def run_post_commit_hooks( + config: Config, current_version: Version, new_version: Version, dry_run: bool = False +) -> None: """Run the post-commit hooks.""" - pass + env = get_post_commit_hook_env(config, current_version, new_version) + if config.post_commit_hooks: + running = "Would run" if dry_run else "Running" + logger.info(f"{running} post-commit hooks:") + else: + logger.info("No post-commit hooks defined") + return + + run_hooks(config.post_commit_hooks, env, dry_run) diff --git a/tests/conftest.py b/tests/conftest.py index bf0198b2..680b5a55 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,8 @@ import pytest +from bumpversion.versioning.models import Version + @pytest.fixture def tests_path() -> Path: @@ -51,6 +53,12 @@ def get_config_data(overrides: dict) -> tuple: return conf, version_config, version +def get_semver(version: str) -> Version: + """Get a semantic version from a string.""" + _, _, version = get_config_data({"current_version": version}) + return version + + @pytest.fixture def git_repo(tmp_path: Path) -> Path: """Generate a simple temporary git repo and return the path.""" diff --git a/tests/test_hooks/test_envs.py b/tests/test_hooks/test_envs.py index 536d8854..3570b7f2 100644 --- a/tests/test_hooks/test_envs.py +++ b/tests/test_hooks/test_envs.py @@ -5,7 +5,7 @@ import subprocess from pathlib import Path -from bumpversion.hooks import scm_env, PREFIX, base_env, current_version_env +from bumpversion.hooks import scm_env, PREFIX, base_env, version_env from tests.conftest import inside_dir, get_config_data @@ -78,10 +78,10 @@ def test_includes_scm_info(self): def test_current_version_env_includes_correct_info(): """pass""" - config, _, _ = get_config_data( + config, _, current_version = get_config_data( {"current_version": "0.1.0", "parse": r"(?P\d+)\.(?P\d+)\.(?P\d+)"} ) - result = current_version_env(config) + result = version_env(current_version, "CURRENT_") assert result[f"{PREFIX}CURRENT_MAJOR"] == "0" assert result[f"{PREFIX}CURRENT_MINOR"] == "1" diff --git a/tests/test_hooks/test_run_hooks.py b/tests/test_hooks/test_run_hooks.py new file mode 100644 index 00000000..f7538924 --- /dev/null +++ b/tests/test_hooks/test_run_hooks.py @@ -0,0 +1,48 @@ +"""Tests for the run_hooks function.""" + +from bumpversion import hooks + + +def test_calls_each_hook(mocker): + """It should call each hook passed to it.""" + # Assemble + mock_logger = mocker.patch("bumpversion.hooks.logger") + mock_run_command = mocker.patch("bumpversion.hooks.run_command") + hooks_list = ["script1", "script2"] + env = {"var": "value"} + mock_run_command.return_value = mocker.MagicMock(stdout="output", stderr="error", returncode=0) + + # Act + hooks.run_hooks(hooks_list, env) + + # Assert + expected_calls = [ + mocker.call("Running 'script1'"), + mocker.call("output"), + mocker.call("error"), + mocker.call("Exited with 0"), + mocker.call("Running 'script2'"), + mocker.call("output"), + mocker.call("error"), + mocker.call("Exited with 0"), + ] + mock_logger.debug.assert_has_calls(expected_calls) + mock_run_command.assert_any_call("script1", env) + mock_run_command.assert_any_call("script2", env) + + +def test_does_not_call_each_hook_when_dry_run(mocker): + """It should not call each hook passed to it when dry_run is True.""" + # Assemble + mock_logger = mocker.patch("bumpversion.hooks.logger") + mock_run_command = mocker.patch("bumpversion.hooks.run_command") + hooks_list = ["script1", "script2"] + env = {"var": "value"} + + # Act + hooks.run_hooks(hooks_list, env, dry_run=True) + + # Assert + expected_calls = [mocker.call("Would run 'script1'"), mocker.call("Would run 'script2'")] + mock_logger.debug.assert_has_calls(expected_calls) + mock_run_command.assert_not_called() diff --git a/tests/test_hooks/test_run_setup_hooks.py b/tests/test_hooks/test_run_setup_hooks.py index 060c2cdc..8202f1bf 100644 --- a/tests/test_hooks/test_run_setup_hooks.py +++ b/tests/test_hooks/test_run_setup_hooks.py @@ -1,11 +1,14 @@ import subprocess +from typing import Callable -from bumpversion.config import Config -from bumpversion.hooks import run_setup_hooks -from tests.conftest import get_config_data +import pytest +from pytest import param +from bumpversion.hooks import run_setup_hooks, run_pre_commit_hooks, run_post_commit_hooks +from bumpversion.versioning.models import Version +from tests.conftest import get_config_data, get_semver -def setup_hook_env(config: Config) -> dict: +def get_hook_env(*args, **kwargs) -> dict: """Mocked function for the environment setup""" return {} @@ -15,19 +18,102 @@ def run_command(script: str, env: dict) -> subprocess.CompletedProcess: return subprocess.CompletedProcess(args=script, returncode=0) -def test_run_setup_hooks_calls_each_hook(mocker): - """The run_setup_hooks function runs each hook.""" - # Assemble - setup_hook_env_mock = mocker.patch("bumpversion.hooks.setup_hook_env", side_effect=setup_hook_env) - run_command_mock = mocker.patch("bumpversion.hooks.run_command", side_effect=run_command) +CURRENT_VERSION = get_semver("1.0.0") +NEW_VERSION = get_semver("1.1.0") - config, _, _ = get_config_data({"current_version": "0.1.0", "setup_hooks": ["script1", "script2"]}) - # Act - result = run_setup_hooks(config) +class TestHookSuites: + """Run each hook suite through the same set of tests.""" - # Asserts for function's behavior - setup_hook_env_mock.assert_called_once_with(config) - assert run_command_mock.call_count == len(config.setup_hooks) - run_command_mock.assert_any_call("script1", {}) - run_command_mock.assert_any_call("script2", {}) + suites = ( + param("setup", run_setup_hooks, (CURRENT_VERSION,), id="setup"), + param( + "pre_commit", + run_pre_commit_hooks, + ( + CURRENT_VERSION, + NEW_VERSION, + ), + id="pre_commit", + ), + param( + "post_commit", + run_post_commit_hooks, + ( + CURRENT_VERSION, + NEW_VERSION, + ), + id="post_commit", + ), + ) + + @pytest.mark.parametrize(["suite_name", "suite_func", "suite_args"], suites) + def test_calls_each_hook(self, mocker, suite_name: str, suite_func: Callable, suite_args: tuple): + """The suite hook runs each hook.""" + # Assemble + env = {"var": "value"} + mock_env = mocker.patch(f"bumpversion.hooks.get_{suite_name}_hook_env") + mock_env.return_value = env + mock_run_command = mocker.patch("bumpversion.hooks.run_command") + mock_run_command.return_value = mocker.MagicMock(stdout="output", stderr="error", returncode=0) + mock_logger = mocker.patch("bumpversion.hooks.logger") + + config, _, _ = get_config_data({"current_version": "1.0.0", f"{suite_name}_hooks": ["script1", "script2"]}) + + # Act + suite_func(config, *suite_args) + + # Assert + mock_logger.info.assert_called_once_with(f"Running {suite_name} hooks:".replace("_", "-")) + mock_env.assert_called_once_with(config, *suite_args) + expected_run_command_calls = [ + mocker.call("script1", env), + mocker.call("script2", env), + ] + mock_run_command.assert_has_calls(expected_run_command_calls) + + @pytest.mark.parametrize(["suite_name", "suite_func", "suite_args"], suites) + def test_does_not_run_hooks_if_none_are_specified( + self, mocker, suite_name: str, suite_func: Callable, suite_args: tuple + ): + """If no setup_hooks are defined, nothing is run.""" + # Assemble + env = {"var": "value"} + mock_env = mocker.patch(f"bumpversion.hooks.get_{suite_name}_hook_env") + mock_env.return_value = env + mock_run_command = mocker.patch("bumpversion.hooks.run_command") + mock_run_command.return_value = mocker.MagicMock(stdout="output", stderr="error", returncode=0) + mock_logger = mocker.patch("bumpversion.hooks.logger") + + config, _, _ = get_config_data({"current_version": "1.0.0", f"{suite_name}_hooks": []}) + + # Act + suite_func(config, *suite_args) + + # Asserts + mock_logger.info.assert_called_once_with(f"No {suite_name} hooks defined".replace("_", "-")) + mock_env.assert_called_once_with(config, *suite_args) + assert mock_run_command.call_count == 0 + + @pytest.mark.parametrize(["suite_name", "suite_func", "suite_args"], suites) + def test_does_not_run_hooks_if_dry_run_is_true( + self, mocker, suite_name: str, suite_func: Callable, suite_args: tuple + ): + """If dry_run is True, nothing is run.""" + # Assemble + env = {"var": "value"} + mock_env = mocker.patch(f"bumpversion.hooks.get_{suite_name}_hook_env") + mock_env.return_value = env + mock_run_command = mocker.patch("bumpversion.hooks.run_hooks") + mock_logger = mocker.patch("bumpversion.hooks.logger") + + config, _, _ = get_config_data({"current_version": "1.0.0", f"{suite_name}_hooks": ["script1", "script2"]}) + + # Act + args = [*suite_args, True] + suite_func(config, *args) + + # Asserts + mock_logger.info.assert_called_once_with(f"Would run {suite_name} hooks:".replace("_", "-")) + mock_env.assert_called_once_with(config, *suite_args) + assert mock_run_command.call_count == 1 From b73a6e120122d1fe28e52c2b9b13eeed95f20a51 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sun, 18 Aug 2024 16:08:48 -0500 Subject: [PATCH 8/9] Added hook suite documentation --- docs/reference/hooks.md | 95 ++++++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 26 deletions(-) diff --git a/docs/reference/hooks.md b/docs/reference/hooks.md index d47943a7..d18a245c 100644 --- a/docs/reference/hooks.md +++ b/docs/reference/hooks.md @@ -7,20 +7,29 @@ comments: true --- # Hooks -- Each global configuration of `setup_hooks`, `pre_commit_hooks`, and `post_commit_hooks` is a list of commands run in a shell -- Explanation of the context passed into the environment -- Run in sequentially +## Hook Suites -Order of operations +A _hook suite_ is a list of _hooks_ to run sequentially. A _hook_ is either an individual shell command or an executable script. -- Run setup hooks -- Increment version -- Change files -- Run pre-commit hooks -- commit and tag -- Run post-commit hooks +There are three hook suites: _setup, pre-commit,_ and _post-commit._ During the version increment process this is the order of operations: -## Setup Hooks +1. Run _setup_ hooks +2. Increment version +3. Change files +4. Run _pre-commit_ hooks +5. Commit and tag +6. Run _post-commit_ hooks + +!!! Note + + Don't confuse the _pre-commit_ and _post-commit_ hook suites with Git pre- and post-commit hooks. Those hook suites are named for their adjacency to the commit and tag operation. + + +## Configuration + +Configure each hook suite with the `setup_hooks`, `pre_commit_hooks`, or `post_commit_hooks` keys. + +Each suite takes a list of strings. The strings may be individual commands: ```toml title="Calling individual commands" [tool.bumpversion] @@ -30,28 +39,35 @@ setup_hooks = [ "git --version", "git config --list", ] +pre_commit_hooks = ["cat CHANGELOG.md"] +post_commit_hooks = ["echo Done"] ``` -or +or the path to an executable script: ```toml title="Calling a shell script" [tool.bumpversion] setup_hooks = ["path/to/setup.sh"] +pre_commit_hooks = ["path/to/pre-commit.sh"] +post_commit_hooks = ["path/to/post-commit.sh"] ``` -```bash title="path/to/setup.sh" -#!/usr/bin/env bash +!!! Note -git config --global user.email "bump-my-version@github.actions" -git config --global user.name "Testing Git" -git --version -git config --list -``` -### Environment + You can make a script executable using the following steps: + + 1. Add a [shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) line to the top like `#!/bin/bash` + 2. Run `chmod u+x path/to/script.sh` to set the executable bit + +## Hook Environments -- The existing OS environment is available +Each hook has these environment variables set when executed. -#### Date and time fields +### Inherited environment + +All environment variables set before bump-my-version was run are available. + +### Date and time fields ::: field-list @@ -61,9 +77,11 @@ git config --list `BVHOOK_UTCNOW` : The ISO-8601-formatted current local time in the UTC time zone. -#### Source code management fields +### Source code management fields + +!!! Note -These fields will only have values if the code is in a Git or Mercurial repository. + These fields will only have values if the code is in a Git or Mercurial repository. ::: field-list @@ -83,7 +101,7 @@ These fields will only have values if the code is in a Git or Mercurial reposito : The current branch name, converted to lowercase, with non-alphanumeric characters removed and truncated to 20 characters. For example, `feature/MY-long_branch-name` would become `featuremylongbranchn`. -#### Version fields +### Current version fields ::: field-list `BVHOOK_CURRENT_VERSION` @@ -93,4 +111,29 @@ These fields will only have values if the code is in a Git or Mercurial reposito : The current tag `BVHOOK_CURRENT_` - : Each version component defined by the [version configuration parsing regular expression](configuration/global.md#parse). The default configuration would have `current_major`, `current_minor`, and `current_patch` available. + : Each version component defined by the [version configuration parsing regular expression](configuration/global.md#parse). The default configuration would have `BVHOOK_CURRENT_MAJOR`, `BVHOOK_CURRENT_MINOR`, and `BVHOOK_CURRENT_PATCH` available. + + +### New version fields + +!!! Note + + These are not available in the _setup_ hook suite. + +::: field-list + `BVHOOK_NEW_VERSION` + : The new version serialized as a string + + `BVHOOK_NEW_TAG` + : The new tag + + `BVHOOK_NEW_` + : Each version component defined by the [version configuration parsing regular expression](configuration/global.md#parse). The default configuration would have `BVHOOK_NEW_MAJOR`, `BVHOOK_NEW_MINOR`, and `BVHOOK_NEW_PATCH` available. + +## Outputs + +The `stdout` and `stderr` streams are echoed to the console if you pass the `-vv` option. + +## Dry-runs + +Bump my version does not execute any hooks during a dry run. With the verbose output option it will state which hooks it would have run. From 04a98d00793e80bca2c11e354672168a8a6de9cd Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sun, 18 Aug 2024 16:26:29 -0500 Subject: [PATCH 9/9] Fix issues with environment test on windows --- tests/test_hooks/test_run_command.py | 4 +++- .../{test_run_setup_hooks.py => test_run_hook_suites.py} | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) rename tests/test_hooks/{test_run_setup_hooks.py => test_run_hook_suites.py} (98%) diff --git a/tests/test_hooks/test_run_command.py b/tests/test_hooks/test_run_command.py index 0f03d798..58f60cd0 100644 --- a/tests/test_hooks/test_run_command.py +++ b/tests/test_hooks/test_run_command.py @@ -1,4 +1,5 @@ import subprocess +import sys import pytest @@ -16,7 +17,8 @@ def test_runs_a_str_command(self): def test_can_access_env(self): """The command can access custom environment variables.""" - result = run_command("echo $TEST_ENV", environment={"TEST_ENV": "Hello"}) + cmd = "echo %TEST_ENV%" if sys.platform == "win32" else "echo $TEST_ENV" + result = run_command(cmd, environment={"TEST_ENV": "Hello"}) assert isinstance(result, subprocess.CompletedProcess) assert result.stdout == "Hello\n" diff --git a/tests/test_hooks/test_run_setup_hooks.py b/tests/test_hooks/test_run_hook_suites.py similarity index 98% rename from tests/test_hooks/test_run_setup_hooks.py rename to tests/test_hooks/test_run_hook_suites.py index 8202f1bf..17667edf 100644 --- a/tests/test_hooks/test_run_setup_hooks.py +++ b/tests/test_hooks/test_run_hook_suites.py @@ -4,7 +4,6 @@ import pytest from pytest import param from bumpversion.hooks import run_setup_hooks, run_pre_commit_hooks, run_post_commit_hooks -from bumpversion.versioning.models import Version from tests.conftest import get_config_data, get_semver