diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index f236374..b50b02a 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -7,7 +7,7 @@ jobs: strategy: matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] - platform: [ubuntu-20.04, macos-latest, windows-latest] + platform: [ubuntu-22.04, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1373277..08c31ba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -48,7 +48,7 @@ repos: - tomli - repo: https://github.com/commitizen-tools/commitizen - rev: v4.6.0 # automatically updated by Commitizen + rev: v4.6.3 # automatically updated by Commitizen hooks: - id: commitizen - id: commitizen-branch diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d2ab1e..dbad463 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## v4.6.3 (2025-05-07) + +### Fix + +- **changelog.py**: cross-platform path handling using os.path.join and modify the path linter and test parameter +- **changelog.py**: modify the CHANGELOG.md generated by cz bump --changelog to the right place + +## v4.6.2 (2025-05-05) + +### Fix + +- **docs**: fix url link and table formatting in the customization docs (#1399) + +## v4.6.1 (2025-05-05) + +### Fix + +- **commit**: use os.unlink to remove temp file + ## v4.6.0 (2025-04-13) ### Feat diff --git a/commitizen/__version__.py b/commitizen/__version__.py index db01fb2..de3842d 100644 --- a/commitizen/__version__.py +++ b/commitizen/__version__.py @@ -1 +1 @@ -__version__ = "4.6.0" +__version__ = "4.6.3" diff --git a/commitizen/commands/changelog.py b/commitizen/commands/changelog.py index 80a7265..3b8f43e 100644 --- a/commitizen/commands/changelog.py +++ b/commitizen/commands/changelog.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os import os.path from difflib import SequenceMatcher from operator import itemgetter @@ -32,21 +33,28 @@ class Changelog: raise NotAGitProjectError() self.config: BaseConfig = config + changelog_file_name = args.get("file_name") or cast( + str, self.config.settings.get("changelog_file") + ) + if not isinstance(changelog_file_name, str): + raise NotAllowed( + "Changelog file name is broken.\n" + "Check the flag `--file-name` in the terminal " + f"or the setting `changelog_file` in {self.config.path}" + ) + self.file_name = ( + os.path.join(str(self.config.path.parent), changelog_file_name) + if self.config.path is not None + else changelog_file_name + ) + self.encoding = self.config.settings["encoding"] self.cz = factory.commiter_factory(self.config) self.start_rev = args.get("start_rev") or self.config.settings.get( "changelog_start_rev" ) - 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( - "Changelog file name is broken.\n" - "Check the flag `--file-name` in the terminal " - f"or the setting `changelog_file` in {self.config.path}" - ) + self.changelog_format = get_changelog_format(self.config, self.file_name) self.incremental = args["incremental"] or self.config.settings.get( diff --git a/commitizen/commands/commit.py b/commitizen/commands/commit.py index abecb3b..93f0480 100644 --- a/commitizen/commands/commit.py +++ b/commitizen/commands/commit.py @@ -89,7 +89,7 @@ class Commit: subprocess.call(argv) with open(file_path) as temp_file: message = temp_file.read().strip() - file.unlink() + os.unlink(file.name) return message def __call__(self): diff --git a/docs/customization.md b/docs/customization.md index 5011330..31749d1 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -164,19 +164,22 @@ commitizen: | `change_type_map` | `dict` | `None` | (OPTIONAL) Dictionary mapping the type of the commit to a changelog entry | [jinja2]: https://jinja.palletsprojects.com/en/2.10.x/ -[changelog-spec]: https://commitizen-tools.github.io/commitizen/changelog/ +[changelog-spec]: https://commitizen-tools.github.io/commitizen/commands/changelog/ #### Detailed `questions` content | Parameter | Type | Default | Description | | ----------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `type` | `str` | `None` | The type of questions. Valid type: `list`, `input` and etc. [See More][different-question-types] | +| `type` | `str` | `None` | The type of questions. Valid types: `list`, `select`, `input` and etc. The `select` type provides an interactive searchable list interface. [See More][different-question-types] | | `name` | `str` | `None` | The key for the value answered by user. It's used in `message_template` | | `message` | `str` | `None` | Detail description for the question. | -| `choices` | `list` | `None` | (OPTIONAL) The choices when `type = list`. Either use a list of values or a list of dictionaries with `name` and `value` keys. Keyboard shortcuts can be defined via `key`. See examples above. | +| `choices` | `list` | `None` | (OPTIONAL) The choices when `type = list` or `type = select`. Either use a list of values or a list of dictionaries with `name` and `value` keys. Keyboard shortcuts can be defined via `key`. See examples above. | | `default` | `Any` | `None` | (OPTIONAL) The default value for this question. | | `filter` | `str` | `None` | (OPTIONAL) Validator for user's answer. **(Work in Progress)** | -| `multiline` | `bool` | `False` | (OPTIONAL) Enable multiline support when `type = input`. | +| `multiline` | `bool` | `False` | (OPTIONAL) Enable multiline support when `type = input`. | +| `use_search_filter` | `bool` | `False` | (OPTIONAL) Enable search/filter functionality for list/select type questions. This allows users to type and filter through the choices. | +| `use_jk_keys` | `bool` | `True` | (OPTIONAL) Enable/disable j/k keys for navigation in list/select type questions. Set to false if you prefer arrow keys only. | + [different-question-types]: https://github.com/tmbo/questionary#different-question-types #### Shortcut keys @@ -444,8 +447,8 @@ Commitizen gives you the possibility to provide your own changelog template, by: - providing one with your customization class - providing one from the current working directory and setting it: - - as [configuration][template-config] - - as `--template` parameter to both `bump` and `changelog` commands + - as [configuration][template-config] + - as `--template` parameter to both `bump` and `changelog` commands - either by providing a template with the same name as the default template By default, the template used is the `CHANGELOG.md.j2` file from the commitizen repository. diff --git a/docs/images/bump.gif b/docs/images/bump.gif new file mode 100644 index 0000000..0e97550 Binary files /dev/null and b/docs/images/bump.gif differ diff --git a/docs/images/demo.gif b/docs/images/demo.gif new file mode 100644 index 0000000..39dcdc9 Binary files /dev/null and b/docs/images/demo.gif differ diff --git a/docs/images/init.gif b/docs/images/init.gif new file mode 100644 index 0000000..f2cdf6b Binary files /dev/null and b/docs/images/init.gif differ diff --git a/docs/tutorials/monorepo_guidance.md b/docs/tutorials/monorepo_guidance.md index 817f923..792c8c2 100644 --- a/docs/tutorials/monorepo_guidance.md +++ b/docs/tutorials/monorepo_guidance.md @@ -77,5 +77,5 @@ changelog_pattern = "^(feat|fix)\\(library-b\\)(!)?:" #the pattern on types can A commit message looking like this, would be included: ``` -fix:(library-b) Some awesome message +fix(library-b): Some awesome message ``` diff --git a/poetry.lock b/poetry.lock index 073e1bc..cccd7a5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,14 +2,14 @@ [[package]] name = "argcomplete" -version = "3.5.3" +version = "3.6.2" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "argcomplete-3.5.3-py3-none-any.whl", hash = "sha256:2ab2c4a215c59fd6caaff41a869480a23e8f6a5f910b266c1808037f4e375b61"}, - {file = "argcomplete-3.5.3.tar.gz", hash = "sha256:c12bf50eded8aebb298c7b7da7a5ff3ee24dffd9f5281867dfe1424b58c55392"}, + {file = "argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591"}, + {file = "argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf"}, ] [package.extras] @@ -1965,4 +1965,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<4.0" -content-hash = "b0f8544806163bc0dddc039eb313f9d82119b845b3a19dedc381e9c88e8f4466" +content-hash = "e15b424a0569f939e297c8abfcf09753f1fbcc5b4ad891163cc0982accd3b372" diff --git a/pyproject.toml b/pyproject.toml index 6475ec0..92b28fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "commitizen" -version = "4.6.0" +version = "4.6.3" description = "Python commitizen client tool" authors = [{ name = "Santiago Fraire", email = "santiwilly@gmail.com" }] maintainers = [ @@ -19,7 +19,7 @@ dependencies = [ "tomlkit (>=0.5.3,<1.0.0)", "jinja2>=2.10.3", "pyyaml>=3.08", - "argcomplete >=1.12.1,<3.6", + "argcomplete >=1.12.1,<3.7", "typing-extensions (>=4.0.1,<5.0.0) ; python_version < '3.11'", "charset-normalizer (>=2.1.0,<4)", # Use the Python 3.11 and 3.12 compatible API: https://github.com/python/importlib_metadata#compatibility @@ -88,7 +88,7 @@ build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "4.6.0" +version = "4.6.3" tag_format = "v$version" version_files = [ "pyproject.toml:version", diff --git a/tests/commands/test_commit_command.py b/tests/commands/test_commit_command.py index 55751f6..3a92f5a 100644 --- a/tests/commands/test_commit_command.py +++ b/tests/commands/test_commit_command.py @@ -511,8 +511,6 @@ def test_manual_edit(editor, config, mocker: MockFixture, tmp_path): assert edited_message == test_message.strip() - temp_file.unlink() - @skip_below_py_3_13 def test_commit_command_shows_description_when_use_help_option( diff --git a/tests/test_changelog.py b/tests/test_changelog.py index df42b82..67ba273 100644 --- a/tests/test_changelog.py +++ b/tests/test_changelog.py @@ -1,13 +1,19 @@ +from __future__ import annotations + +import os import re from dataclasses import dataclass from pathlib import Path -from typing import Any, Optional +from typing import Any +from unittest.mock import Mock import pytest from jinja2 import FileSystemLoader from commitizen import changelog, git from commitizen.changelog_formats import ChangelogFormat +from commitizen.commands.changelog import Changelog +from commitizen.config import BaseConfig from commitizen.cz.conventional_commits.conventional_commits import ( ConventionalCommitsCz, ) @@ -1499,7 +1505,7 @@ def test_changelog_message_builder_hook_can_access_and_modify_change_type( def test_render_changelog_with_changelog_release_hook( gitcommits, tags, any_changelog_format: ChangelogFormat ): - def changelog_release_hook(release: dict, tag: Optional[git.GitTag]) -> dict: + def changelog_release_hook(release: dict, tag: git.GitTag | None) -> dict: release["extra"] = "whatever" return release @@ -1631,3 +1637,36 @@ def test_tags_rules_get_version_tags(capsys: pytest.CaptureFixture): captured = capsys.readouterr() assert captured.err.count("InvalidVersion") == 2 assert captured.err.count("not-a-version") == 2 + + +def test_changelog_file_name_from_args_and_config(): + mock_config = Mock(spec=BaseConfig) + mock_config.path.parent = "/my/project" + mock_config.settings = { + "name": "cz_conventional_commits", + "changelog_file": "CHANGELOG.md", + "encoding": "utf-8", + "changelog_start_rev": "v1.0.0", + "tag_format": "$version", + "legacy_tag_formats": [], + "ignored_tag_formats": [], + "incremental": True, + "changelog_merge_prerelease": True, + } + + args = { + "file_name": "CUSTOM.md", + "incremental": None, + "dry_run": False, + "unreleased_version": "1.0.1", + } + changelog = Changelog(mock_config, args) + assert os.path.normpath(changelog.file_name) == os.path.normpath( + os.path.join("/my/project", "CUSTOM.md") + ) + + args = {"incremental": None, "dry_run": False, "unreleased_version": "1.0.1"} + changelog = Changelog(mock_config, args) + assert os.path.normpath(changelog.file_name) == os.path.normpath( + os.path.join("/my/project", "CHANGELOG.md") + ) diff --git a/tests/test_cz_search_filter.py b/tests/test_cz_search_filter.py new file mode 100644 index 0000000..0e70e31 --- /dev/null +++ b/tests/test_cz_search_filter.py @@ -0,0 +1,76 @@ +import pytest + +from commitizen.config import TomlConfig +from commitizen.cz.customize import CustomizeCommitsCz + +TOML_WITH_SEARCH_FILTER = r""" +[tool.commitizen] +name = "cz_customize" + +[tool.commitizen.customize] +message_template = "{{change_type}}:{% if scope %} ({{scope}}){% endif %}{% if breaking %}!{% endif %} {{message}}" + +[[tool.commitizen.customize.questions]] +type = "select" +name = "change_type" +message = "Select the type of change you are committing" +use_search_filter = true +use_jk_keys = false +choices = [ + {value = "fix", name = "fix: A bug fix. Correlates with PATCH in SemVer"}, + {value = "feat", name = "feat: A new feature. Correlates with MINOR in SemVer"}, + {value = "docs", name = "docs: Documentation only changes"}, + {value = "style", name = "style: Changes that do not affect the meaning of the code"}, + {value = "refactor", name = "refactor: A code change that neither fixes a bug nor adds a feature"}, + {value = "perf", name = "perf: A code change that improves performance"}, + {value = "test", name = "test: Adding missing or correcting existing tests"}, + {value = "build", name = "build: Changes that affect the build system or external dependencies"}, + {value = "ci", name = "ci: Changes to CI configuration files and scripts"} +] + +[[tool.commitizen.customize.questions]] +type = "input" +name = "scope" +message = "What is the scope of this change? (class or file name): (press [enter] to skip)" + +[[tool.commitizen.customize.questions]] +type = "input" +name = "message" +message = "Write a short and imperative summary of the code changes: (lower case and no period)" +""" + + +@pytest.fixture +def config(): + return TomlConfig(data=TOML_WITH_SEARCH_FILTER, path="not_exist.toml") + + +def test_questions_with_search_filter(config): + """Test that questions are properly configured with search filter""" + cz = CustomizeCommitsCz(config) + questions = cz.questions() + + # Test that the first question (change_type) has search filter enabled + assert questions[0]["type"] == "select" + assert questions[0]["name"] == "change_type" + assert questions[0]["use_search_filter"] is True + assert questions[0]["use_jk_keys"] is False + + # Test that the choices are properly configured + choices = questions[0]["choices"] + assert len(choices) == 9 # We have 9 commit types + assert choices[0]["value"] == "fix" + assert choices[1]["value"] == "feat" + + +def test_message_template(config): + """Test that the message template is properly configured""" + cz = CustomizeCommitsCz(config) + template = cz.message( + { + "change_type": "feat", + "scope": "search", + "message": "add search filter support", + } + ) + assert template == "feat: (search) add search filter support"