feat: add changelog_skip_prereleases setting to omit prerelease entries from changelog#1985
Conversation
Users can now mark a cz_customize input question as required by setting required = true in their config. The loader converts this into a questionary validate= callable that rejects empty/whitespace-only answers with the message 'This answer is required.' The required key is stripped before the question dict reaches questionary, so questionary never sees the unknown key. Closes commitizen-tools#1231 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When changelog_skip_prereleases = true is set, prerelease tags (rc, alpha, beta, dev) are omitted entirely from the generated changelog. Both cz changelog and cz bump --changelog honour the setting. When both changelog_skip_prereleases and changelog_merge_prerelease are set, skip takes precedence and prerelease entries are dropped. Closes commitizen-tools#1218 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1985 +/- ##
==========================================
- Coverage 98.23% 98.18% -0.06%
==========================================
Files 61 61
Lines 2779 2804 +25
==========================================
+ Hits 2730 2753 +23
- Misses 49 51 +2 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new configuration option to omit prerelease tags from generated changelogs, and also extends the customize question system with a required field while refining changelog file path resolution behavior.
Changes:
- Add
changelog_skip_prereleasessetting and plumb it throughTagRulesandcz changelog. - Add
required = truesupport fortype = "input"customize questions via a generatedvalidatecallback. - Adjust changelog output file path handling so CLI
--file-namestays CWD-relative while configchangelog_fileis anchored to the config directory.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| commitizen/defaults.py | Add changelog_skip_prereleases to settings + defaults. |
| commitizen/tags.py | Add skip_prereleases to TagRules and filter prerelease tags. |
| commitizen/commands/changelog.py | Wire skip_prereleases and change file-name path resolution logic. |
| commitizen/question.py | Extend InputQuestion with validate and required. |
| commitizen/cz/customize/customize.py | Implement required by injecting a validator and stripping the key. |
| tests/test_conf.py | Update expected settings dictionaries with the new key. |
| tests/test_changelog.py | Add unit tests for skip-prereleases + refactor file-name resolution test. |
| tests/commands/test_changelog_command.py | Add E2E tests for skip-prereleases config behavior. |
| tests/test_cz_customize.py | Add unit tests for required questions behavior. |
| docs/commands/changelog.md | Document changelog_skip_prereleases and clarify path resolution. |
| docs/customization/config_file.md | Document required for input questions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| file_name_from_args: str | None = arguments.get("file_name") or None | ||
| file_name_from_config: str | None = self.config.settings.get("changelog_file") | ||
| changelog_file_name = file_name_from_args or file_name_from_config | ||
| 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 = ( | ||
| Path(self.config.path.parent, changelog_file_name).as_posix() | ||
| if self.config.path is not None | ||
| else changelog_file_name | ||
| ) | ||
| if file_name_from_args: | ||
| # Explicit CLI argument — keep relative to cwd (or use as-is if absolute). | ||
| self.file_name = file_name_from_args | ||
| elif self.config.path is not None: | ||
| # From configuration — anchor to the config file's directory. | ||
| self.file_name = Path( | ||
| self.config.path.parent, changelog_file_name | ||
| ).as_posix() | ||
| else: | ||
| self.file_name = changelog_file_name |
| self.tag_rules = TagRules( | ||
| scheme=self.scheme, | ||
| tag_format=arguments.get("tag_format") | ||
| or self.config.settings["tag_format"], | ||
| legacy_tag_formats=self.config.settings["legacy_tag_formats"], | ||
| ignored_tag_formats=self.config.settings["ignored_tag_formats"], | ||
| merge_prereleases=arguments.get("merge_prerelease") | ||
| or self.config.settings["changelog_merge_prerelease"], | ||
| skip_prereleases=bool( | ||
| self.config.settings.get("changelog_skip_prereleases") | ||
| ), | ||
| ) |
| def include_in_changelog(self, tag: GitTag) -> bool: | ||
| """Check if a tag should be included in the changelog""" | ||
| try: | ||
| version = self.extract_version(tag) | ||
| except InvalidVersion: | ||
| return False | ||
| return not (self.merge_prereleases and version.is_prerelease) | ||
| if version.is_prerelease: | ||
| # skip_prereleases wins over merge_prereleases when both are set | ||
| if self.skip_prereleases: | ||
| return False | ||
| if self.merge_prereleases: | ||
| return False | ||
| return True |
| assert "v1.2.0" in versions | ||
| assert "v1.0.0" in versions | ||
|
|
||
|
|
| assert "more stuff" in out | ||
|
|
||
|
|
||
| @pytest.mark.usefixtures("tmp_commitizen_project") |
| for raw in raw_questions: | ||
| q: dict[str, Any] = dict(raw) | ||
| if q.get("type") == "input" and q.pop("required", False): | ||
| q["validate"] = _required_validator | ||
| result.append(q) # type: ignore[arg-type] |
|
Closing this PR per maintainer-triage policy: feature-request issues should sit with the maintainers for design / scope review before any implementation lands. The issue's The implementation itself is preserved on the branch ( This PR is being closed so that #1218 reverts to "awaiting maintainer triage / decision" rather than "PR pending review", which is the correct state for a feature request. Closed via the round-2 triage cleanup in #1965. |
Description
Adds a new changelog_skip_prereleases configuration setting (default alse). When enabled, all prerelease release entries (tags whose version parses as a prerelease -
c, �lpha, �eta, dev, etc.) are omitted from the generated changelog entirely.
Closes #1218
Why
Currently, users who ship prereleases see cluttered changelogs like:
`
0.1.0-b0 (2024-08-20)
0.1.0-a0 (2024-08-20)
Feat
0.1.0 (2024-08-20)
`
The only existing option (--merge-prerelease) collapses prereleases into the next stable entry. There is no way to drop them silently. This PR adds that opt-in.
What changed
How it works
TagRules.include_in_changelog is the single gate that decides whether a tag produces a release block in the changelog tree. When skip_prereleases=True the method returns False for any tag whose parsed version satisfies �ersion.is_prerelease, before the merge-prerelease check runs.
Backward compatibility
Checklist
Was generative AI tooling used to co-author this PR?
Generated-by: Claude following the guidelines
Code Changes
Documentation Changes
Expected Behavior
Steps to Test This Pull Request
`�ash
Clone and set up
git clone https://github.com/bearomorphism/commitizen && cd commitizen
git checkout fix/1218-changelog-skip-prereleases
uv sync --frozen --group base --group test --group linters
Run targeted tests
uv run pytest tests/test_changelog.py::test_generate_tree_from_commits_skip_prereleases
uv run pytest tests/test_changelog.py::test_skip_prereleases_wins_over_merge_prereleases
uv run pytest tests/commands/test_changelog_command.py::test_changelog_config_flag_skip_prereleases
uv run pytest tests/commands/test_changelog_command.py::test_changelog_config_skip_prereleases_wins_over_merge_prerelease
`
Additional Context
Related to #1965 (merge-prerelease) which was the original implementation of prerelease handling in changelogs.