Skip to main content

Using Expeditor to manage your Changelog

Keeping a changelog is a critical part of your release life-cycle. It provides meaningful, contextual information about what has changed in your software product. While git log provides a historical record of what has changed, the changelog provides that same information with the added context of your release life-cycle (i.e. what changed between stable releases of your software) with the added benefit of being easier to read for the less technically-inclined.

With Expeditor, you can automate the management of your changelog using two steps:

  1. Update your changelog with new line item changes as they are made, typically in response to a pull_request_merged workload.
  2. Reorganize the line items into categories associated with stable releases of your software, typically in response to a project_promoted workload.

While Expeditor can help manage your changelog, it does not own it. If you want to reword a line item, or move the line item to a different category, you are free to do so. The only requirement is that the comment blocks that Expeditor adds stay intact. We’ll cover those comment blocks in more detail later in this guide.

Requirements to get started

In order to Expeditor to successfully manage your changelog, the changelog file must:

  1. Already exist. Expeditor will not create the changelog file for you if it does not already exist.
  2. Contain the following comment blocks. These comment blocks are used by Expeditor to separate the portions of the changelog that it controls from the rest of the changelog.

Warning

The concept of promotion inside Expeditor has evolved organically and is a little muddled at the moment. The patterns we’re going to describe in the majority of this document will only apply if your project adheres to semantic version promotion. If your project does not follow these patterns, you’ll need to leverage the simple changelog management described at the bottom of this page.

The first step is to ensure that your changelog file (e.g. CHANGELOG.md) has the necessary comment blocks. If you already have an existing changelog file, the comment blocks should go between your file header (e.g. # Changelog) and your existing content. You do not need to inject existing content into any of the comment blocks.

CHANGELOG.md
# Changelog

<!-- latest_release -->
<!-- latest_release -->

<!-- release_rollup -->
<!-- release_rollup -->

<!-- latest_stable_release -->
<!-- latest_stable_release -->

## Previous Release
* The content of your changelog prior to using Expeditor.
  • latest_release. This comment block tells Expeditor where to add new line items as part of the built_in:update_changelog action.
  • release_rollup. This comment block is where Expeditor will place line items added since the last stable release. It is only required if you’ve elected to roll over changelog line items into stable releases using the built_in:rollover_changelog action. If both latest_release and release_rollup are present, Expeditor will add new line items to both comment blocks.
  • latest_stable_release. This comment block is where Expeditor places line items that are rolled over as part of the built_in:rollover_changelog action.

You should only add the comment blocks associated with the actions you are using. For example, if you are only using the basic changelog management, you should not add the release_rollup or latest_stable_release comment blocks.

As part of the same pull request where you add the comment blocks to your changelog file, please add the appropriate configuration to your .expeditor/config.yml. At the bottom of this guide we’ll cover a few different scenarios where you might want to adjust the configuration, but for this portion of the guide we’ll assume you’re using the following defaults.

  • Your changelog file is named CHANGELOG.md.
  • You have three categories of changes: Bug Fixes, Features & Enhancements, and Merged Pull Requests.

Important

Expeditor does not keep an internal record of your changelog.

Expeditor depends on conventions surrounding your Expeditor configuration (such as categories and headers) and the comment blocks to power a collection of regular expressions that is uses to modify your changelog inline.

Any modifications you make to your changelog will be respected by Expeditor including:

  • Moving line items around to different categories within your comment blocks.
  • Changing the versions associated with line items in your release_rollup comment block.

If you choose to manually modify your changelog file, it is your responsibility to ensure that you do not break those conventions, otherwise Expeditor may not be able to manage your changelog correctly.

Adding line items when pull requests are merged

The only method Expeditor supports for adding line items to your changelog is by running the built_in:update_changelog action in response to a pull_request_merged workload.

.expeditor/config.yml
---
subscriptions:
  - workload: pull_request_merged:{{github_repo}}:{{release_branch}}:*
    actions:
      - built_in:bump_version:
          ignore_labels:
            - "Expeditor: Skip Version Bump"
            - "Expeditor: Skip All"
      - bash:.expeditor/update_version.sh:
          only_if: built_in:bump_version
      - built_in:update_changelog:
          ignore_labels:
            - "Expeditor: Skip Changelog"
            - "Expeditor: Skip All"

You’ll see in the example above that the built_in:update_changelog action is paired with the built_in:bump_version and bash.expeditor/update_version.sh actions. These three actions make up the core actions that most projects use in their post merge action sets. We talk in our guide on version management about the close association (via the only_if action filter) between built_in:bump_version and bash.expeditor/update_version.sh, but let’s take a moment to discuss the loose association between version management and your changelog.

Looking at the example above, you’ll see that the built_in:update_changelog action occurs after we bump the version. That is because the built_in:update_changelog action will behave differently depending on whether or not the VERSION file was modified, either as part of the pull request or by the built_in:bump_version action.

  • If VERSION was modified, Expeditor will associate the new line item with that version.
  • If VERSION was not modified, Expeditor will associate the new line item with an “unreleased” version.

You can also see that the built_in:update_changelog in our example has two labels specified for the ignore_labels action filter: Expeditor: Skip Changelog and Expeditor: Skip All. By adding one of these two labels to your GitHub pull request, you can control whether or not a line item is added to your changelog.

  • If you add Expeditor: Skip Changelog, Expeditor will bump the version but it will not add an item to the changelog. This label isn’t useful in our example above, but if your action set included additional actions you can imagine applying that label in conjunction with Expeditor: Skip Version Bump.
  • If you add Expeditor: Skip All, Expeditor will neither bump the version nor update the changelog. The Expeditor: Skip All is the standard label most projects will use to identify that no Expeditor actions should occur.

Adding a line item when VERSION is modified

When the VERSION file is modified, Expeditor will use that version as part of its modifications of the changelog. Here is an example of how your changelog file is modified in response to the built_in:update_changelog action. In this example, we’ve bumped our VERSION from 0.9.8 to 0.9.9.

Note

You’ll see us use YYYY-MM-DD in our examples to indicate the presence of a date stamp. In practice, this value will be the date that the Expeditor action occurred rather than a date based on git metadata.
CHANGELOG.md
- <!-- latest_release 0.9.8-->
- ## [v0.9.8](https://github.com/chef/example/tree/v0.9.8) (YYYY-MM-DD)
+ <!-- latest_release 0.9.9 -->
+ ## [v0.9.9](https://github.com/chef/project/tree/v0.9.9) (YYYY-MM-DD)

  #### Merged Pull Requests
- - pull request that created version 0.9.8
+ - pull request that created version 0.9.9
  <!-- latest_release -->

  <!-- release_rollup since=0.9.5 -->
  ### Changes not yet released to stable

  #### Merged Pull Requests
+ - pull request that created version 0.9.9 <!-- 0.9.9 -->
  - pull request that created version 0.9.8 <!-- 0.9.8 -->
  - pull request that created version 0.9.7 <!-- 0.9.7 -->
  - pull request that created version 0.9.6 <!-- 0.9.6 -->
  - pull request that created version 0.9.5 <!-- 0.9.5 -->
  - pull request that created version 0.9.4 <!-- 0.9.4 -->
  <!-- release_rollup -->

  <!-- latest_stable_release -->
  <!-- latest_stable_release -->

In this example, Expeditor did four things (from top to bottom):

  1. It set the version metadata in the latest_release comment block to 0.9.8. Expeditor uses this value as part of the built_in:rollover_changelog action. We’ll discuss it in more detail when we cover that action down below.
  2. It updated the version and date in the H2 header of the latest_release comment block.
  3. It replaced the old line item in the latest_release comment block.
  4. It added the line item to the release_rollup comment block. The line itself also contains a comment that includes the version (0.9.9) as well.

What happens to unreleased line items when VERSION is modified?

It will incorporate those line items into an updated latest_release section. For this example, we’ll be bumping our VERSION to 0.9.9 with two “unreleased” line items.

CHANGELOG.md
+ <!-- latest_release 0.9.9 -->
+ ## [v0.9.9](https://github.com/chef/example/tree/v0.9.9) (YYYY-MM-DD)
- <!-- latest_release unreleased -->
- ## Unreleased

  #### Merged Pull Requests
+ - pull request that created version 0.9.9
  - another pull request that did not modify the version
  - pull request that did not modify the version
  <!-- latest_release -->

  <!-- release_rollup since=0.9.5 -->
  ### Changes not yet released to stable

  #### Merged Pull Requests
+ - pull request that created version 0.9.9 <!-- 0.9.9 -->
- - another pull request that did not modify the version <!-- unreleased -->
+ - another pull request that did not modify the version <!-- 0.9.9 -->
- - pull request that did not modify the version <!-- unreleased -->
+ - pull request that did not modify the version <!-- 0.9.9 -->
  - pull request that created version 0.9.8 <!-- 0.9.8 -->
  - pull request that created version 0.9.7 <!-- 0.9.7 -->
  - pull request that created version 0.9.6 <!-- 0.9.6 -->
  - pull request that created version 0.9.5 <!-- 0.9.5 -->
  - pull request that created version 0.9.4 <!-- 0.9.4 -->
  <!-- release_rollup -->

  <!-- latest_stable_release -->
  <!-- latest_stable_release -->

In this example, Expeditor did five things (from top to bottom):

  1. It updated the version metadata in the latest_release comment block from unreleased to 0.9.9. Expeditor uses this value as part of the built_in:rollover_changelog action.
  2. It updated the version and date in the H2 header of the latest_release comment block.
  3. It added the line item to the latest_release comment block without replacing the existing content.
  4. It added the line item to the release_rollup comment block. The line itself also contains a comment that includes the version (0.9.9) as well.
  5. It updated the comments for previously unreleased line items in the release_rollup to 0.9.9 to indicate that those changes were included in the 0.9.9 release.

Adding a line item when VERSION is not modified

If the VERSION file was not modified, Expeditor will add the line item to the changelog but establish that the associated functionality is unreleased. Here is an example of how your changelog file is modified in response to the built_in:update_changelog action. In this example, the version of our last versioned release was 0.9.8 and has not been modified.

CHANGELOG.md
- <!-- latest_release 0.9.8 -->
- ## [v0.9.8](https://github.com/chef/example/tree/v0.9.8) (YYYY-MM-DD)
+ <!-- latest_release unreleased -->
+ ## Unreleased

  #### Merged Pull Requests
- - pull request that created version 0.9.8
+ - pull request that did not modify the version
  <!-- latest_release -->

  <!-- release_rollup since=0.9.5 -->
  ### Changes not yet released to stable

  #### Merged Pull Requests
+ - pull request that did not modify the version <!-- unreleased -->
  - pull request that created version 0.9.8 <!-- 0.9.8 -->
  - pull request that created version 0.9.7 <!-- 0.9.7 -->
  - pull request that created version 0.9.6 <!-- 0.9.6 -->
  - pull request that created version 0.9.5 <!-- 0.9.5 -->
  - pull request that created version 0.9.4 <!-- 0.9.4 -->
  <!-- release_rollup -->

  <!-- latest_stable_release -->
  <!-- latest_stable_release -->

In this example, Expeditor did four things (from top to bottom):

  1. It set the version metadata in the latest_release comment block to unreleased.
  2. It updated the H2 header of the latest_release comment block to ## Unreleased.
  3. It replaced the old line item in the latest_release comment block.
  4. It added the line item to the release_rollup comment block. The version comment block associated with the line item is unreleased.

What happens when the latest release is also unreleased?

It will add the line item to the existing unreleased latest_release section. In this example, the version of our last versioned release was 0.9.8 and we have one existing unreleased line item.

CHANGELOG.md
  <!-- latest_release unreleased -->
  ## Unreleased

  #### Merged Pull Requests
+ - another pull request that did not modify the version
  - pull request that did not modify the version
  <!-- latest_release -->

  <!-- release_rollup since=0.9.5 -->
  ### Changes not yet released to stable

  #### Merged Pull Requests
+ - another pull request that did not modify the version <!-- unreleased -->
  - pull request that did not modify the version <!-- unreleased -->
  - pull request that created version 0.9.8 <!-- 0.9.8 -->
  - pull request that created version 0.9.7 <!-- 0.9.7 -->
  - pull request that created version 0.9.6 <!-- 0.9.6 -->
  - pull request that created version 0.9.5 <!-- 0.9.5 -->
  - pull request that created version 0.9.4 <!-- 0.9.4 -->
  <!-- release_rollup -->

  <!-- latest_stable_release -->
  <!-- latest_stable_release -->

In this example, Expeditor did two things (from top to bottom):

  1. It added the line item item to the latest_release comment block without replacing the existing content.
  2. It added the line item to the release_rollup comment block. The version comment block associated with the line item is unreleased.

Grouping line items into releases

If you’re using a project with a top-level semantic version, Expeditor provides you the ability to automatically re-arrange the contents of your changelog when you execute the built_in:rollover_changelog action in response to the promotion of your project to its final release channel (typically stable).

.expeditor/config.yml
---
artifact_channels:
  - unstable
  - current
  - stable

subscriptions:
  - workloads: project_promoted:{{agent_id}}:*
    actions:
      - built_in:rollover_changelog

The built_in:rollover_changelog action is a pre-commit action that takes effected versioned line items from your release_rollup comment block and moves them to the latest_stable_release comment block. Line items are determined to be effected when the version in their comment block is equal to or less than the version that was just promoted as determined by the Mixlib::Versioning library.

What happens when you promote the latest version?

When you promote the latest version of a project, Expeditor will empty out the latest_release and release_rollup comment blocks, moving all the content to the latest_stable_release comment block. In this example, the latest versioned release is 0.9.9 and we’ve just promoted it to stable.

CHANGELOG.md
-  <!-- latest_release 0.9.9 -->
+  <!-- latest_release -->
- ## [v0.9.9](https://github.com/chef/project/tree/v0.9.9) (YYYY-MM-DD)
-
- #### Merged Pull Requests
- - pull request that created version 0.9.9
  <!-- latest_release -->

-  <!-- release_rollup since=0.9.5 -->
+  <!-- release_rollup since=0.9.9 -->
- ### Changes not yet released to stable
-
- #### Merged Pull Requests
- - pull request that created version 0.9.9 <!-- 0.9.9 -->
- - pull request that created version 0.9.8 <!-- 0.9.8 -->
- - pull request that created version 0.9.7 <!-- 0.9.7 -->
- - pull request that created version 0.9.6 <!-- 0.9.6 -->
- - pull request that created version 0.9.5 <!-- 0.9.5 -->
- - pull request that created version 0.9.4 <!-- 0.9.4 -->
  <!-- release_rollup -->

  <!-- latest_stable_release -->
+ ## [v0.9.9](https://github.com/chef/project/tree/v0.9.9) (YYYY-MM-DD)
+
+ #### Merged Pull Requests
+ - pull request that created version 0.9.9
+ - pull request that created version 0.9.8
+ - pull request that created version 0.9.7
+ - pull request that created version 0.9.6
+ - pull request that created version 0.9.5
+ - pull request that created version 0.9.4
  <!-- latest_stable_release -->

In this example, Expeditor did three things (from top to bottom):

  1. It cleared out the contents of the latest_release comment block.
  2. It cleared out the contents of the release_rollup comment block, but set the since label to the version that was just promoted.
  3. It moved all of the relevant line items into the latest_stable_release comment block.

What happens when you promote a non-latest version?

When you promote a version of your project that is not the latest version, Expeditor will extract the appropriate line items from your release_rollup comment block and generate the correct latest_stable_release comment block.

CHANGELOG.md
  <!-- latest_release 0.9.9 -->
  ## [v0.9.9](https://github.com/chef/project/tree/v0.9.9) (YYYY-MM-DD)

  #### Merged Pull Requests
  - pull request that created version 0.9.9
  <!-- latest_release -->

- <!-- release_rollup since=0.9.5 -->
+ <!-- release_rollup since=0.9.7 -->
  ### Changes not yet released to stable

  #### Merged Pull Requests
  - another pull request that did not modify the version <!-- 0.9.9 -->
  - pull request that did not modify the version <!-- 0.9.9 -->
  - pull request that created version 0.9.9 <!-- 0.9.9 -->
  - pull request that created version 0.9.8 <!-- 0.9.8 -->
- - pull request that created version 0.9.7 <!-- 0.9.7 -->
- - pull request that created version 0.9.6 <!-- 0.9.6 -->
- - pull request that created version 0.9.5 <!-- 0.9.5 -->
- - pull request that created version 0.9.4 <!-- 0.9.4 -->
  <!-- release_rollup -->

  <!-- latest_stable_release -->
+ ## [v0.9.7](https://github.com/chef/project/tree/v0.9.7) (YYYY-MM-DD)
+
+ #### Merged Pull Requests
+ - pull request that created version 0.9.7
+ - pull request that created version 0.9.6
+ - pull request that created version 0.9.5
+ - pull request that created version 0.9.4
  <!-- latest_stable_release -->

In this example, Expeditor did three things (from top to bottom):

  1. It updated the since label in the release_rollup comment block to the version that was just promoted.
  2. It removed line items from the release_rollup comment block whose version was less than or equal the version that was just promoted.
  3. It moved all of the relevant line items into the latest_stable_release comment block.

Organizing line items into categories

One of the benefits of changelogs over something like git log is the ability to organize changes into different categories. These categories can make it easier to understand what has changed in a release by organizing the changes to common categories such as bug fixes, security fixes, product enhancements, or any number of categories which best serve your project. In Expeditor, these categories are represented in your changelog as H4 headers (as seen in the example below).

CHANGELOG.md
<!-- latest_release 0.1.7 -->
## [v0.1.7](https://github.com/chef/project/tree/v0.1.7) (YYYY-MM-DD)

#### Bug Fixes
- Ensure that UI elements align correctly in Firefox
<!-- latest_release -->

<!-- release_rollup since=0.1.0 -->
### Changes not yet released to stable

#### Features & Enhancements
- Add flag that serves API responses in XML <!-- 0.1.5 -->
- UI elements can now wiggle when computer is shaken violently <!-- 0.1.3 -->

#### Bug Fixes
- Ensure that UI elements align correctly in Firefox <!-- 0.1.7 -->
- Correct HEX code in dark-mode color pallette <!-- 0.1.4 -->

#### Merged Pull Requests
- Migrate test framework to latest release <!-- 0.1.6 -->
- Update the pinnings of all our Golang dependencies <!-- 0.1.2-->
- Added Expeditor configuration <!-- 0.1.1 -->
<!-- release_rollup -->

<!-- latest_stable_release -->
<!-- latest_stable_release -->

Which category a line item is added under is controlled by GitHub labels. The mapping of GitHub labels to changelog categories is handled by the changelog.categories configuration value. If none of the configured labels are applied to the pull request, Expeditor puts the changelog item in the “Merged Pull Requests” category.

Remember, if you accidentally forget to apply a label, you can place the line item under the correct header by manually modifying the changelog file. Just make sure that if you need to add an H4 header that you use the correct name (as indicated by your Expeditor configuration). If they do not match 100%, you may get “duplicate headers” (which, itself, can be fixed manually as well).

Basic changelog management

Note

This section covers rudimentary changelog management you can use if your project does not meet the the following criteria outlined at the top of this guide.

If you are unable to leverage the release_rollup and latest_stable_release comment blocks because your project does not meet the current criteria, you are still able to leverage Expeditor to manage your changelog, you are just limited to only using the built_in:update_changelog action in conjunction with latest_release comment block.

CHANGELOG.md
# Changelog

<!-- latest_release -->
<!-- latest_release -->

Do not put the release_rollup and latest_stable_release comment blocks in your changelog file. Expeditor determines the behavior of the built_in:update_changelog action based on their presence. Having them in your changelog file when you cannot use them will cause undesired behavior.

To exemplify how the basic changelog management works, we’ll going to walk through four modifications of your changelog:

  1. Adding a line item corresponding with a version release (0.1.0)
  2. Adding a line item for an unreleased change
  3. Adding a line item corresponding with another version release (0.1.1)
  4. Adding a line item corresponding with another version release (0.1.2)
Adding a line item corresponding with a version release (0.1.0)
- <!-- latest_release -->
+ <!-- latest_release 0.1.0 -->
+ ## [v0.1.0](https://github.com/chef/example/tree/v0.1.0) (YYYY-MM-DD)

  #### Merged Pull Requests
+ - pull request that created version 0.1.0
  <!-- latest_release -->
Adding a line item for an unreleased change
- <!-- latest_release 0.1.0 -->
+ <!-- latest_release unreleased -->
- ## [v0.1.0](https://github.com/chef/example/tree/v0.1.0) (YYYY-MM-DD)
+ ## Unreleased

  #### Merged Pull Requests
- - pull request that created version 0.1.0
+ - pull request for an unreleased change
  <!-- latest_release -->

+ ## [v0.1.0](https://github.com/chef/example/tree/v0.1.0) (YYYY-MM-DD)

+ #### Merged Pull Requests
+ - pull request that created version 0.1.0
Adding a line item corresponding with another version release (0.1.1)
- <!-- latest_release unreleased -->
+ <!-- latest_release 0.1.1 -->
- ## Unreleased
+ ## [v0.1.1](https://github.com/chef/example/tree/v0.1.1) (YYYY-MM-DD)

  #### Merged Pull Requests
+ - pull request that created version 0.1.1
  - pull request for an unreleased change
  <!-- latest_release -->

  ## [v0.1.0](https://github.com/chef/example/tree/v0.1.0) (YYYY-MM-DD)

  #### Merged Pull Requests
  - pull request that created version 0.1.0
Adding a line item corresponding with another version release (0.1.2)
- <!-- latest_release 0.1.1 -->
+ <!-- latest_release 0.1.2 -->
- ## [v0.1.1](https://github.com/chef/example/tree/v0.1.1) (YYYY-MM-DD)
+ ## [v0.1.2](https://github.com/chef/example/tree/v0.1.2) (YYYY-MM-DD)

  #### Merged Pull Requests
+ - pull request that created version 0.1.2
- - pull request that created version 0.1.1
- - pull request for an unreleased change
  <!-- latest_release -->

+ ## [v0.1.1](https://github.com/chef/example/tree/v0.1.1) (YYYY-MM-DD)

+ #### Merged Pull Requests
+ - pull request that created version 0.1.1
+ - pull request for an unreleased change

  ## [v0.1.0](https://github.com/chef/example/tree/v0.1.0) (YYYY-MM-DD)

  #### Merged Pull Requests
  - pull request that created version 0.1.0

This method of changelog management is much more verbose and isn’t able to reflect the concept of stable releases. If you’re required to use this method of changelog management, here are some recommendations for you to help create better changelogs:

  1. Bump your version sparingly. By making use of unreleased changes, you can better organization your changes into chunks.
  2. Manually modify your changelog to create releases. You can, in response to a release of your software, manually modify your changelog to group all the line items together into something similar to what Expeditor does with release_rollup.