Module continuous_delivery_scripts.utils.changelog

Helpers for generating and repairing changelogs.

Functions

def generate_changelog(version: str | None) ‑> None
Expand source code
def generate_changelog(version: Optional[str]) -> None:
    """Builds the changelog with Towncrier and normalises Markdown output when needed."""
    project_config_path = configuration.get_value(ConfigurationVariable.PROJECT_CONFIG)
    with _create_towncrier_workaround_config(project_config_path) as workaround_config_path:
        # See Towncrier's build command docs:
        # https://towncrier.readthedocs.io/en/stable/cli.html#build
        command = ["towncrier", "build", "--yes", "--name", "", "--version", str(version)]
        if workaround_config_path:
            command.extend(["--config", workaround_config_path])

        with cd(os.path.dirname(project_config_path)):
            subprocess.check_call(command)

    # FIXME: Remove this workaround when https://github.com/twisted/towncrier/issues/758 is fixed.
    normalise_markdown_release_headings(version)

Builds the changelog with Towncrier and normalises Markdown output when needed.

def normalise_markdown_release_headings(version: str | None) ‑> None
Expand source code
def normalise_markdown_release_headings(version: Optional[str]) -> None:
    """Promote the latest markdown release title to a heading when Towncrier omits it."""
    if not version:
        return

    changelog_path = Path(str(configuration.get_value(ConfigurationVariable.CHANGELOG_FILE_PATH)))
    if changelog_path.suffix.lower() not in _MARKDOWN_CHANGELOG_SUFFIXES or not changelog_path.exists():
        return

    original = changelog_path.read_text(encoding="utf8")
    lines = original.splitlines()
    version_index = next(
        (index for index, line in enumerate(lines) if line.startswith(f"{version} ") and not line.startswith("#")),
        None,
    )
    if version_index is None:
        return

    next_release_index = next(
        (
            index
            for index in range(version_index + 1, len(lines))
            if _RELEASE_TITLE_PATTERN.match(lines[index]) and not lines[index].startswith("#")
        ),
        len(lines),
    )
    section_indexes = [index for index in range(version_index + 1, next_release_index) if lines[index].startswith("# ")]
    if not section_indexes:
        return

    lines[version_index] = f"# {lines[version_index]}"
    for index in section_indexes:
        lines[index] = f"#{lines[index]}"

    rendered = "\n".join(lines) + ("\n" if original.endswith("\n") else "")
    if rendered != original:
        changelog_path.write_text(rendered, encoding="utf8")

Promote the latest markdown release title to a heading when Towncrier omits it.