Posted on

Changelogs are common when developing software to inform stakeholders about the changes and improvements a project is experiencing. In some domains, such a documentation is even mandatory. See for example the Zola Changelog Page on GitHub.

One option is, of course, to write this changelog by hand. But, if a version control system like Git is used, changes are already documented there. So why duplicate the work? This is where automatic changelog generators like git-cliff come in. The tool is capable of reading the git history, grouping the commits in a configurable way, and creating a changelog automatically. This has two main advantages:

  • Manual work for writing the changelog is eliminated
  • Knowing commit messages will be part of the changelog, commit discipline among developers might be improved

Example

git-cliff requires a configuration file named cliff.toml either in the project directory or in a global configuration folder (see docs for details) to tell the tool how a document shall be created based on a commit history. The most straightforward way is to just print every commit message one after another. But git-cliff is capable to interpret commit messages to some extend, allowing for a more sophisticated changelog.

An example config might look like this (see git-cliff docs for details):

[changelog]
# changelog header
header = """
# Changelog\n
All notable changes to this project will be documented in this file.\n
"""
# template for the changelog body
# https://tera.netlify.app/docs/#introduction
body = """
{% if version %}\
    ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
    ## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
    ### {{ group | upper_first }}
    {% for commit in commits %}
        - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
    {% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
footer = """
<!-- generated by git-cliff -->
"""

[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = false
# regex for parsing and grouping commits
commit_parsers = [
    { message = "^feat", group = "Features"},
    { message = "^Merge", skip = true},
]
# filter out the commits that are not matched by commit parsers
filter_commits = false
# glob pattern for matching git tags
tag_pattern = "*"

To break it down, the config allows for a header, a footer, as well as a templated part in between (using the Tera templating engine) that determines how each commit is represented. Here, tags are interpreted as releases and commits are broken down into each tag, allowing to associate changes with certain versions.

If the conventional_commits flag is set to false, git-cliff will not interpret the commit messages in any way, but use the regexes defined in commit_parsers to group commits. If the flag is true however, commits are interpreted according to the conventional commit specification. This means that Strings at the beginning of a message before a colon will be used to group commits (if not overwritten by commit_parsers). Eventually, this allows to list commits of one group (e.g. bugfixes) together in the changelog.

With filter_unconventional it is possible to skip all commits that don't follow the conventional commit specification. Also, the same is true for filter_commits if they don't match to any of the regexes in the commit_parsers. Furthermore, specific commits matching a regex can be skipped in commit_parsers, as done above with all commits starting with the word "Merge".

If developers have a certain discipline in writing commits, like

feat: Add green button

Add green button on start page, allowing users to launch the app

then it is super easy to auto-generate a changelog out of a project using git-cliff.

Example (from git-cliff readme)

A commit history like this

* df6aef4 (HEAD -> master) feat(cache): use cache while fetching pages
* a9d4050 feat(config): support multiple file formats
* 06412ac (tag: v1.0.1) chore(release): add release script
* e4fd3cf refactor(parser): expose string functions
* ad27b43 (tag: v1.0.0) docs(example)!: add tested usage example
* 9add0d4 fix(args): rename help argument due to conflict
* a140cef feat(parser): add ability to parse arrays
* 81fbc63 docs(project): add README.md
* a78bc36 Initial commit

Can generate a changelog that looks like this

Changelog

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

[unreleased]

Features

  • Support multiple file formats
  • Use cache while fetching pages

[1.0.1] - 2021-07-18

Miscellaneous Tasks

  • Add release script

Refactor

  • Expose string functions

[1.0.0] - 2021-07-18

Bug Fixes

  • Rename help argument due to conflict

Documentation

  • Add README.md
  • Add tested usage example

Features

  • Add ability to parse arrays

Copyright © 2022 Sebastian Müller. All rights reserved.