Introducing the changelog-updater Action

• 10 min read

I've recently published a new GitHub Action to solve a seemingly mundane task: updating the changelog file with the latest release notes.
The Action has the boring name of "changelog-updater" and can be found on GitHub.

This post will explain why I think keeping a separate changelog is important and how you can use the Action in your own projects. I will also explain my approach of automatically generating release notes by using a combination of labels on pull requests and a GitHub Action called release-drafter.

But first, why you should keep a changelog as a project maintainer.

Why you should keep a separate changelog

If you write code in PHP or JavaScript – like I do – you are probably using packages or even entire frameworks to help you accomplish your goals. Some packages get regular updates. Others are updated irregularly.

What have popular packages in common? They have a CHANGELOG.md file in their repository. Inside of it, you (should) find every significant change to the code, grouped by version.

Unfortunately, not all projects keep their CHANGELOG.md file up-to-date with their releases and release notes. I've come across many projects in the past year, where the changelog was missing several of the latest releases. I then had to browse through several GitHub releases pages to find the releases that were relevant to my project. Quite a hassle!

Changelogs also come in many different formats.
Some projects write vague description of what has changed; without providing a link to a pull request or a compare view.
Others group their changes by type. Headings like "Added", "Changed" or "Removed" indicate to the reader what new features have been added or have been removed.

Changelogs written in the format I mentioned first (vague description; no links) are the ones I dread the most.
At work, we let Dependabot upgrade our dependencies on a monthly basis 1. Reviewing pull requests concerning updates to dependencies with these changelogs take a lot of time and energy: I have to manually browse through the Git repository to find the relevant commits or look through recently closed pull requests to find the one that introduced a new feature or a breaking change.

Thankfully, GitHub recently overhauled the "Releases experience" and made it much easier to automatically generate release notes based on the merged pull requests between two releases.
I hope maintainers use this feature frequently in the future and add the notes to the changelog as well. And that we as consumers waste less time finding the right pull request which documents a change.

Personally though, I really like the other format I mentioned. The one with the "Added", "Changed" and "Removed" headings. At a glance, I immediately see what a new release means for my project and if I have to update an implementation.

These headings are based on the aptly named "Keep A Changelog"-format which I started using in all my personal and professional projects. (It's also the format "changelog-updater" works really well with. More about this later.)

By now I hope it has become clear, why I like separate changelog files. Here is a quick summary:

  • All changes of a project are listed in a single file.
  • Changelog can easily be searched through by hitting Ctrl+F
  • If needed, pull requests or issues can be referenced and linked to

To make it easier to keep CHANGELOG.md and the release notes you write on GitHub in sync, I created the "changelog-updater" Action. It takes a version number and release notes and adds them to your CHANGELOG.md file at the right place.

With the reasoning out of the way, let me show you how you can add the Action to your projects.

(If you want to read more on why I think so strongly about changelogs, you can read my in depth opinion: Dear Maintainer, Please Keep a Changelog)

Prepare your repository

First you need to have a CHANGELOG.md file. To get started with "changelog-updater" all you need is one that looks like this:

# Changelog

All notable changes to this project will be documented in this file.

When the next release is published, "changelog-updater" will append the new notes at the end of the document. If your project already had at least one release, it's probably better to add the past releases to the changelog file. Your file might looks like this then.

# Changelog

All notable changes to this project will be documented in this file.

## v1.0.0 - 2021-11-10

- Initial Release
- …

Next time a release is published, the Action will put the notes above the previous release (v1.0.0).


Next you need a new GitHub Actions workflow which runs whenever a new release on GitHub is published.

Below is an example worfklow you can add to your repository. Store it in ./github/workflows/update-changelog.yaml

name: "Update Changelog"

on:
  release:
    types: [released]

jobs:
  update:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2
        with:
          ref: main

      - name: Update Changelog
        uses: stefanzweifel/changelog-updater-action@v1
        with:
          release-notes: ${{ github.event.release.body }}
          latest-version: ${{ github.event.release.name }}

      - name: Commit updated CHANGELOG
        uses: stefanzweifel/git-auto-commit-action@v4
        with:
          branch: main
          commit_message: Update CHANGELOG
          file_pattern: CHANGELOG.md

The workflow triggers whenever a new release has been published. It will check out your repository, pass the name of the release as the latest version and the release notes to the "changelog-updater"-Action. Then the git-auto-commit-Action will commit and push the updated changelog back to GitHub.

I mention that the release name is passed as the "latest version" to the Action. This means whatever title you choose for your release name, will also be used as the title of the version in the changelog. So best stick to semantic versioning and use names like v2.12.0 or 4.53.12. Avoid using names that describe the biggest change in the release like "Support Webpack 5".

With a changelog and GitHub Actions workflow in your repository, you're ready for the next step: Creating a new release.

Creating a new release and release notes

To trigger the newly added update-changelog.yaml workflow we need to create a new release on GitHub. And to make a new release, there must be code changes to be released.

I now just assume that you know how to make a change to your repository. I don't want to go into detail on how to check out a new local branch, make a change to your code, commit the change, push the change to GitHub, open a pull request and write meaningful description of the change and then merge the pull request.

Let's just skip to the "create release and release-notes"-part.

As mentioned above, GitHub recently revamped the "Releases"-feature and added a button to automatically generate the release notes based on merged pull requests.
To keep things simple, we're going to use this feature to generate our release notes. (A couple paragraphs below, I will share other different approaches to automate the process of generating release notes.)

Now it's finally time to create that new release.
If you want, you can create a new Git tag in your local repository and push that tag to Github. But you can also create a new tag through GitHubs web UI.
Head to the "Releases" section of your repository and click on "Draft a new release". Select your pushed tag in the "Choose a tag" dropdown or create a new one. Then click on the "Auto-generate release notes" button. Your recently merged pull request should appear in the editor.

This screencast shows how to generate a new tag in GitHub's web UI and how to automatically generate release notes.

You can update and polish the release notes if you want. Maybe you want to write an introduction to the "highlight" feature of this release. Or thank a specific contributor.

If everything looks good, click on the "Publish release" button.

This screencast shows how the GitHub workflow is triggered, after we publish our new release.

Immediately after publishing the release the update-changelog.yaml workflow will be triggered. After a couple of seconds a new commit should appear in your repository with the updated changelog file. 🎉

Approaches to auto generate release notes

There are a many different ways how release notes can be written:

  • Manually, by looking at recently merged pull requests and writing down the changes in a draft release on GitHub or anywhere else.
  • Automated, by using the feature GitHub provides (as mentioned above).
  • Automated, by following the conventional commits specification when writing your commit messages and using a tool like conventional-changelog to generate the release notes.
  • Automated, by using a GitHub Action like release-drafter that can determine the type of change a pull request introduces by branch name, title or label and then generate release notes based on those merged pull requests.

I use release-drafter in my personal projects and my team also recently switched to release-drafter. And I think I created a pretty cool workflow to sort pull requests into the right "Keep A Changelog" headings.

Screenshot of a Pull Request on GitHub. The changelog:removed label is applied to indicate that a feature has been removed.
This Pull Request is marked as a removal and will show up under the "Removed" heading in the changelog.

I and my team use a set of labels that are all prefixed with "changelog:" to make it crystal clear what type of change any given pull request represents. The labels are:

  • changelog:added
  • changelog:changed
  • changelog:deprecated
  • changelog:fixed
  • changelog:removed
  • changelog:security

As you can see, we have a label for every type of change a changelog following the "Keep A Changelog" format expects to exist.
Our release drafter configuration file, then matches labels and headings. Here is how a release-drafter.yml configuration file in our projects looks like.

ame-template: 'v$RESOLVED_VERSION'
tag-template: 'v$RESOLVED_VERSION'
template: |
  $CHANGES
change-template: '- $TITLE ([#$NUMBER](https://github.com/user/repo/pull/$NUMBER))' 
categories:
  - title: Added
    labels:
      - 'changelog:added'
  - title: Changed
    labels:
      - 'changelog:changed'
  - title: Deprecated
    labels:
      - 'changelog:deprecated '
  - title: Removed
    labels:
    - 'changelog:removed'
  - title: Fixed
    labels:
      - 'changelog:fixed'
  - title: Security
    labels:
      - security
      - changelog:security
  - title: 'Dependency Updates'
    labels:
      - dependencies

version-resolver:
  major:
    labels:
      - 'changelog:removed'
  minor:
    labels:
      - 'changelog:added'
      - 'changelog:deprecated'
  patch:
    labels:
      - 'changelog:fixed'
      - 'changelog:security'
      - 'dependency'

exclude-labels:
  - 'skip-changelog'

Whenever a pull request is merged, release-drafter will draft a new release and will sort the pull request into the right heading/type based on the label the developer has attached to it.

We really enjoy this label-driven approach. It allows us to easily change the "type of change" of a pull requests in GitHubs web UI and keeps our commit messages clean. (One could argue that the information, that a commit adds or changes a feature, should be placed in the commit message itself. I'm not against this. Conventional commits is very popular. We as a team however decided against it.)

If you would like to use these labels in your projects too, you can use the following script to create the new labels through the GitHub API. You just have to provide a valid GitHub token, username and repository name.

#!/bin/bash

set -e

# 1. Create your github PERSONAL ACCESS TOKEN at https://github.com/settings/tokens
# 2. Enter these fields
GH_TOKEN=''
USERNAME=''
REPO=''

# 3. Hit GitHub API
curl -f -X POST https://api.github.com/repos/$USERNAME/$REPO/labels -H "Authorization: token $GH_TOKEN" -d '{"name":"changelog:added","color":"0E8A16"}'
curl -f -X POST https://api.github.com/repos/$USERNAME/$REPO/labels -H "Authorization: token $GH_TOKEN" -d '{"name":"changelog:changed","color":"5319E7"}'
curl -f -X POST https://api.github.com/repos/$USERNAME/$REPO/labels -H "Authorization: token $GH_TOKEN" -d '{"name":"changelog:dreprecated","color":"000000"}'
curl -f -X POST https://api.github.com/repos/$USERNAME/$REPO/labels -H "Authorization: token $GH_TOKEN" -d '{"name":"changelog:fixed","color":"0E8A16"}'
curl -f -X POST https://api.github.com/repos/$USERNAME/$REPO/labels -H "Authorization: token $GH_TOKEN" -d '{"name":"changelog:removed","color":"D93F0B"}'
curl -f -X POST https://api.github.com/repos/$USERNAME/$REPO/labels -H "Authorization: token $GH_TOKEN" -d '{"name":"changelog:security","color":"FBCA04"}'
curl -f -X POST https://api.github.com/repos/$USERNAME/$REPO/labels -H "Authorization: token $GH_TOKEN" -d '{"name":"skip-changelog","color":"DDDDDD"}'

Special Case: Having an Unreleased Heading in the Changelog

As mentioned earlier, the "changelog-updater" Action works great if your changelog follows the "Keep A Changelog" format to a T. To be precise: If your changelog contains an "Unreleased"-heading that contains an URL to GitHub's compare view, the Action will automatically update that URL.

To benefit from this feature, your base changelog has to look like this.

# Changelog
All notable changes to this project will be documented in this file.

## [Unreleased](https://github.com/org/repo/compare/v1.0.0...HEAD)

## v1.0.0 - 2021-11-10

- Initial Release

Update the URL in the heading to point to your GitHub repository and you're good to go.
After releasing a new version, the changelog will look like this.

# Changelog

+## [Unreleased](https://github.com/org/repo/compare/v1.1.0...HEAD)
-## [Unreleased](https://github.com/org/repo/compare/v1.0.0...HEAD)

+## [v1.1.0](https://github.com/org/repo/compare/v1.0.0...v1.1.0) - 2021-11-11

+### Added

+* New Feature A
+* New Feature B

## v1.0.0 - 2021-11-10

### Added
* Initial Release

Having an "Unreleased" heading in the changelog itself gives your users an easy way to see, what commits have been made since the last release has been published. By letting the Action keep that URL up to date, you have to think about one less thing when doing a release.

Conclusion

I hope this article made it clear, that changelogs are an important document for each project. Be it a public software project – like a package. Or a private project – like the app you're working on for your company or your latest side project.

The mentioned strategies of generating release notes hopefully gave you a bit of inspiration for your own projects and I hope, I could inspire you to try out the changelog-updater-Action in your projects.

If you have thoughts on this topic, questions or general feedback to the mentioned projects of mine, feel free to open issues or discussion on the respective repositories (Action, CLI) or reach out to me via email or Twitter.

Now, be a good developer citizen and keep an up-to-date changelog!

Thanks and Inspiration

Thanks to Andreas Möller and his ergebnis/github-changelog project.

Thanks to Freek Van der Herten for testing the v1.0.0 version of the Action on his projects. His feedback gave the nudges to make the Action accessible to more people by removing the need for an Unreleased heading.


  1. We also let a subset of pull request be auto merged by GitHub Actions. â†©

Webmentions