From f10ef61b5fbd42376145fff7278caebaebd510c8 Mon Sep 17 00:00:00 2001 From: Tim Hsiung Date: Sat, 9 May 2026 19:36:01 +0800 Subject: [PATCH 1/2] fix(changelog): clearer error when changelog format cannot be inferred When `changelog_format` was unset and the changelog filename had an unknown extension (e.g. `CHANGELOG.md.sections`), commitizen raised `Unknown changelog format 'None'` -- which is confusing because the user never set anything to `None`. Improve the two failure paths: * If the user set `changelog_format` to an unknown value, the error echoes that value and lists the registered formats. * If the format had to be inferred from the filename and the inference failed, the error names the offending filename, points to the `changelog_format` setting and lists the registered formats. No behaviour change for the success paths. Closes #894 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- commitizen/changelog_formats/__init__.py | 15 ++++++++++++-- tests/test_changelog_formats.py | 26 ++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/commitizen/changelog_formats/__init__.py b/commitizen/changelog_formats/__init__.py index 26e697cad..428a3c39e 100644 --- a/commitizen/changelog_formats/__init__.py +++ b/commitizen/changelog_formats/__init__.py @@ -67,7 +67,9 @@ def get_changelog_format( """ Get a format from its name - :raises FormatUnknown: if a non-empty name is provided but cannot be found in the known formats + :raises ChangelogFormatUnknown: if a non-empty name is provided but cannot + be found in the known formats, or if the filename's extension cannot + be matched to a known format and no ``changelog_format`` is set. """ name: str | None = config.settings.get("changelog_format") format = ( @@ -75,7 +77,16 @@ def get_changelog_format( ) if not format: - raise ChangelogFormatUnknown(f"Unknown changelog format '{name}'") + known = ", ".join(sorted(KNOWN_CHANGELOG_FORMATS)) or "(none registered)" + if name: + raise ChangelogFormatUnknown( + f"Unknown changelog format '{name}'. Known formats: {known}." + ) + raise ChangelogFormatUnknown( + "Cannot infer changelog format from filename " + f"'{filename}'. Set the `changelog_format` setting " + f"explicitly. Known formats: {known}." + ) return format(config) diff --git a/tests/test_changelog_formats.py b/tests/test_changelog_formats.py index 6ffbc8dc3..388946cb9 100644 --- a/tests/test_changelog_formats.py +++ b/tests/test_changelog_formats.py @@ -53,11 +53,33 @@ def test_get_format_empty_filename(config: BaseConfig, filename: str | None): @pytest.mark.parametrize("filename", [None, ""]) def test_get_format_empty_filename_no_setting(config: BaseConfig, filename: str | None): config.settings["changelog_format"] = None - with pytest.raises(ChangelogFormatUnknown): + with pytest.raises(ChangelogFormatUnknown) as excinfo: get_changelog_format(config, filename) + # The error message should hint at setting `changelog_format` and list + # the known formats so users on non-standard file extensions know what + # to do (#894). + msg = str(excinfo.value) + assert "changelog_format" in msg + assert "Known formats" in msg @pytest.mark.parametrize("filename", ["extensionless", "file.unknown"]) def test_get_format_unknown(config: BaseConfig, filename: str | None): - with pytest.raises(ChangelogFormatUnknown): + with pytest.raises(ChangelogFormatUnknown) as excinfo: get_changelog_format(config, filename) + # Same hint when the filename extension is unknown. + msg = str(excinfo.value) + assert "changelog_format" in msg + assert "Known formats" in msg + + +def test_get_format_unknown_name_lists_known_formats(config: BaseConfig): + """Regression test for #894: when ``changelog_format`` is set to an + unknown value, the error must list the registered formats so users + can self-correct.""" + config.settings["changelog_format"] = "definitely-not-a-format" + with pytest.raises(ChangelogFormatUnknown) as excinfo: + get_changelog_format(config) + msg = str(excinfo.value) + assert "definitely-not-a-format" in msg + assert "Known formats" in msg From 931877e66f584ae8d02856f61e12b998df99c7f0 Mon Sep 17 00:00:00 2001 From: Tim Hsiung <26526132+bearomorphism@users.noreply.github.com> Date: Sat, 9 May 2026 22:27:27 +0800 Subject: [PATCH 2/2] fix(changelog_formats): raise on explicit invalid format even with valid filename Refactor get_changelog_format with explicit branching: when changelog_format is set, look it up directly and raise ChangelogFormatUnknown if missing, regardless of whether the filename extension is recognized. Previously the 'or _guess_changelog_format' fallback could silently ignore an invalid explicit configuration when the filename had a known extension. Add a regression test exercising changelog_format='invalidformat' with filename='CHANGELOG.md'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- commitizen/changelog_formats/__init__.py | 16 +++++++++------- tests/test_changelog_formats.py | 9 +++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/commitizen/changelog_formats/__init__.py b/commitizen/changelog_formats/__init__.py index 428a3c39e..d9f9cd079 100644 --- a/commitizen/changelog_formats/__init__.py +++ b/commitizen/changelog_formats/__init__.py @@ -72,23 +72,25 @@ def get_changelog_format( be matched to a known format and no ``changelog_format`` is set. """ name: str | None = config.settings.get("changelog_format") - format = ( - name and KNOWN_CHANGELOG_FORMATS.get(name) or _guess_changelog_format(filename) - ) + known = ", ".join(sorted(KNOWN_CHANGELOG_FORMATS)) or "(none registered)" - if not format: - known = ", ".join(sorted(KNOWN_CHANGELOG_FORMATS)) or "(none registered)" - if name: + if name: + format_cls = KNOWN_CHANGELOG_FORMATS.get(name) + if format_cls is None: raise ChangelogFormatUnknown( f"Unknown changelog format '{name}'. Known formats: {known}." ) + return format_cls(config) + + format_cls = _guess_changelog_format(filename) + if format_cls is None: raise ChangelogFormatUnknown( "Cannot infer changelog format from filename " f"'{filename}'. Set the `changelog_format` setting " f"explicitly. Known formats: {known}." ) - return format(config) + return format_cls(config) def _guess_changelog_format(filename: str | None) -> type[ChangelogFormat] | None: diff --git a/tests/test_changelog_formats.py b/tests/test_changelog_formats.py index 388946cb9..a62ac6ad7 100644 --- a/tests/test_changelog_formats.py +++ b/tests/test_changelog_formats.py @@ -83,3 +83,12 @@ def test_get_format_unknown_name_lists_known_formats(config: BaseConfig): msg = str(excinfo.value) assert "definitely-not-a-format" in msg assert "Known formats" in msg + + +def test_get_format_unknown_name_with_known_filename_raises(config: BaseConfig): + config.settings["changelog_format"] = "invalidformat" + with pytest.raises(ChangelogFormatUnknown) as excinfo: + get_changelog_format(config, "CHANGELOG.md") + msg = str(excinfo.value) + assert "invalidformat" in msg + assert "Known formats" in msg