From a2996288c796265d7e8b0095ffede8b38ff6abb2 Mon Sep 17 00:00:00 2001 From: Daniel Wedul Date: Thu, 3 Oct 2024 18:12:52 -0600 Subject: [PATCH 1/7] Create the add-change.sh script as an alternative to unclog. --- .changelog/add-change.sh | 377 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100755 .changelog/add-change.sh diff --git a/.changelog/add-change.sh b/.changelog/add-change.sh new file mode 100755 index 000000000..0e49a21a3 --- /dev/null +++ b/.changelog/add-change.sh @@ -0,0 +1,377 @@ +#!/bin/bash +# This script will add a new entry to the unreleased changes. +# It is similar to unclog, but is hopefully a little easier for us to use. + +show_usage () { + cat << EOF +add-change.sh - Add an entry to the unreleased changelog. + +Usage: add-change.sh [] [] [
] [] + +Each argument can alternatively be provided using flags: + [-n|--issue|--issue-no|-p|--pr|--pull-request] + [-i|--id] + [-s|--section]
+ [-m|--message] + +[] is the issue or pr number. + This can only contain digits. + If one is provided (without a flag), it is treated as a PR number. + If the current branch name has the format .*/-, this arg can be omitted. + If the comes from the branch name, it is treated as an issue number. +[] is the filename id to use for this entry. + This can only have alpha-numeric characters, and dashes. + If not provided, it will be extracted from the current branch name. + The branch name can be one of .*/- or .*/ or just . +[
] is the section directory name that the entry will go in. + If not provided, you'll be prompted to select it using fzf. + If one isn't selected (or fzf isn't available), an error is printed. + This argument can also be a partial match for a valid section as long as it + matches only a single valid section value. I.e. you can provide this as "bug" + and it'll automatically use "bug-fixes". But if it's "dep" it'd match either + "deprecated" or "dependencies", so you'd be prompted using fzf. +[] is the text to include in the changelog. + If provided, it MUST have at least one space in it. + If not provided, and the
is "dependencies", the get-dep-changes.sh + script is used to generate the . If not provided, and the
+ is anything else, the new entry will be created with a TODO note in it. + The link will be added to the automatically. + Multiple s can be provided to create multiple bullet-points in the + new entry file. + +Differentiation between and
(when provided without flags): + Any un-flagged args that neither have spaces nor are all digits are either + the and/or
. It is an error if there are more than three + of them provided. + + If two such args are provided, then the order is determined by checking + each for valid section matches. If the 2nd one is a valid section match (or + neither are), the order is
. Otherwise, it's
. + It is an error if there are two of these but either the or
+ is provided using flags. + + If one such arg is provided, it is used to fill in what was not provided. + I.e. if an is provided using flags, it is the
. If a
+ is provided using flags, it is the . If neither are provided using + flags, it is the
only if it is a valid section match, otherwise it + is the . It is an error if there is one of these, but both the + and
were provided using flags. + +EOF + +} + +messages=() + +while [[ "$#" -gt '0' ]]; do + case "$1" in + -h|--help) + show_usage + exit 0 + ;; + -v|--verbose) + verbose="$1" + ;; + -n|--issue|--issue-no) + if [[ -z "$2" ]]; then + printf 'No argument provided after %s\n' "$1" + exit 1 + fi + num="$2" + num_type='issue' + shift + ;; + -p|--pr|--pull-request) + if [[ -z "$2" ]]; then + printf 'No argument provided after %s\n' "$1" + exit 1 + fi + num="$2" + num_type='pr' + shift + ;; + -i|--id) + if [[ -z "$2" ]]; then + printf 'No argument provided after %s\n' "$1" + exit 1 + fi + id="$2" + shift + ;; + -s|--section) + if [[ -z "$2" ]]; then + printf 'No argument provided after %s\n' "$1" + exit 1 + fi + section="$2" + shift + ;; + -m|--message) + if [[ -z "$2" ]]; then + printf 'No argument provided after %s\n' "$1" + exit 1 + fi + messages+=( "$2" ) + shift + ;; + *) + if [[ "$1" =~ [[:space:]] ]]; then + messages+=( "$1" ) + elif [[ "$1" =~ ^[[:digit:]]+$ ]]; then + if [[ -n "$num" ]]; then + printf 'Unknown argument: [%s]. The was already provided as [%s] using a flag.\n' "$1", "$num" + exit 1 + fi + num="$1" + num_type='pr' + elif [[ "$1" =~ ^[-[:alnum:]]+$ ]]; then + if [[ -z "$id_sect_1" ]]; then + id_sect_1="$1" + elif [[ -z "$id_sect_2" ]]; then + id_sect_2="$1" + else + printf 'Unknown argument: [%s]. An and
were already provided.\n' "$1" + exit + fi + else + printf 'Unknown argument: [%s].\n' "$1" + printf 'The can only contain digits.\n' + printf 'The and
can only contain alphanumeric characters and dashes.\n' + printf 'The must contain at least one space.\n' + exit 1 + fi + ;; + esac + shift +done + +where_i_am="$( cd "$( dirname "${BASH_SOURCE:-$0}" )"; pwd -P )" +repo_root="$( git rev-parse --show-toplevel 2> /dev/null )" +if [[ -z "$repo_root" ]]; then + if [[ "$where_i_am" =~ /.changelog$ || "$where_i_am" =~ /scripts$ ]]; then + # If this is in the .changelog or scripts directory, assume it's {repo_root}/. + repo_root="$( dirname "$where_i_am" )" + else + # Not in a git repo, and who knows where this script is in relation to the root, + # so let's just hope that our current location is the repo root. + repo_root='.' + fi +fi +[[ -n "$verbose" ]] && printf ' Repo root dir: [%s].\n' "$repo_root" + +valid_sections=( $( "${where_i_am}/get-valid-sections.sh" ) ) +if [[ "${#valid_sections[@]}" -eq '0' ]]; then + printf 'Program error: Could not get list of valid sections.\n' + exit 1 +fi + +# Usage: validate_section +# If is a valid section, or is a substring of exactly one valid section, +# the valid section name is printed to stdout and this will return with an exit +# code of 0. Otherwise, it'll return exit code of 1 and nothing will be printed. +validate_section () { + [[ -z "$1" || "$1" =~ [[:space:]] ]] && return 1 + local s opts + # First look. If there's an exact match, use that. + # Otherwise, identify all the valid entries that start with the given val. + opts=() + for s in "${valid_sections[@]}"; do + if [[ "$s" == "$1" ]]; then + printf '%s' "$s" + return 0 + elif [[ "$s" == "$1"* ]]; then + opts+=( "$s" ) + fi + done + # If there's exactly one option, we're good to go. + if [[ "${#opts[@]}" -eq '1' ]]; then + printf '%s' "${opts[*]}" + return 0 + fi + # If there were more options, we can't match and can return now. + [[ "${#opts[@]}" -ne '0' ]] && return 1 + + # Second look. Check for it anywhere in the valid entries. + for s in "${valid_sections[@]}"; do + if [[ "$s" == *"$1"* ]]; then + opts+=( "$s" ) + fi + done + # If we don't have exactly one match, there's nothing more we can do. + [[ "${#opts[@]}" -ne '1' ]] && return 1 + printf '%s' "${opts[*]}" + return 0 +} + +if [[ -n "$id_sect_2" ]]; then + # Assume that id_sect_2 is only ever set after id_sect_1, so we've got both here. + if [[ -n "$id" && -n "$section" ]]; then + printf 'Unknown arguments: [%s] and [%s]. The id [%s] and section [%s] were provided using flags.\n' "$id_sect_1" "$id_sect_2" "$id" "$section" + exit 1 + elif [[ -n "$id" ]]; then + printf 'Unknown arguments: [%s] and [%s]. The id [%s] was provided using flags.\n' "$id_sect_1" "$id_sect_2" "$id" + exit 1 + elif [[ -n "$section" ]]; then + printf 'Unknown arguments: [%s] and [%s]. The section [%s] was provided using flags.\n' "$id_sect_1" "$id_sect_2" "$section" + exit 1 + fi + + # The order is
if the 2nd is a valid section (regardless of the first), or if neither are. + # The order is only ever
if the 1st is a valid section, but the 2nd is not. + if validate_section "$id_sect_2" > /dev/null || ! validate_section "$id_sect_1" > /dev/null; then + id="$id_sect_1" + section="$id_sect_2" + else + id="$id_sect_2" + section="$id_sect_1" + fi +elif [[ -n "$id_sect_1" ]]; then + if [[ -n "$id" && -n "$section" ]]; then + printf 'Unknown argument: [%s]. The id [%s] and section [%s] were provided using flags.\n' "$id_sect_1" "$id" "$section" + exit 1 + fi + + if [[ -z "$id" && -z "$section" ]]; then + if vs="$( validate_section "$id_sect_1" )"; then + section="$vs" + else + id="$id_sect_1" + fi + elif [[ -z "$id" ]]; then + id="$id_sect_1" + elif [[ -z "$section" ]]; then + section="$id_sect_1" + fi +fi + +if [[ -z "$id" || -z "$num" ]]; then + br="$( git branch --show-current )" || exit 1 + br_sfx="$( sed -E 's|^.*/||' <<< "$br" )" + if [[ "$br_sfx" =~ ^[[:digit:]]+- ]]; then + if [[ -z "$num" ]]; then + num="$( sed -E 's/-.*$//' <<< "$br_sfx" )" + num_type='issue' + fi + br_sfx="$( sed -E 's/^[[:digit:]]+-//' <<< "$br_sfx" )" + fi + if [[ -z "$id" ]]; then + id="$br_sfx" + fi +fi + +if [[ -z "$num" ]]; then + printf 'No provided, and it could not be determined from the current branch name.\n' + exit 1 +elif [[ "$num" =~ [^[:digit:]] ]]; then + printf 'Invalid : [%s]. Can only contain digits.\n' "$num" + exit 1 +fi + +if [[ -z "$id" ]]; then + printf 'No provided, and it could not be determined from the current branch name.\n' + exit 1 +elif [[ "$id" =~ [^-[:alnum:]] ]]; then + printf 'Invalid : [%s]. Can only contain alphanumeric characters and dashes.\n' "$id" + exit 1 +fi + +if vs="$( validate_section "$section" )"; then + section="$vs" +elif command -v fzf > /dev/null 2>&1; then + # Use fzf to prompt for selection of the desired section. + # Make the height be the number of entries + 1 for the prompt line. That will make it so all options are + # visible, and it makes fzf show up below the command prompt (as opposed to taking over the whole screen). + # Use --layout reverse-list so that the top item is selected first instead of the bottom, and so + # the options are in the original order (instead of reversed). + vs="$( + printf '%s\n' "${valid_sections[@]}" \ + | fzf --cycle --no-multi --no-info --layout reverse-list --query "$section" \ + --height "$(( ${#valid_sections[@]} + 1 ))" --prompt 'Select a section:' + )" + if [[ -n "$vs" ]]; then + section="$vs" + fi +fi + +if ! validate_section "$section" > /dev/null; then + printf 'Invalid
: [%s].\n' "$section" + printf 'Valid Sections:\n' + printf ' %s\n' "${valid_sections[@]}" + exit 1 +fi + +if [[ -n "$verbose" ]]; then + printf ' : [%s].\n' "$num" + printf ' : [%s].\n' "$id" + printf '
: [%s].\n' "$section" + if [[ "${#messages[@]}" -gt '0' ]]; then + printf ': [%s].\n' "${messages[@]}" + else + printf ': not provided.\n' + fi +fi + +if [[ "$num_type" == 'issue' ]]; then + link="[#${num}](https://github.com/provenance-io/provenance/issues/${num})" + num_type_flag='--issue-no' +elif [[ "$num_type" == 'pr' ]]; then + link="[PR ${num}](https://github.com/provenance-io/provenance/pull/${num})" + num_type_flag='--pull-request' +else + printf 'Program error: Unknown num_type: [%s]. Should be either [issue] or [pr].\n' "$num_type" + exit 1 +fi + +if [[ "${#messages[@]}" -eq '0' ]]; then + if [[ "$section" == "dependencies" ]]; then + [[ -n "$verbose" ]] && printf 'Using get-dep-changes.sh for new entry.\n' + "${where_i_am}/get-valid-sections.sh" "$num_type_flag" "$num" --id "$id" --force + exit $? + fi + messages+=( "TODO: Write me." ) +fi + +[[ -n "$verbose" ]] && printf 'Creating temp file.\n' +temp_file="$( mktemp -t add-change.XXXX )" || exit 1 +[[ -n "$verbose" ]] && printf 'Created temp file: %s\n' "$temp_file" + +# Usage: clean_exit [] +# Default is 0. +# Deletes the temp file if it exists, then exits. +clean_exit () { + local ec + ec="${1:-0}" + if [[ -n "$temp_file" && -f "$temp_file" ]]; then + rm -rf "$temp_file" > /dev/null 2>&1 + temp_file='' + fi + exit "$ec" +} + +[[ -n "$verbose" ]] && printf 'Creating entry content.\n' +for message in "${messages[@]}"; do + # For the first line, make sure it starts with a "* " and add the link to the end, removing any + # period from before the link. For all other lines, add two spaces to the beginning of the line. + awk -v link="$link" '{if(NR==1){sub(/^[[:space:]]*[-*]?[[:space:]]*/,"* "); sub(/[[:space:]]*(\.)?[[:space:]]*$/," " link "."); print;}else{print " " $0;};}' <<< "$message" > "$temp_file" +done + +section_dir="${repo_root}/.changelog/unreleased/${section}" +if [[ ! -d "$section_dir" ]]; then + [[ -n "$verbose" ]] && printf 'Creating section dir: [%s].\n' "$section_dir" + mkdir "$section_dir" || clean_exit 1 +fi +filename="${section_dir}/${num}-${id}.md" +[[ -n "$verbose" ]] && printf 'Moving temp file [%s] to [%s].\n' "$temp_file" "$filename" +mv "$temp_file" "$filename" || clean_exit 1 +temp_file='' + +# filename will have the full path to the file, but we want to output the path relative to the repo root. +# This uses parameter expansion to get all characters of filename starting at n+1 and going to the end, +# where n is the number of characters in repo_root. If such parameter expansion isn't available, we +# suppress any error messages and the filename will just have to have everything. +if shorter="${filename:${#repo_root}+1}" 2> /dev/null; then + filename="$shorter" +fi +printf 'Wrote entry to: %s\n' "${filename}" + +clean_exit 0 From a9e5306722d89040067940d2fc1f67cb72d5b3d3 Mon Sep 17 00:00:00 2001 From: Daniel Wedul Date: Thu, 3 Oct 2024 19:17:05 -0600 Subject: [PATCH 2/7] Update the changelog readme with info about the new script. --- .changelog/README.md | 73 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/.changelog/README.md b/.changelog/README.md index deeaa3f5c..0208e12dd 100644 --- a/.changelog/README.md +++ b/.changelog/README.md @@ -30,7 +30,73 @@ The `CHANGELOG.md` is usually only updated as part of the release process. ## Adding Changes When a change is being made to this repo, at least one changelog entry should be created about it. -The easiest way to do this is using [unclog](https://github.com/informalsystems/unclog). +The easiest way to do this is using `add-change.sh`, but [unclog](https://github.com/informalsystems/unclog) can also be used. + + +### Using add-change.sh + +Usage: +```plaintext +Usage: add-change.sh [] [] [
] [] + +Each argument can alternatively be provided using flags: + [-n|--issue|--issue-no|-p|--pr|--pull-request] + [-i|--id] + [-s|--section]
+ [-m|--message] +``` + +See `add-change.sh --help` for full usage details. +In general, though, it tries to be as natural as possible to use. +The `` can be omitted if your branch name has the format `/-...`. +The `` can be omitted to just use the branch name (without the `-` part if it has it). +The `
` is a fuzzy-match field against the list of possible sections. +E.g. you can provide "bug" and it'll convert that to "bug-fixes". +If not provided (or there's confusion), you'll be prompted to select one using `fzf` (or get an error if `fzf` isn't available). +If the `` isn't provided, behavior depends on the `
`. +If the section is "dependencies", then the `get-dep-changes.sh` script will be executed to create the message content. +If it's any other section, the file will be created with a `TODO` entry. + + +Example for changes that do **not** have a related GitHub issue: + +```console +$ .changelog/add-change.sh 123 fix-the-thing bug 'Fix the thing that was broken' +``` + +If your branch is named `yourname/fix-the-thing`, then this next command is equivalent to the previous one: + +```console +$ .changelog/add-change.sh 123 bug 'Fix the thing that was broken' +``` + +Example for changes that **do** have a related GitHub issue. + +```console +$ .changelog/add-change.sh --issue 123 fix-the-thing bug 'Fix the thing that was broken' +``` + +If your branch is named `yourname/123-fix-the-thing`, then this next command is equivalent to the previous one: + +```console +$ .changelog/add-change.sh bug 'Fix the thing that was broken' +``` + +All of these example commands will create the file `.changelog/unreleased/bug-fixes/123-fix-the-thing.md` +with this content (respectively): + +```md +* Fix the thing that was broken [PR 123](https://github.com/provenance-io/provenance/pull/123). +``` + +or + +```md +* Fix the thing that was broken [#123](https://github.com/provenance-io/provenance/issues/123). +``` + + +### Using unclog Example for changes that do **not** have a related GitHub issue: @@ -39,6 +105,7 @@ $ unclog add --pull-request 123 --section bug-fixes --id fix-the-thing --message ``` Example for changes that **do** have a related GitHub issue. + ```console $ unclog add --issue-no 123 --section bug-fixes --id fix-the-thing --message 'Fix the thing that was broken' ``` @@ -204,8 +271,8 @@ To manually update the `.changelog/` entries: 1. Move the `.changelog/unreleased` directory to `.changelog/`, e.g. `mv .changelog/unreleased .changelog/v1.13.0`. 2. Delete the old `.gitkeep` file, e.g. `rm .changelog/v1.13.0/.gitkeep`. -2. Create a new `.changelog/unreleased` directory, e.g. `mkdir .changelog/unreleased`. -3. Create the new `.gitkeep` file, e.g. `touch .changelog/unreleased/.gitkeep`. +3. Create a new `.changelog/unreleased` directory, e.g. `mkdir .changelog/unreleased`. +4. Create the new `.gitkeep` file, e.g. `touch .changelog/unreleased/.gitkeep`. From ae4958d67f280b36a063416820da5f455bb3437e Mon Sep 17 00:00:00 2001 From: Daniel Wedul Date: Thu, 3 Oct 2024 19:38:01 -0600 Subject: [PATCH 3/7] Add changelog entry. --- .changelog/unreleased/improvements/2166-add-changelog-helper.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/unreleased/improvements/2166-add-changelog-helper.md diff --git a/.changelog/unreleased/improvements/2166-add-changelog-helper.md b/.changelog/unreleased/improvements/2166-add-changelog-helper.md new file mode 100644 index 000000000..bd9975772 --- /dev/null +++ b/.changelog/unreleased/improvements/2166-add-changelog-helper.md @@ -0,0 +1 @@ +* Create the `add-change.sh` script to make it easier to add changelog entries [PR 2166](https://github.com/provenance-io/provenance/pull/2166). From a5d25c11de02eaa6f1e7c3780fbc2a0c822e712b Mon Sep 17 00:00:00 2001 From: Daniel Wedul Date: Thu, 3 Oct 2024 19:44:03 -0600 Subject: [PATCH 4/7] Update the TOC in the .changelog/README.md. --- .changelog/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changelog/README.md b/.changelog/README.md index 0208e12dd..f4a806970 100644 --- a/.changelog/README.md +++ b/.changelog/README.md @@ -6,6 +6,8 @@ ultimately to generate the release notes and changelog content for a release. - [Overview](#overview) - [Adding Changes](#adding-changes) + - [Using add-change.sh](#using-add-changesh) + - [Using unclog](#using-unclog) - [Dependencies](#dependencies) - [Section Names](#section-names) - [Unclog](#unclog) From ca12fbf5cfbb4cf2d1ddf992bd6bb25c3d08a816 Mon Sep 17 00:00:00 2001 From: Daniel Wedul Date: Thu, 3 Oct 2024 19:44:21 -0600 Subject: [PATCH 5/7] Update the comment at the top of the CHANGELOG.md file too. --- CHANGELOG.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56ede08f0..38220b9e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,14 @@ The release date of each version is displayed. Usage: -Change log entries should be added using the unclog application (see: https://github.com/informalsystems/unclog) -to create entry files under the `.changelog/unreleased` directory (in a section/stanza dir). -Example of adding an entry: +Change log entries should be added using one of: +1. Our .changelog/add-change.sh script (see: .changelog/README.md#using-add-changesh). +2. The unclog application (see: https://github.com/informalsystems/unclog). +Those will create entry files under the `.changelog/unreleased` directory (in a section/stanza dir). +Examples of adding an entry: +$ .changelog/add-change.sh --issue-no 123 bug-fixes fix-the-thing 'Fix the thing that was broken' $ unclog add --issue-no 123 --section bug-fixes --id fix-the-thing --message 'Fix the thing that was broken' -That will create the file .changelog/unreleased/bug-fixes/123-fix-the-thing.md with this content: +Those will create the file .changelog/unreleased/bug-fixes/123-fix-the-thing.md with this content: `* Fix the thing that was broken [#123](https://github.com/provenance-io/provenance/issues/123).` If there is no issue to link to, use the --pull-request flag instead of --issue-no. @@ -26,12 +29,6 @@ generated by analyzing the go.mod file changes. The content in this CHANGELOG.md file takes precedence over the content of the .changelog directory (in the case of a discrepancy). -Ultimately, each entry should ideally include a message and a link to either -an issue number or pull request number: -* message # -or -* message PR - Types of changes (Stanzas): "Features" for new features. From 140104d9aabfad8977195fef295469ef0c575e67 Mon Sep 17 00:00:00 2001 From: Daniel Wedul Date: Thu, 3 Oct 2024 19:47:00 -0600 Subject: [PATCH 6/7] Exit with the correct code when an argument isn't known. --- .changelog/add-change.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/add-change.sh b/.changelog/add-change.sh index 0e49a21a3..fb9b9da1e 100755 --- a/.changelog/add-change.sh +++ b/.changelog/add-change.sh @@ -131,7 +131,7 @@ while [[ "$#" -gt '0' ]]; do id_sect_2="$1" else printf 'Unknown argument: [%s]. An and
were already provided.\n' "$1" - exit + exit 1 fi else printf 'Unknown argument: [%s].\n' "$1" From b679fc3189bfbb24db8809dd81ba62c5d3ca16e6 Mon Sep 17 00:00:00 2001 From: Daniel Wedul Date: Thu, 3 Oct 2024 19:57:00 -0600 Subject: [PATCH 7/7] Point to the correct input in the error message when an id isn't provided. --- .changelog/add-change.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/add-change.sh b/.changelog/add-change.sh index fb9b9da1e..fb17fbd23 100755 --- a/.changelog/add-change.sh +++ b/.changelog/add-change.sh @@ -268,7 +268,7 @@ elif [[ "$num" =~ [^[:digit:]] ]]; then fi if [[ -z "$id" ]]; then - printf 'No provided, and it could not be determined from the current branch name.\n' + printf 'No provided, and it could not be determined from the current branch name.\n' exit 1 elif [[ "$id" =~ [^-[:alnum:]] ]]; then printf 'Invalid : [%s]. Can only contain alphanumeric characters and dashes.\n' "$id"