fix(release): do not end a changelog on a blank line
An entry carries a trailing blank line so that a `## [version]` below it is separated. Two of the four placements in _insert_after_h1 put the entry at the end of the file instead, where that separator becomes a blank line after the final newline and markdownlint reports MD012. Both now pass the entry through _at_end_of_file. Only the first entry a file ever receives is affected, which is why it appears on a repository's opening release and never again: every later entry is inserted above an existing H2, and there the separator is correct. The base-images repository hit it on 1.0.0 and its lint job went red on the first push it ever had. The other two placements are deliberately untouched. In the legacy branch the old content follows the entry, and before the first H2 the previous release section does, so the blank line has to stay. Verified: four tests, one per placement, assert both that no document holds three consecutive newlines and that it ends on exactly one; two of them also assert the separator survives where it belongs, which fails if the trim is applied too widely. 74 tests pass under tests/unit/pkgmgr/actions/release. Unrelated and pre-existing: test_update_changelog_transforms_heading_and_inline_code fails wherever markdownlint-cli2 is installed, because transform_changelog_message renders a leading `#` as bold and markdownlint calls that MD036. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,11 @@ H1_RE = re.compile(r"^#\s+\S", re.MULTILINE)
|
||||
H2_RE = re.compile(r"^##\s+\S", re.MULTILINE)
|
||||
|
||||
|
||||
def _at_end_of_file(entry: str) -> str:
|
||||
"""Return *entry* without the blank line that separates it from a next one."""
|
||||
return entry.rstrip("\n") + "\n"
|
||||
|
||||
|
||||
def _insert_after_h1(existing: str, entry: str) -> str:
|
||||
"""Place *entry* after the H1 (and any intro prose), above the first H2.
|
||||
|
||||
@@ -26,7 +31,7 @@ def _insert_after_h1(existing: str, entry: str) -> str:
|
||||
``## ``) is preserved: *entry* is prepended unchanged.
|
||||
"""
|
||||
if not existing.strip():
|
||||
return f"# Changelog\n\n{entry}"
|
||||
return f"# Changelog\n\n{_at_end_of_file(entry)}"
|
||||
|
||||
if not H1_RE.search(existing):
|
||||
# Legacy layout: file starts with `## [version]` and has no H1.
|
||||
@@ -43,7 +48,7 @@ def _insert_after_h1(existing: str, entry: str) -> str:
|
||||
if existing.endswith("\n\n")
|
||||
else ("\n" if existing.endswith("\n") else "\n\n")
|
||||
)
|
||||
return f"{existing}{suffix}{entry}"
|
||||
return f"{existing}{suffix}{_at_end_of_file(entry)}"
|
||||
|
||||
# Insert new entry just before the first H2.
|
||||
head = existing[: h2_match.start()].rstrip("\n") + "\n\n"
|
||||
|
||||
41
tests/unit/pkgmgr/actions/release/test_changelog_md.py
Normal file
41
tests/unit/pkgmgr/actions/release/test_changelog_md.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from pkgmgr.actions.release.files.changelog_md import _insert_after_h1
|
||||
|
||||
ENTRY = "## [1.0.0] - 2026-09-18\n\nOfficial Release\n\n"
|
||||
|
||||
|
||||
class TestInsertAfterH1(unittest.TestCase):
|
||||
def _assert_lint_clean(self, document: str) -> None:
|
||||
self.assertNotIn(
|
||||
"\n\n\n",
|
||||
document,
|
||||
f"MD012: multiple consecutive blank lines in\n{document!r}",
|
||||
)
|
||||
self.assertTrue(
|
||||
document.endswith("\n") and not document.endswith("\n\n"),
|
||||
f"MD012: blank line at end of file in\n{document!r}",
|
||||
)
|
||||
|
||||
def test_an_empty_changelog_gets_one_trailing_newline(self) -> None:
|
||||
self._assert_lint_clean(_insert_after_h1("", ENTRY))
|
||||
|
||||
def test_a_first_entry_under_a_bare_h1_gets_one_trailing_newline(self) -> None:
|
||||
self._assert_lint_clean(_insert_after_h1("# Changelog\n", ENTRY))
|
||||
|
||||
def test_a_second_entry_stays_separated_from_the_first(self) -> None:
|
||||
existing = "# Changelog\n\n## [0.9.0] - 2026-09-01\n\nOlder\n"
|
||||
document = _insert_after_h1(existing, ENTRY)
|
||||
self._assert_lint_clean(document)
|
||||
self.assertIn("Official Release\n\n## [0.9.0]", document)
|
||||
|
||||
def test_a_legacy_headerless_changelog_gains_an_h1(self) -> None:
|
||||
document = _insert_after_h1("## [0.9.0] - 2026-09-01\n\nOlder\n", ENTRY)
|
||||
self._assert_lint_clean(document)
|
||||
self.assertTrue(document.startswith("# Changelog\n\n## [1.0.0]"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user