Description
The towncrier-based changelog generator splits any changelog fragment that contains hard line breaks into one bullet per source line, and appends the [#NNNN] issue link to every single bullet. The result for any multi-paragraph fragment is N broken bullets all tagged with the same issue number, with sentences sheared in half across bullets.
This regressed in 808241d ("update towncrier template for multi-line entry"), which replaced the single-line render with a per-line loop in changelog/.template.jinja.
Reproduction
Source fragment (one entry, intentionally wrapped at ~72 columns for readability), at changelog/69031.fixed.md in the v3008.1 tag:
Fixed two distinct bugs in the `salt.engines.redis_sentinel` engine that
together prevented it from being usable. `start()` no longer raises
`AttributeError: 'dict_values' object has no attribute 'pop'` on Python 3
(the dict.values() result is now wrapped in `list(...)`). `Listener` and
`start()` now accept an optional `password` argument and forward it to
the redis client, allowing the engine to authenticate against a Sentinel
that requires AUTH; the default of `None` keeps existing configurations
working unchanged.
Rendered output in CHANGELOG.md at v3008.1 (the ### Fixed block):
- Fixed two distinct bugs in the `salt.engines.redis_sentinel` engine that [#69031](https://github.com/saltstack/salt/issues/69031)
- together prevented it from being usable. `start()` no longer raises [#69031](https://github.com/saltstack/salt/issues/69031)
- `AttributeError: 'dict_values' object has no attribute 'pop'` on Python 3 [#69031](https://github.com/saltstack/salt/issues/69031)
- (the dict.values() result is now wrapped in `list(...)`). `Listener` and [#69031](https://github.com/saltstack/salt/issues/69031)
- `start()` now accept an optional `password` argument and forward it to [#69031](https://github.com/saltstack/salt/issues/69031)
- the redis client, allowing the engine to authenticate against a Sentinel [#69031](https://github.com/saltstack/salt/issues/69031)
- that requires AUTH; the default of `None` keeps existing configurations [#69031](https://github.com/saltstack/salt/issues/69031)
- working unchanged. [#69031](https://github.com/saltstack/salt/issues/69031)
The same shape repeats in the v3008.1 changelog for #69032, #69033, #69035, #69037, #69038, #69039, #69129, #43718, and every other multi-line fragment in that release. It also pollutes the RPM pkg/rpm/salt.spec changelog block, which is regenerated from the same source.
Expected
A single bullet per fragment, rendered as a multi-line markdown list item, with the [#NNNN] link appended exactly once at the end:
- Fixed two distinct bugs in the `salt.engines.redis_sentinel` engine that
together prevented it from being usable. `start()` no longer raises
`AttributeError: 'dict_values' object has no attribute 'pop'` on Python 3
(the dict.values() result is now wrapped in `list(...)`). `Listener` and
`start()` now accept an optional `password` argument and forward it to
the redis client, allowing the engine to authenticate against a Sentinel
that requires AUTH; the default of `None` keeps existing configurations
working unchanged. [#69031](https://github.com/saltstack/salt/issues/69031)
Actual
N bullets per fragment, each ending in the same [#NNNN] link, with sentences cut in half at the source line breaks. Severity is cosmetic but the rendered CHANGELOG.md is genuinely hard to read and the broken bullets look unprofessional on the GitHub releases page.
Root cause
changelog/.template.jinja (introduced in 808241d, "update towncrier template for multi-line entry"):
{% for text, values in sections[""][category].items() %}
{% set lines = text.split('\n') %}
{% for line in lines %}
{% if line.startswith('- ') %}
{{ line | trim }} {{ values|join(', ') }}
{% else %}
- {{ line | trim }} {{ values|join(', ') }}
{% endif %}
{% endfor %}
{% endfor %}
The template splits the fragment text on \n, emits a - bullet for each line, and appends {{ values|join(', ') }} (the issue link list) to every line. The intent of that commit was to support entries that already include their own - sub-bullets, but the implementation made every soft line break in a fragment into a top-level bullet.
[tool.towncrier] in pyproject.toml does not set wrap, so towncrier itself is not re-wrapping the text -- the multiplication is purely a template bug.
Suggested fix
Render text once as a single bullet and only append the issue link to the last line. Something like:
{% for text, values in sections[""][category].items() %}
- {{ text | indent(2) }} {{ values|join(', ') }}
{% endfor %}
(plus whatever escape hatch is needed for the rare fragment that genuinely wants its own nested sub-bullets -- e.g. require the fragment author to write a properly-indented markdown list and treat the whole fragment as one bullet regardless).
Versions
Affects every release built with the post-808241d3154d template (3008.0, 3008.1, and any nightly built off master / 3008.x since 2025-05-27).
Description
The towncrier-based changelog generator splits any changelog fragment that contains hard line breaks into one bullet per source line, and appends the
[#NNNN]issue link to every single bullet. The result for any multi-paragraph fragment is N broken bullets all tagged with the same issue number, with sentences sheared in half across bullets.This regressed in 808241d ("update towncrier template for multi-line entry"), which replaced the single-line render with a per-line loop in
changelog/.template.jinja.Reproduction
Source fragment (one entry, intentionally wrapped at ~72 columns for readability), at
changelog/69031.fixed.mdin the v3008.1 tag:Rendered output in
CHANGELOG.mdat v3008.1 (the### Fixedblock):The same shape repeats in the v3008.1 changelog for
#69032,#69033,#69035,#69037,#69038,#69039,#69129,#43718, and every other multi-line fragment in that release. It also pollutes the RPMpkg/rpm/salt.specchangelog block, which is regenerated from the same source.Expected
A single bullet per fragment, rendered as a multi-line markdown list item, with the
[#NNNN]link appended exactly once at the end:Actual
N bullets per fragment, each ending in the same
[#NNNN]link, with sentences cut in half at the source line breaks. Severity is cosmetic but the renderedCHANGELOG.mdis genuinely hard to read and the broken bullets look unprofessional on the GitHub releases page.Root cause
changelog/.template.jinja(introduced in 808241d, "update towncrier template for multi-line entry"):The template splits the fragment text on
\n, emits a-bullet for each line, and appends{{ values|join(', ') }}(the issue link list) to every line. The intent of that commit was to support entries that already include their own-sub-bullets, but the implementation made every soft line break in a fragment into a top-level bullet.[tool.towncrier]inpyproject.tomldoes not setwrap, so towncrier itself is not re-wrapping the text -- the multiplication is purely a template bug.Suggested fix
Render
textonce as a single bullet and only append the issue link to the last line. Something like:(plus whatever escape hatch is needed for the rare fragment that genuinely wants its own nested sub-bullets -- e.g. require the fragment author to write a properly-indented markdown list and treat the whole fragment as one bullet regardless).
Versions
Affects every release built with the post-808241d3154d template (3008.0, 3008.1, and any nightly built off
master/3008.xsince 2025-05-27).