Skip to content

Commit

Permalink
chore(release): generate changelogs with git-cliff (#416)
Browse files Browse the repository at this point in the history
We maintain separate changelogs for the `tokio-console`, `console-api`,
and `console-subscriber` crates.

Currently, changelogs are generated using the [`clog`] CLI tool.
However, `clog` does not support generating separate changelogs for
different subdirectories of a repo, so the current release process for a
given crate requires generating a changelog for the whole repo since the
last tag, and then manually editing it to remove changes to other
crates. This is a pain.

The [`git-cliff`] changelog-generating tool supports generating a
changelog that only includes commits that modified specific paths in the
repo. This means it can be used to generate separate changelogs for each
crate automatically, unlike `clog`. It's also much more configurable,
and we can use it to generate changelogs that have somewhat nicer
formatting (IMO).

This branch changes changelog generation from using `clog` to use
`git-cliff`, and regenerates all the existing changelogs. The included
`bin/update-changelog.sh` shell script updates the changelog for a given
crate to the provided tag. This will make the release process much less
complicated. 

A subsequent PR will add a shell script that handles updating crate
versions, generating the changelog, and publishing the crate and tag, so
that a release can be published by running a single command.

[`clog`]: https://github.com/clog-tool/clog-cli
[`git-cliff`]: https://git-cliff.org/
  • Loading branch information
hawkw authored May 8, 2023
1 parent 665e5f6 commit 17093bc
Show file tree
Hide file tree
Showing 6 changed files with 550 additions and 144 deletions.
80 changes: 80 additions & 0 deletions bin/_util.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# utility functions used in other shell scripts.
#
# currently, this includes:
# - cargo-style stderr logging (`err`, `note`, and `status` functions)
# - confirmation prompts (`confirm` function)
set -euo pipefail

# Log an error to stderr
#
# Args:
# $1: message to log
err() {
echo -e "\e[31m\e[1merror:\e[0m" "$@" 1>&2;
}

# Log a note to stderr
#
# Args:
# $1: message to log
note() {
echo -e "\e[31m\e[1mnote:\e[0m" "$@" 1>&2;
}

# Log a cargo-style status message to stderr
#
# Args:
# $1: a "tag" for the log message (should be 12 characters or less in
# length)
# $2: message to log
status() {
local width=12
local tag="$1"
local msg="$2"
printf "\e[32m\e[1m%${width}s\e[0m %s\n" "$tag" "$msg"
}

# Prompt the user to confirm an action
#
# Args:
# $1: message to display to the user along with the `[y/N]` prompt
#
# Returns:
# 0 if the user confirmed, 1 otherwise
confirm() {
while read -r -p "$1 [Y/n] " input
do
case "$input" in
[yY][eE][sS]|[yY])
return 0
;;
[nN][oO]|[nN])
return 1
;;
*)
err "invalid input $input"
;;
esac
done
}

# Returns the path to a Mycelium crate.
#
# Args:
# $1: crate name
#
# Returns:
# 0 if the crate exists, 0 if it does not exist.
crate_path() {
local crate="$1"
local mycoprefix='mycelium-';
if [[ -d $crate ]]; then
echo "$crate"
elif [[ -d "${crate#"$mycoprefix"}" ]]; then
echo "${crate#"$mycoprefix"}"
else
err "unknown crate $crate"
return 1;
fi
}
93 changes: 93 additions & 0 deletions bin/update-changelog.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env bash
usage="Updates the changelog for a Tokio Console crate.
USAGE:
$(basename "$0") [FLAGS] <CRATE_PATH> <TAG>
FLAGS:
-h, --help Show this help text and exit.
-v, --verbose Enable verbose output."

set -euo pipefail

bindir=$( cd "${BASH_SOURCE[0]%/*}" && pwd )
rootdir=$( cd "$bindir"/.. && pwd )

# shellcheck source=_util.sh
. "$bindir"/_util.sh

cd "$rootdir"

verbose=''

for arg in "$@"
do
case "$arg" in
-h|--help)
echo "$usage"
exit 0
;;
-v|--verbose)
verbose="--verbose"
;;
-*)
err "unknown flag $arg"
echo "$usage"
exit 1
;;
*) # crate or version
if [[ -z "${path+path}" ]]; then
path="$arg"
elif [[ -z "${tag+tag}" ]]; then
tag="$arg"
else
err "unknown positional argument \"$arg\""
echo "$usage"
exit 1
fi
;;
esac
done

if [[ -z "${path+path}" ]]; then
err "no version specified!"
errexit=1
fi

if [[ -z "${tag+tag}" ]]; then
err "no tag specified!"
errexit=1
fi

if [[ "${errexit+errexit}" ]]; then
echo "$usage"
exit 1
fi

if ! [[ -x "$(command -v git-cliff)" ]]; then
err "missing git-cliff executable"
if confirm " install it?"; then
cargo install git-cliff
else
echo "okay, exiting"
exit 0
fi
fi

changelog_path="${path}/CHANGELOG.md"

status "Updating" "$changelog_path for tag $tag"

git_cliff=(
git-cliff
--include-path "${path}/**"
--output "$changelog_path"
--config cliff.toml
--tag "$tag"
)
if [[ "$verbose" ]]; then
git_cliff+=("$verbose")
fi

export GIT_CLIFF__GIT__TAG_PATTERN="${path}-v[0-9]*"
"${git_cliff[@]}"
113 changes: 113 additions & 0 deletions cliff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# git-cliff ~ default configuration file
# https://git-cliff.org/docs/configuration
#
# Lines starting with "#" are comments.
# Configuration options are organized into tables and keys.
# See documentation for more information on available options.

[changelog]
# changelog header
header = """
# Changelog\n
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\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 %}\
{% if previous %}\
{% if previous.commit_id %}
[{{ previous.commit_id | truncate(length=7, end="") }}](https://github.com/tokio-rs/console/commit/{{ previous.commit_id }})...\
[{{ commit_id | truncate(length=7, end="") }}](https://github.com/tokio-rs/console/commit/{{ commit_id }})
{% endif %}\
{% endif %}
{% set has_breaking = false -%}
{% for commit in commits -%}
{% if commit.breaking -%}
{% set_global has_breaking = true -%}
{% endif -%}
{% endfor -%}
{% if has_breaking -%}
### <a id = "{{ version }}-breaking"></a>Breaking Changes
{% for commit in commits -%}
{% if commit.breaking -%}
- **{{ commit.message | upper_first }}** ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/tokio-rs/console/commit/{{ commit.id }}))<br />{{ commit.breaking_description }}
{% endif -%}
{% endfor -%}
{% endif -%}
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.breaking %}[**breaking**](#{{ version }}-breaking) {% endif -%} {{ commit.message | upper_first }} (\
[{{ commit.id | truncate(length=7, end="") }}](https://github.com/tokio-rs/console/commit/{{ commit.id }})\
{% for link in commit.links -%}
, {{ link.text }}({{ link.href }})\
{% endfor -%}
)\
{% 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 = true
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/tokio-rs/console/issues/${2}))"}, # replace issue numbers
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "Added" },
{ message = "^fix", group = "Fixed" },
{ message = "^doc", group = "Documented" },
{ message = "^perf", group = "Performance" },
{ message = "^refactor", skip = true },
{ message = "^refac", skip = true },
{ message = "^style", skip = true },
{ message = "^test", skip = true },
{ message = "^chore\\(release\\)", skip = true },
{ message = "[Pp]repare to release", skip = true },
{ message = "^chore", skip = true },
{ body = ".*security", group = "Security" },
{ body = ".*[dD]eprecate.*", group = "Deprecated" },
# older non-conventional commits
{ message = "^subscriber:", group = "Added" },
{ message = "^console:", group = "Added" },
{ message = "^api:", group = "Added" },
{ message = "^example:", group = "Documented" },
]
# protect breaking changes from being skipped due to matching a skipping commit_parser
protect_breaking_commits = false
# filter out the commits that are not matched by commit parsers
filter_commits = false
# regex for skipping tags
# skip_tags = "v0.1.0-beta.1"
# regex for ignoring tags
ignore_tags = ""
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"
# limit the number of commits included in the changelog.
# limit_commits = 42

link_parsers = [
{ pattern = "[fF]ixes #(\\d+)", text = "fixes [#${1}]", href = "https://github.com/tokio-rs/console/issues/$1"},
{ pattern = "[cC]loses #(\\d+)", text = "closes [#${1}]", href = "https://github.com/tokio-rs/console/issues/$1"},
{ pattern = "[sS]ee #(\\d+)", text = "see [#${1}]", href = "https://github.com/tokio-rs/console/issues/$1"},
]
Loading

0 comments on commit 17093bc

Please sign in to comment.