Changelog automation

Sunday 29 September 2024

I have two main approaches for producing changelogs, but both are based on the same principles: make it convenient for the author to create them, then make it possible to use the information automatically to benefit the readers.

The first way is with a tool such as scriv, which I wrote, but which was inspired by previous similar tools like towncrier and CPython’s blurb. They let you write your changelog one entry at a time in the same pull request as the product change itself. The entries are individual uniquely named files that are collected together when a release is made. This avoids merge conflicts that will happen if a number of developers have to all edit the same changelog file.

The second way I maintain a changelog is how I do it for coverage.py. This predates scriv, and is more custom-coded, so I’ll walk through the steps. Maybe you will be inspired to add bits to other tooling.

I hand-edit a CHANGES.rst file. An entry there might look like this:

CHANGES.rst

- Fix: we failed calling
  :func:`runpy.run_path <python:runpy.run_path>`, as described
  in `issue 1234`_.  This is now fixed, thanks to `Debbie Developer
  <pull 2345_>`_.  Details are on the :ref:`configuration page
  <config_report_format>`.

.. _issue 1234: https://github.com/nedbat/coveragepy/issues/1234
.. _pull 2345: https://github.com/nedbat/coveragepy/pull/2345

This lets me use semantic linking mechanisms. GitHub displays .rst files, but doesn’t understand the :ref:-style of links unfortunately.

The changelog is part of the docs for the project, pulled into the docs/ tree with a Sphinx directive. The :end-before: lets me have end-page content in CHANGES.rst that don’t appear in the docs:

doc/changes.rst

.. include:: ../CHANGES.rst
    :end-before: scriv-end-here

It’s great when researching a bug fix in other projects to see an issue closed with a comment about the commit that fixed it. Even better is when the issue mentions what release first had the fix. I automate that process for coverage.py.

To do that and a few other things, I have some custom tooling. It’s a bit baroque because it grew over time, but it suits my purposes. First I need to get the changelog into a more easily understood form. Sphinx has a little-known feature to produce .rst files as output. It sounds paradoxical, but the benefit is that all links are reduced to their simplest form. The entry above becomes:

tmp/changes.rst

*  Fix: we failed calling
   https://docs.python.org/3/library/runpy.html#runpy.run_path, as
   described in `issue 1234
   <https://github.com/nedbat/coveragepy/issues/1234>`_.  This is now
   fixed, thanks to `Debbie Developer
   <https://github.com/nedbat/coveragepy/pull/2345>`_.  Details are on
   the `configuration page <config.rst#config-report-format>`_.

Then pandoc converts it to Markdown and my parse_relnotes.py creates a JSON file to make it easy to find entries for each version:

[
    {
        "version": "7.6.1",
        "text": "-   Fix: coverage used to fail when measuring code using ...",
        "prerelease": false,
        "when": "2024-08-04"
    },
    ...

Finally(!) comment_on_fixes.py gets the latest release from the JSON file, regexes it for GitHub URLs in the text, and adds a comment to closed issues and merged pull requests:

This is now released as part of [coverage 7.x.y](https://pypi.org/project/coverage/7.x.y).

The other automated output from my CHANGES.rst file is a GitHub release. GitHub releases are both convenient and problematic. I don’t like the idea of authoring content on GitHub that is only available on GitHub. The history of my project is an important part of my project, so I want the source of truth to be a version-controlled text file in my source distribution. But people want to see GitHub releases. So I author in CHANGES.rst, but publish to GitHub releases.

Using github_releases.py I automatically generate a GitHub release from the JSON file. This was useful enough that I added a github-release command to scriv to do a similar thing, but coverage.py still has the custom code to take advantage of the rst link simplifications I showed above.

One of the things I don’t like about GitHub releases is that they always have “Assets” appended to the end, with links to .zip and .tar.gz snapshots of the repo. Those aren’t the right way to get the package, so I include the link to the PyPI page and the correct command to install the package.

Describing all this, it sounds complicated, and I guess it is. I like being able to publish information to people who want it, and this automation accomplishes that.

Comments

[gravatar]

“I don’t like the idea of authoring content on GitHub that is only available on GitHub. The history of my project is an important part of my project, so I want the source of truth to be a version-controlled text file in my source distribution. But people want to see GitHub releases. So I author in CHANGES.rst, but publish to GitHub releases.”

That’s such a good point and such a Solomonic solution! Ditto re: linking to the canonical releases instead of attaching files.

Add a comment:

Ignore this:
Leave this empty:
Name is required. Either email or web are required. Email won't be displayed and I won't spam you. Your web site won't be indexed by search engines.
Don't put anything here:
Leave this empty:
Comment text is Markdown.