The changelog in platform engineering

At my current job, we maintain a package that is used by developers in their own codebases to use a database service on the platform. Like any package or artifact that is released to then be used by application developers, this requires a clear release with a semantic version that communicates what users can expect from it: Does it contain only fixes (patch), or are there new features in the release (minor)? Most importantly, do users (potentially) need to change how they use the package when upgrading (major)?

Ideally, the version communicates the category of the release: patch (fixes only), minor (new features), or major (breaking changes), and the details are found in a changelog. Without a changelog, your users have a hard time figuring out what to expect from a new release.

Can’t I just write a changelog myself?

If your users are normal end users, you can have the product and/or UX people write a changelog that is aimed at the end-user experience. But when your users are developers who integrate your package with their own application, completeness is key! You cannot know all the ways and context in which developers use your software, so it is important to be careful and complete and mark changes that are potentially breaking as such. If you forget to mention a change, that could waste a lot of developer time on debugging.

The reason why you could forget relevant changes, is because the changelog is fundamentally a duplication. In a changelog, you describe the changes to the software since the previous release. But as a platform engineer, you should already have this information somewhere else: in the git log!

If you follow conventional commits, your commits already contain this information:

feat(api)!: Make retentionPeriod a required property on orderDetails
fix: OOM on submission of large orderList
chore: Bump Python to 3.14.0

Thus, I needed to generate a changelog (and a next version number) based on the conventional commits in the git log between the last release and now. Seems like it should be a common problem.

What if you’re not on Github?

To my surprise, I saw that the most commonly used tool for this problem does not use plain Git: release-please is built around the Github API. We use a less commonly used forge, so this did not work for us. The alternative release-it has bindings for the GitLab and GitLab APIs, but also has a way to generate a changelog from pure Git. Since we do not directly depend on any NodeJS projects in the team I work, I looked a bit further for a self-contained binary that really only does changelog and version generation from git.

Git Cliff

Git Cliff is precisely what I was looking for:

  • only does changelog and semver generation from git
  • self-contained binary
  • sensible defaults, works out of the box
  • very customizable
  • great documentation
  • written in Rust and available from many repositories

To generate a configuration file with the defaults and a lot of helpful comments, run git-cliff --init.

The cliff.toml default configuration has a section that looks like:

commit_parsers = [
    { message = "^feat", group = "<!-- 0 -->🚀 Features" },
    { message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
    { message = "^doc", group = "<!-- 3 -->📚 Documentation" },
    ...
]

You can add or remove parsers easily: the message field is the regex pattern that is matched to the commit message, and the number in the group determines the order of the sections in the generated changelog.

Some useful commands:

# Generate the changelog file by running:
git-cliff -o CHANGELOG.md

# Get the last released (tagged) version:
git-cliff --context | jq '.[0].version'

# Get the version for the next release (based on what has been committed since)
git-cliff --bumped-version

# Generate the changelog for that next release
git-cliff --bump -o CHANGELOG.md

Whichever Git Forge you use, these commands should allow you to create a changelog and release process that works for your platform! Happy crafting!