diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..a19ade077d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +CHANGELOG.md merge=union diff --git a/CHANGELOG.d/20260911-changelog-union-contract.md b/CHANGELOG.d/20260911-changelog-union-contract.md new file mode 100644 index 0000000000..fc2cf80e2b --- /dev/null +++ b/CHANGELOG.d/20260911-changelog-union-contract.md @@ -0,0 +1,6 @@ +## Changed + +- `CHANGELOG.md`에 `merge=union` 속성을 추가하고, union 병합이 제목을 코드 + 펜스 안으로 삼키거나 빈 섹션을 만드는 경우를 감지하는 구조 계약을 + 추가했습니다. 현재 changelog의 무펜스 구조를 유지하면서 독립 PR의 + prepend 충돌을 줄입니다. diff --git a/tests/test_changelog_structure_contract.py b/tests/test_changelog_structure_contract.py new file mode 100644 index 0000000000..8a754a10d1 --- /dev/null +++ b/tests/test_changelog_structure_contract.py @@ -0,0 +1,37 @@ +"""Structural guardrails for the union-merged changelog.""" + +from pathlib import Path +import re + + +ROOT = Path(__file__).resolve().parents[1] +CHANGELOG = ROOT / "CHANGELOG.md" +ATTRIBUTES = ROOT / ".gitattributes" + + +def test_changelog_uses_union_merge_driver() -> None: + """Prepending independent release notes must not create pairwise conflicts.""" + assert "CHANGELOG.md merge=union" in ATTRIBUTES.read_text(encoding="utf-8") + + +def test_changelog_sections_are_nonempty_and_fences_are_balanced() -> None: + """Union merging must not silently swallow a heading inside a code fence.""" + lines = CHANGELOG.read_text(encoding="utf-8").splitlines() + headings = [index for index, line in enumerate(lines) if line.startswith("### ")] + assert headings + assert headings[0] == 0 + + in_fence = False + fence_count = 0 + for line in lines: + if line.startswith("```"): + in_fence = not in_fence + fence_count += 1 + if line.startswith("### "): + assert not in_fence + assert fence_count % 2 == 0 + + for start, end in zip(headings, [*headings[1:], len(lines)]): + body = [line for line in lines[start + 1 : end] if line.strip()] + assert body, f"empty changelog section at line {start + 1}" + assert re.match(r"^### .+", lines[start])