From 8ee6c5055a04b75a8938af80ef05f10773b5bb5d Mon Sep 17 00:00:00 2001 From: Adam Abrams Date: Thu, 29 Aug 2024 23:30:19 -0500 Subject: [PATCH 01/12] change naming and improve semver filtering --- change | 265 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 134 insertions(+), 131 deletions(-) diff --git a/change b/change index 34051e4..1ce15c1 100755 --- a/change +++ b/change @@ -1,6 +1,6 @@ #!/bin/sh -change_file="CHANGELOG.md" +log_file="CHANGELOG.md" xdg_data_home=${XDG_DATA_HOME:-$HOME/.local/share} auth_dir="$xdg_data_home/change/" auth_file="$auth_dir/auth" @@ -8,48 +8,49 @@ change_version="0.14.5" nl=" " -Usage() { - echo "usage: change [COMMAND [ARGS]]" - echo - echo "change [--bump PATH]" - echo " updates an existing $change_file" - echo " optional: --bump passes the newest version as an arg to a script at PATH" - echo - echo "change init" - echo " creates a $change_file with the first version" - echo - echo "change tag [-p]" - echo " tags the latest local commit with the lastest $change_file version" - echo " optional: -p also pushes that tag" - echo - echo "change auth [--token TOKEN]" - echo " prompts you for a personal access token for posting releases" - echo " optional: --token uses TOKEN instead of asking for it interactively" - echo - echo "change post [--dry-run]" - echo " posts a GitHub release for the latest version in $change_file" - echo " optional: --dry-run prints url, version, and body without sending" - echo - echo "change all [--bump PATH]" - echo " runs change, then opens $change_file in \$EDITOR" - echo " commits and pushes the $change_file" - echo " runs change tag -p and then runs change post" - echo " optional: --bump passes the newest version as an arg to a script at PATH" - echo - echo "change version" - echo " prints which version of the change tool is being used" -} - -#### Tags #### - -get_git_tags() { - reverse=${1:-v} # send arg "-v" to reverse the list - git tag --list "[0-9]*\.[0-9]*.*" "v[0-9]*\.[0-9]*.*" --sort="${reverse}:refname" -} - -get_tag_date() { +usage() { + echo "usage: change [COMMAND [ARGS]] + +change [--bump PATH] + updates an existing $log_file + optional: --bump passes the newest version as an arg to a script at PATH + +change init + creates a $log_file with the first version + +change tag [-p] + tags the latest local commit with the lastest $log_file version + optional: -p also pushes that tag + +change auth [--token TOKEN] + prompts you for a personal access token for posting releases + optional: --token uses TOKEN instead of asking for it interactively + +change post [--dry-run] + posts a GitHub release for the latest version in $log_file + optional: --dry-run prints url, version, and body without sending + +change all [--bump PATH] + runs change, then opens $log_file in \$EDITOR + commits and pushes the $log_file + runs change tag -p and then runs change post + optional: --bump passes the newest version as an arg to a script at PATH + +change version + prints which version of the change tool is being used" +} + +filter_semver() { + sed -En 's,^(v|V)?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$,\1\2.\3.\4\5\9,p' +} + +semver_git_tags() { + git tag --list --sort="-v:refname" | filter_semver +} + +tag_date() { date=$(git log -1 --pretty=format:"%ci" "$1" 2>/dev/null | cut -d " " -f 1) - [ "$date" ] && { echo "$date"; return; } + [ "$date" ] && echo "$date" && return date +%F } @@ -61,8 +62,8 @@ remote_url_clean() { remote_url | sed -e "s|^git@\(.*\.com\):\(.*\)|https://\1/\2|" -e "s|\(.*\)\.git|\1|" } -get_latest_log_tag() { - sed -n "s|^\[Unreleased\]:.*/\(.*\)\.\.\.HEAD$|\1|p" $change_file +latest_log_tag() { + sed -n "s|^\[Unreleased\]:.*/\(.*\)\.\.\.HEAD$|\1|p" $log_file } rm_tag_prefix() { @@ -72,18 +73,18 @@ rm_tag_prefix() { inc_tag_major() { cur_major=$(echo "$1" | sed "s|^[^0-9]*\([0-9]*\)\..*$|\1|") echo "$1" | sed "s|\.[0-9]*|.0|g" | - sed "s|\(^[^0-9]*\)[0-9]*\(\..*$\)|\1$(( cur_major + 1 ))\2|" + sed "s|\(^[^0-9]*\)[0-9]*\(\..*$\)|\1$((cur_major + 1))\2|" } inc_tag_minor() { cur_minor=$(echo "$1" | sed "s|^.*\.\([0-9]*\)\..*$|\1|") echo "$1" | sed "s|\.[0-9]*|.0|g" | - sed "s|\(^.*\.\)[0-9]*\(\..*$\)|\1$(( cur_minor + 1 ))\2|" + sed "s|\(^.*\.\)[0-9]*\(\..*$\)|\1$((cur_minor + 1))\2|" } inc_tag_patch() { cur_patch=$(echo "$1" | sed "s|^.*\.\([0-9]*\).*$|\1|") - echo "$1" | sed "s|\(^.*\.\)[0-9]*\(.*$\)|\1$(( cur_patch + 1 ))\2|" + echo "$1" | sed "s|\(^.*\.\)[0-9]*\(.*$\)|\1$((cur_patch + 1))\2|" } rm_tag_suffix() { @@ -121,7 +122,7 @@ filter_patch_commits() { grep -i "$args" "^fix: " \ -e "^fix(.*): " \ - -e "^[*-] *fix: "\ + -e "^[*-] *fix: " \ -e "^[*-] *fix(.*): " } @@ -159,7 +160,7 @@ fmt_commits() { done } -get_commits_between() { +commits_between() { log_format=$1 latest_git_tag=$2 latest_log_tag=$3 @@ -171,15 +172,17 @@ get_commits_between() { tag_range="$latest_log_tag.." [ "$latest_git_tag" = "$highest_tag" ] && tag_range="$latest_log_tag..$needed_tag" - git log --pretty=format:"$log_format" "$tag_range" | sed "s| $||" + # git log --pretty=format:"$log_format" "$tag_range" | sed "s|^M$||" + git log --pretty=format:"$log_format" "$tag_range" | sed "s|\r$||" } #### Utils #### -get_new_tag() { +new_tag() { latest_changes=$1 latest_git_tag=$2 + # TODO: fix these return echo "$latest_changes" | filter_major_commits -q && inc_tag_major "$latest_git_tag" && return echo "$latest_changes" | filter_minor_commits -q && inc_tag_minor "$latest_git_tag" && return echo "$latest_changes" | filter_patch_commits -q && inc_tag_patch "$latest_git_tag" && return @@ -206,19 +209,19 @@ format_commits() { [ "$list" ] && echo "### Changed" && fmt_commits "$list" && echo } -get_needed_tags() { +needed_tags() { latest_git_tag=$1 latest_log_tag=$2 - tags=$(get_git_tags -v) + tags=$(semver_git_tags) - ! echo "$tags" | grep -q "^${latest_log_tag}$" && return 0 + ! echo "$tags" | grep -q "^${latest_log_tag}$" && return echo "$tags" | sed -n "/^${latest_log_tag}$/!p;//q" | sed '1!G;h;$!d' latest_changes=$(git log --pretty=format:"%B" "$latest_git_tag..") [ ! "$latest_changes" ] && return - get_new_tag "$latest_changes" "$latest_git_tag" | rm_tag_suffix + new_tag "$latest_changes" "$latest_git_tag" | rm_tag_suffix } update_diffs() { @@ -229,14 +232,14 @@ update_diffs() { latest_log_ver=$(rm_tag_prefix "$latest_log_tag") needed_ver=$(rm_tag_prefix "$needed_tag") - new_commits_head=$(get_commits_between "%s" "$latest_git_tag" "$latest_log_tag" "$needed_tag") - new_commits_body=$(get_commits_between "%b" "$latest_git_tag" "$latest_log_tag" "$needed_tag") + new_commits_head=$(commits_between "%s" "$latest_git_tag" "$latest_log_tag" "$needed_tag") + new_commits_body=$(commits_between "%b" "$latest_git_tag" "$latest_log_tag" "$needed_tag") - new_section="## [$needed_ver] - $(get_tag_date "$needed_tag")\\ + new_section="## [$needed_ver] - $(tag_date "$needed_tag")\\ $(format_commits "$new_commits_head" "$new_commits_body" | sed -e "s|\([&\\]\)|\\\\\1|g" -e "s|$|\\\\|")" regex="s|\(^## \[$latest_log_ver\] -.*$\)|$new_section$nl\1|" - sed -i.backup "$regex" $change_file && rm "${change_file}.backup" + sed -i.backup "$regex" $log_file && rm "${log_file}.backup" } update_links() { @@ -250,39 +253,39 @@ update_links() { new_link="[$needed_ver]: $repo_url/compare/$latest_log_tag...$needed_tag" regex="s|\(^\[$latest_log_ver\]:.*$\)|$new_link\\$nl\1|" - sed -i.backup "$regex" "$change_file" && rm "${change_file}.backup" + sed -i.backup "$regex" "$log_file" && rm "${log_file}.backup" regex="s|\(^\[Unreleased\]:.*\)/$latest_log_tag\.\.\.HEAD$|\1/$needed_tag...HEAD|" - sed -i.backup "$regex" "$change_file" && rm "${change_file}.backup" + sed -i.backup "$regex" "$log_file" && rm "${log_file}.backup" } startup_checks() { - [ ! -e "$change_file" ] && { echo "couldn't find $change_file" >&2; return 1; } - [ ! -d .git ] && { echo "the current directory doesn't contain .git" >&2; return 1; } - ! remote_url >/dev/null && { echo "cannot find remote url for this repo" >&2; return 1; } + [ ! -e "$log_file" ] && echo "couldn't find $log_file" >&2 && return 1 + [ ! -d .git ] && echo "the current directory doesn't contain .git" >&2 && return 1 + ! remote_url >/dev/null && echo "cannot find remote url for this repo" >&2 && return 1 return 0 } bump_version() { - [ ! "$bump_script" ] && return 0 - latest_log_tag=$(get_latest_log_tag) + [ ! "$bump_script" ] && return + latest_log_tag=$(latest_log_tag) [ -x "$bump_script" ] && - { echo "executing $bump_script $latest_log_tag"; "$bump_script" "$latest_log_tag"; return; } + echo "executing $bump_script $latest_log_tag" && "$bump_script" "$latest_log_tag" && return [ ! -x "$bump_script" ] && [ -f "$bump_script" ] && - { echo "executing sh $bump_script $latest_log_tag"; sh "$bump_script" "$latest_log_tag"; return; } + echo "executing sh $bump_script $latest_log_tag" && sh "$bump_script" "$latest_log_tag" && return - [ ! -f "$bump_script" ] && echo "script not found: $bump_script" >&2 - return 1 + [ ! -f "$bump_script" ] && + echo "script not found: $bump_script" >&2 && return 1 } #### Public #### -Make_and_push_tag() { +tag_and_push() { ! startup_checks && return 1 - latest_log_tag=$(get_latest_log_tag) + latest_log_tag=$(latest_log_tag) repo_url=$(remote_url) git tag "$latest_log_tag" || return 1 @@ -297,29 +300,29 @@ Make_and_push_tag() { printf "to push all local tags use:\n\tgit push %s --tags\n" "$repo_url" } -Save_auth() { +save_auth() { [ ! "$token" ] && echo "enter a personal access token" && printf "token: " && read -r token - [ ! "$token" ] && { echo "invalid token" >&2; return 1; } + [ ! "$token" ] && echo "invalid token" >&2 && return 1 [ ! -d "$auth_dir" ] && mkdir -p "$auth_dir" - echo "$token" | base64 > "$auth_file" && echo "git auth saved" + echo "$token" | base64 >"$auth_file" && echo "git auth saved" } -Post_release() { +post_release() { ! startup_checks && return 1 - [ ! -e "$auth_file" ] && { echo "you need to save a token with the auth" >&2; return 1; } + [ ! -e "$auth_file" ] && echo "you need to save a token with the auth" >&2 && return 1 - auth_token=$(base64 --decode < "$auth_file") - latest_log_tag=$(get_latest_log_tag) + auth_token=$(base64 --decode <"$auth_file") + latest_log_tag=$(latest_log_tag) latest_log_ver=$(rm_tag_prefix "$latest_log_tag") repo_url=$(remote_url_clean | sed "s|\(^.*\)\(github.com\)|\1api.\2/repos|") - diff=$(sed -n "/^## \[$latest_log_ver\] -.*/,/^## \[.*\] -.*/P" $change_file | sed "\$d") - link=$(grep "^\[$latest_log_ver\]: .*" $change_file) + diff=$(sed -n "/^## \[$latest_log_ver\] -.*/,/^## \[.*\] -.*/P" $log_file | sed "\$d") + link=$(grep "^\[$latest_log_ver\]: .*" $log_file) body=$(echo "$diff$nl$nl$link" | sed -e "s|\([\"\\]\)|\\\\\1|g" -e "s|$|\\\\|" | tr '\n' 'n') [ "$dry_run" = "true" ] && - printf "%s\n%s\n%s\n" "$repo_url" "$latest_log_tag" "$body" && return 0 + printf "%s\n%s\n%s\n" "$repo_url" "$latest_log_tag" "$body" && return curl -X POST "$repo_url/releases" \ -H "Authorization: token $auth_token" \ @@ -327,21 +330,21 @@ Post_release() { -d "{\"tag_name\": \"$latest_log_tag\", \"name\": \"$latest_log_tag\", \"body\": \"$body\" }" } -Create_changelog() { - [ ! -d .git ] && { echo "the current directory doesn't contain .git" >&2; return 1; } - [ -e "$change_file" ] && { echo "$change_file already exists" >&2; return 1; } +create_log() { + [ ! -d .git ] && echo "the current directory doesn't contain .git" >&2 && return 1 + [ -e "$log_file" ] && echo "$log_file already exists" >&2 && return 1 - first_tag=$(get_git_tags | head -1) - [ ! "$first_tag" ] && { echo "couldn't find any valid version tags" >&2; return 2; } + first_tag=$(semver_git_tags | tail -n 1) + [ ! "$first_tag" ] && echo "couldn't find any valid version tags" >&2 && return 2 first_ver=$(rm_tag_prefix "$first_tag") - [ ! "$first_ver" ] && { echo "couldn't get first version" >&2; return 2; } + [ ! "$first_ver" ] && echo "couldn't get first version" >&2 && return 2 first_commits=$(git log --pretty=format:"%s" "$first_tag") - [ ! "$first_commits" ] && { echo "couldn't get first commits" >&2; return 2; } + [ ! "$first_commits" ] && echo "couldn't get first commits" >&2 && return 2 repo_url=$(remote_url_clean) - [ ! "$repo_url" ] && { echo "remote url isn't set for this repo" >&2; return 1; } + [ ! "$repo_url" ] && echo "remote url isn't set for this repo" >&2 && return 1 echo "# Changelog All notable changes to this project will be documented in this file. @@ -351,56 +354,57 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [$first_ver] - $(get_tag_date "$first_tag") +## [$first_ver] - $(tag_date "$first_tag") $(format_commits "$first_commits") [Unreleased]: $repo_url/compare/$first_tag...HEAD -[$first_ver]: $repo_url/releases/tag/$first_tag" > "$change_file" +[$first_ver]: $repo_url/releases/tag/$first_tag" >"$log_file" - [ ! -e "$change_file" ] && - echo "something when wrong while creating $change_file" >&2 && return 1 - echo "created $change_file" + [ ! -e "$log_file" ] && + echo "something when wrong while creating $log_file" >&2 && return 1 + echo "created $log_file" } -Update_changelog() { +update_log() { ! startup_checks && return 1 - latest_git_tag=$(get_git_tags -v | head -1) - latest_log_tag=$(get_latest_log_tag) - all_needed_tags=$(get_needed_tags "$latest_git_tag" "$latest_log_tag") + latest_git_tag=$(semver_git_tags | head -1) + latest_log_tag=$(latest_log_tag) + all_needed_tags=$(needed_tags "$latest_git_tag" "$latest_log_tag") - [ ! "$all_needed_tags" ] && { echo "no new versions to add"; return 1; } + [ ! "$all_needed_tags" ] && echo "no new versions to add" && return 1 IFS="$nl" for needed_tag in $all_needed_tags; do update_diffs "$latest_git_tag" "$latest_log_tag" "$needed_tag" update_links "$latest_log_tag" "$needed_tag" - echo "added $needed_tag to $change_file" + echo "added $needed_tag to $log_file" latest_log_tag="$needed_tag" done - [ ! "$command" ] && { bump_version; return; } + # TODO: fix this return + [ ! "$command" ] && bump_version && return return 0 } -Run_all() { - save_file=$(cat $change_file) - ! Update_changelog && return 1 - first_change=$(stat -qf "%Sc" $change_file) - ${EDITOR:-vi} $change_file - last_change=$(stat -qf "%Sc" $change_file) +run_all() { + save_file=$(cat $log_file) + ! update_log && return 1 + first_change=$(stat -qf "%Sc" $log_file) + ${EDITOR:-vi} $log_file + last_change=$(stat -qf "%Sc" $log_file) [ "$first_change" = "$last_change" ] && - echo "cancelling because no manual edits were made to $change_file" >&2 && - echo "$save_file" > $change_file && + echo "cancelling because no manual edits were made to $log_file" >&2 && + echo "$save_file" >$log_file && return 1 bump_version push_tag="true" - git add $change_file && + git add $log_file && git commit --quiet -m "docs: update changelog" && git push --quiet && - Make_and_push_tag && Post_release + tag_and_push && post_release } #### Args #### @@ -411,31 +415,30 @@ command=$1 while [ $# != 0 ]; do case $1 in - "-p" ) push_tag="true";; - "--dry-run" ) dry_run="true";; - "--bump" ) bump_script="$2"; shift;; - "--bump="* ) bump_script="${1#*=}";; - "--token" ) token="$2"; shift;; - "--token="* ) token="${1#*=}";; - "--help" | "-h" ) Usage; exit;; - "--version" | "-v" ) echo "$change_version"; exit;; - * ) echo "invalid argument: $1" >&2; exit 1;; + "-p") push_tag="true" ;; + "--dry-run") dry_run="true" ;; + "--bump") bump_script="$2" && shift ;; + "--bump="*) bump_script="${1#*=}" ;; + "--token") token="$2" && shift ;; + "--token="*) token="${1#*=}" ;; + "--help" | "-h") usage && exit ;; + "--version" | "-v") echo "$change_version" && exit ;; + *) echo "invalid argument: $1" >&2 && exit 1 ;; esac shift done - case $command in - "" ) Update_changelog;; - "init" ) Create_changelog;; - "auth" ) Save_auth;; - "tag" ) Make_and_push_tag;; - "post" ) Post_release;; - "all" ) Run_all;; - "help" ) Usage;; - "version" ) echo "$change_version";; - * ) echo "invalid command: $command" >&2; exit 1;; +"") update_log ;; +"init") create_log ;; +"auth") save_auth ;; +"tag") tag_and_push ;; +"post") post_release ;; +"all") run_all ;; +"help") usage ;; +"version") echo "$change_version" ;; +*) echo "invalid command: $command" >&2 && exit 1 ;; esac exit From 23451e08bbba6b95e06dfd615acf7614b848b8d8 Mon Sep 17 00:00:00 2001 From: Adam Abrams Date: Fri, 30 Aug 2024 00:48:08 -0500 Subject: [PATCH 02/12] improve version handling --- change | 70 +++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/change b/change index 1ce15c1..89014e7 100755 --- a/change +++ b/change @@ -1,5 +1,6 @@ #!/bin/sh +# TODO: fix quotes log_file="CHANGELOG.md" xdg_data_home=${XDG_DATA_HOME:-$HOME/.local/share} auth_dir="$xdg_data_home/change/" @@ -40,55 +41,68 @@ change version prints which version of the change tool is being used" } +semver_select() { + groups=$1 + # for full version use: '\1\2.\3.\4\5\9' + + sed -En "s,^(v)?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$,$groups,p" +} + filter_semver() { - sed -En 's,^(v|V)?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$,\1\2.\3.\4\5\9,p' + semver_select '\1\2.\3.\4\5\9' } semver_git_tags() { - git tag --list --sort="-v:refname" | filter_semver + git tag --list --sort='-v:refname' | filter_semver } -tag_date() { - date=$(git log -1 --pretty=format:"%ci" "$1" 2>/dev/null | cut -d " " -f 1) - [ "$date" ] && echo "$date" && return - date +%F +inc_tag_major() { + tag=$1 + major=$(echo "$tag" | semver_select '\2') + echo "$tag" | semver_select "\1$((major + 1)).0.0\5\9" } -remote_url() { - git ls-remote --get-url 2>/dev/null +inc_tag_minor() { + tag=$1 + minor=$(echo "$tag" | semver_select '\3') + echo "$tag" | semver_select "\1\2.$((minor + 1)).0\5\9" } -remote_url_clean() { - remote_url | sed -e "s|^git@\(.*\.com\):\(.*\)|https://\1/\2|" -e "s|\(.*\)\.git|\1|" +inc_tag_patch() { + tag=$1 + patch=$(echo "$tag" | semver_select '\4') + echo "$tag" | semver_select "\1\2.\3.$((patch + 1))\5\9" } -latest_log_tag() { - sed -n "s|^\[Unreleased\]:.*/\(.*\)\.\.\.HEAD$|\1|p" $log_file +rm_tag_suffix() { + semver_select '\1\2.\3.\4' } rm_tag_prefix() { - echo "$1" | sed "s|^[^0-9]*\([0-9.]*.*\)|\1|" + tag=$1 + echo "${tag#v}" } -inc_tag_major() { - cur_major=$(echo "$1" | sed "s|^[^0-9]*\([0-9]*\)\..*$|\1|") - echo "$1" | sed "s|\.[0-9]*|.0|g" | - sed "s|\(^[^0-9]*\)[0-9]*\(\..*$\)|\1$((cur_major + 1))\2|" +tag_date_or_today() { + tag=$1 + date=$(git log -1 --pretty=format:'%ci' "$tag" -- 2>/dev/null | cut -wf1) + [ "$date" ] && echo "$date" && return + date +%F } -inc_tag_minor() { - cur_minor=$(echo "$1" | sed "s|^.*\.\([0-9]*\)\..*$|\1|") - echo "$1" | sed "s|\.[0-9]*|.0|g" | - sed "s|\(^.*\.\)[0-9]*\(\..*$\)|\1$((cur_minor + 1))\2|" +remote_url() { + git ls-remote --get-url 2>/dev/null } -inc_tag_patch() { - cur_patch=$(echo "$1" | sed "s|^.*\.\([0-9]*\).*$|\1|") - echo "$1" | sed "s|\(^.*\.\)[0-9]*\(.*$\)|\1$((cur_patch + 1))\2|" +remote_url_clean() { + remote_url | sed -E -e 's,\.git$,,' \ + -e 's,https://.*@(.*),https://\1,' \ + -e 's,^git@(.*):(.*),https://\1/\2,' + } -rm_tag_suffix() { - sed "s|\(^[^0-9]*[0-9.]*\).*|\1|" +latest_log_tag() { + sed -En 's,^\[Unreleased\]:.*/(.*)\.\.\.HEAD$,\1,p' $log_file } #### Commits #### @@ -235,7 +249,7 @@ update_diffs() { new_commits_head=$(commits_between "%s" "$latest_git_tag" "$latest_log_tag" "$needed_tag") new_commits_body=$(commits_between "%b" "$latest_git_tag" "$latest_log_tag" "$needed_tag") - new_section="## [$needed_ver] - $(tag_date "$needed_tag")\\ + new_section="## [$needed_ver] - $(tag_date_or_today "$needed_tag")\\ $(format_commits "$new_commits_head" "$new_commits_body" | sed -e "s|\([&\\]\)|\\\\\1|g" -e "s|$|\\\\|")" regex="s|\(^## \[$latest_log_ver\] -.*$\)|$new_section$nl\1|" @@ -354,7 +368,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [$first_ver] - $(tag_date "$first_tag") +## [$first_ver] - $(tag_date_or_today "$first_tag") $(format_commits "$first_commits") [Unreleased]: $repo_url/compare/$first_tag...HEAD From cfa3c740c98dfe89a09a613ba2e09b013912850b Mon Sep 17 00:00:00 2001 From: Adam Abrams Date: Fri, 30 Aug 2024 02:14:59 -0500 Subject: [PATCH 03/12] update bump script --- scripts/update-version | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/scripts/update-version b/scripts/update-version index 4abd11b..8e4d1a8 100755 --- a/scripts/update-version +++ b/scripts/update-version @@ -1,15 +1,13 @@ #!/bin/sh new_version=$1 -version_file="change" +version_file='change' -[ ! -e $version_file ] && - { echo "$version_file not found in current directory"; exit 1; } +[ ! -f $version_file ] && echo "$version_file not found in current directory" >&2 && exit 1 -sed -i.backup "s|\(^change_version=\"\).*\(\"$\)|\1$new_version\2|" $version_file && - rm "${version_file}.backup" && - git add "$version_file" && - { echo "success - changed version to $new_version"; exit 0; } +sed -E -i.backup "s,^(version=\").*(\")$,\1$new_version\2," "$version_file" && + rm "$version_file.backup" && git add "$version_file" && + echo "success: changed version to $new_version" && exit -echo "failure - something when wrong" +echo "failure: something when wrong" >&2 exit 1 From 7bac918ae4031899ca8d4f437da996fbbc4b6251 Mon Sep 17 00:00:00 2001 From: Adam Abrams Date: Fri, 30 Aug 2024 02:15:40 -0500 Subject: [PATCH 04/12] fix structure --- change | 95 ++++++++++++++++++++++++++++------------------------------ 1 file changed, 46 insertions(+), 49 deletions(-) diff --git a/change b/change index 89014e7..b2c6001 100755 --- a/change +++ b/change @@ -1,13 +1,10 @@ #!/bin/sh -# TODO: fix quotes +version="0.14.5" log_file="CHANGELOG.md" xdg_data_home=${XDG_DATA_HOME:-$HOME/.local/share} auth_dir="$xdg_data_home/change/" auth_file="$auth_dir/auth" -change_version="0.14.5" -nl=" -" usage() { echo "usage: change [COMMAND [ARGS]] @@ -41,9 +38,11 @@ change version prints which version of the change tool is being used" } +#### Tags #### + semver_select() { - groups=$1 # for full version use: '\1\2.\3.\4\5\9' + groups=$1 sed -En "s,^(v)?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$,$groups,p" } @@ -90,6 +89,12 @@ tag_date_or_today() { date +%F } +latest_log_tag() { + sed -En 's,^\[Unreleased\]:.*/(.*)\.\.\.HEAD$,\1,p' $log_file +} + +#### URLs #### + remote_url() { git ls-remote --get-url 2>/dev/null } @@ -101,10 +106,6 @@ remote_url_clean() { } -latest_log_tag() { - sed -En 's,^\[Unreleased\]:.*/(.*)\.\.\.HEAD$,\1,p' $log_file -} - #### Commits #### filter_major_commits() { @@ -164,14 +165,12 @@ fmt_conv_type() { } fmt_commits() { - all_commits=$(echo "$1" | fmt_conv_type) - - IFS="$nl" - for commit in $all_commits; do - first_char=$(echo "$commit" | cut -c 1 | tr "[:lower:]" "[:upper:]") - - echo "$commit" | sed -e "s|^.\(.*$\)|- $first_char\1|" -e 's|\(.*[^.!?]\)$|\1.|' + while read -r commit; do + first_char=$(echo "$commit" | cut -c1 | tr '[:lower:]' '[:upper:]') + echo "$commit" | sed -e "s,^.\(.*$\),- $first_char\1," -e 's,\(.*[^.!?]\)$,\1.,' done + + printf '\n' } commits_between() { @@ -180,8 +179,7 @@ commits_between() { latest_log_tag=$3 needed_tag=$4 - highest_tag=$(printf "%s\n%s\n" "$latest_git_tag" "$needed_tag" | - sort -Vr | head -1) + highest_tag=$(printf '%s\n%s\n' "$latest_git_tag" "$needed_tag" | sort -Vr | head -1) tag_range="$latest_log_tag.." [ "$latest_git_tag" = "$highest_tag" ] && tag_range="$latest_log_tag..$needed_tag" @@ -196,7 +194,6 @@ new_tag() { latest_changes=$1 latest_git_tag=$2 - # TODO: fix these return echo "$latest_changes" | filter_major_commits -q && inc_tag_major "$latest_git_tag" && return echo "$latest_changes" | filter_minor_commits -q && inc_tag_minor "$latest_git_tag" && return echo "$latest_changes" | filter_patch_commits -q && inc_tag_patch "$latest_git_tag" && return @@ -217,10 +214,10 @@ format_commits() { fix=$(echo "$list" | filter_patch_commits) list=$(echo "$list" | filter_patch_commits -v) - [ "$dep" ] && echo "### BREAKING CHANGE" && fmt_commits "$dep" && echo - [ "$feat" ] && echo "### Added" && fmt_commits "$feat" && echo - [ "$fix" ] && echo "### Fixed" && fmt_commits "$fix" && echo - [ "$list" ] && echo "### Changed" && fmt_commits "$list" && echo + [ "$dep" ] && echo "### BREAKING CHANGE" && echo "$dep" | fmt_conv_type | fmt_commits + [ "$feat" ] && echo "### Added" && echo "$feat" | fmt_conv_type | fmt_commits + [ "$fix" ] && echo "### Fixed" && echo "$fix" | fmt_conv_type | fmt_commits + [ "$list" ] && echo "### Changed" && echo "$list" | fmt_conv_type | fmt_commits } needed_tags() { @@ -252,6 +249,8 @@ update_diffs() { new_section="## [$needed_ver] - $(tag_date_or_today "$needed_tag")\\ $(format_commits "$new_commits_head" "$new_commits_body" | sed -e "s|\([&\\]\)|\\\\\1|g" -e "s|$|\\\\|")" + nl=" +" regex="s|\(^## \[$latest_log_ver\] -.*$\)|$new_section$nl\1|" sed -i.backup "$regex" $log_file && rm "${log_file}.backup" } @@ -266,6 +265,8 @@ update_links() { new_link="[$needed_ver]: $repo_url/compare/$latest_log_tag...$needed_tag" + nl=" +" regex="s|\(^\[$latest_log_ver\]:.*$\)|$new_link\\$nl\1|" sed -i.backup "$regex" "$log_file" && rm "${log_file}.backup" @@ -333,7 +334,8 @@ post_release() { diff=$(sed -n "/^## \[$latest_log_ver\] -.*/,/^## \[.*\] -.*/P" $log_file | sed "\$d") link=$(grep "^\[$latest_log_ver\]: .*" $log_file) - body=$(echo "$diff$nl$nl$link" | sed -e "s|\([\"\\]\)|\\\\\1|g" -e "s|$|\\\\|" | tr '\n' 'n') + # body=$(echo "$diff$nl$nl$link" | sed -e "s|\([\"\\]\)|\\\\\1|g" -e "s|$|\\\\|" | tr '\n' 'n') + body=$(printf '%s\n\n%s\n' "$diff" "$link" | sed -e "s|\([\"\\]\)|\\\\\1|g" -e "s|$|\\\\|" | tr '\n' 'n') [ "$dry_run" = "true" ] && printf "%s\n%s\n%s\n" "$repo_url" "$latest_log_tag" "$body" && return @@ -388,8 +390,7 @@ update_log() { [ ! "$all_needed_tags" ] && echo "no new versions to add" && return 1 - IFS="$nl" - for needed_tag in $all_needed_tags; do + echo "$all_needed_tags" | while read -r needed_tag; do update_diffs "$latest_git_tag" "$latest_log_tag" "$needed_tag" update_links "$latest_log_tag" "$needed_tag" echo "added $needed_tag to $log_file" @@ -397,10 +398,8 @@ update_log() { latest_log_tag="$needed_tag" done - # TODO: fix this return - [ ! "$command" ] && bump_version && return - - return 0 + [ "$command" ] && return 0 + bump_version } run_all() { @@ -424,19 +423,19 @@ run_all() { #### Args #### command=$1 -[ "$(expr "$command" : "-.*")" != 0 ] && command="" +[ "$(expr "$command" : '-.*')" != 0 ] && command='' [ "$command" ] && shift while [ $# != 0 ]; do case $1 in - "-p") push_tag="true" ;; - "--dry-run") dry_run="true" ;; - "--bump") bump_script="$2" && shift ;; - "--bump="*) bump_script="${1#*=}" ;; - "--token") token="$2" && shift ;; - "--token="*) token="${1#*=}" ;; - "--help" | "-h") usage && exit ;; - "--version" | "-v") echo "$change_version" && exit ;; + '-p') push_tag='true' ;; + '--dry-run') dry_run='true' ;; + '--bump') bump_script="$2" && shift ;; + '--bump='*) bump_script="${1#*=}" ;; + '--token') token="$2" && shift ;; + '--token='*) token="${1#*=}" ;; + '--help' | '-h') usage && exit ;; + '--version' | '-v') echo "$version" && exit ;; *) echo "invalid argument: $1" >&2 && exit 1 ;; esac @@ -444,15 +443,13 @@ while [ $# != 0 ]; do done case $command in -"") update_log ;; -"init") create_log ;; -"auth") save_auth ;; -"tag") tag_and_push ;; -"post") post_release ;; -"all") run_all ;; -"help") usage ;; -"version") echo "$change_version" ;; +'') update_log ;; +'init') create_log ;; +'auth') save_auth ;; +'tag') tag_and_push ;; +'post') post_release ;; +'all') run_all ;; +'help') usage ;; +'version') echo "$version" ;; *) echo "invalid command: $command" >&2 && exit 1 ;; esac - -exit From 875012265797c4d9957d8d3269adfbdc4c5ff823 Mon Sep 17 00:00:00 2001 From: Adam Abrams Date: Fri, 30 Aug 2024 02:23:54 -0500 Subject: [PATCH 05/12] use portable flags with cut --- change | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/change b/change index b2c6001..9e6e4ed 100755 --- a/change +++ b/change @@ -84,7 +84,7 @@ rm_tag_prefix() { tag_date_or_today() { tag=$1 - date=$(git log -1 --pretty=format:'%ci' "$tag" -- 2>/dev/null | cut -wf1) + date=$(git log -1 --pretty=format:'%ci' "$tag" -- 2>/dev/null | cut -d' ' -f1) [ "$date" ] && echo "$date" && return date +%F } From e943e0d452e7a4dac3c181a31cf1c21c02e1604a Mon Sep 17 00:00:00 2001 From: Adam Abrams Date: Fri, 30 Aug 2024 08:47:04 -0500 Subject: [PATCH 06/12] improve control flow and add todos --- change | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/change b/change index 9e6e4ed..77df3aa 100755 --- a/change +++ b/change @@ -100,8 +100,9 @@ remote_url() { } remote_url_clean() { - remote_url | sed -E -e 's,\.git$,,' \ - -e 's,https://.*@(.*),https://\1,' \ + remote_url | sed -E \ + -e 's,\.git$,,' \ + -e 's,^https://.*@(.*),https://\1,' \ -e 's,^git@(.*):(.*),https://\1/\2,' } @@ -164,6 +165,7 @@ fmt_conv_type() { -e "s|^BREAKING[ -]CHANGE|a breaking change|g" } +# TODO: remove var from sed expression fmt_commits() { while read -r commit; do first_char=$(echo "$commit" | cut -c1 | tr '[:lower:]' '[:upper:]') @@ -220,13 +222,14 @@ format_commits() { [ "$list" ] && echo "### Changed" && echo "$list" | fmt_conv_type | fmt_commits } +# TODO: remove var from sed expression needed_tags() { latest_git_tag=$1 latest_log_tag=$2 tags=$(semver_git_tags) - ! echo "$tags" | grep -q "^${latest_log_tag}$" && return + echo "$tags" | grep -q "^${latest_log_tag}$" || return 0 echo "$tags" | sed -n "/^${latest_log_tag}$/!p;//q" | sed '1!G;h;$!d' latest_changes=$(git log --pretty=format:"%B" "$latest_git_tag..") @@ -235,6 +238,7 @@ needed_tags() { new_tag "$latest_changes" "$latest_git_tag" | rm_tag_suffix } +# TODO: remove var from sed expression update_diffs() { latest_git_tag=$1 latest_log_tag=$2 @@ -252,9 +256,10 @@ $(format_commits "$new_commits_head" "$new_commits_body" | sed -e "s|\([&\\]\)|\ nl=" " regex="s|\(^## \[$latest_log_ver\] -.*$\)|$new_section$nl\1|" - sed -i.backup "$regex" $log_file && rm "${log_file}.backup" + sed -i.backup "$regex" $log_file && rm "$log_file.backup" } +# TODO: remove var from sed expression update_links() { latest_log_tag=$1 needed_tag=$2 @@ -268,18 +273,17 @@ update_links() { nl=" " regex="s|\(^\[$latest_log_ver\]:.*$\)|$new_link\\$nl\1|" - sed -i.backup "$regex" "$log_file" && rm "${log_file}.backup" + sed -i.backup "$regex" $log_file && rm "$log_file.backup" regex="s|\(^\[Unreleased\]:.*\)/$latest_log_tag\.\.\.HEAD$|\1/$needed_tag...HEAD|" - sed -i.backup "$regex" "$log_file" && rm "${log_file}.backup" + sed -i.backup "$regex" $log_file && rm "$log_file.backup" } startup_checks() { - [ ! -e "$log_file" ] && echo "couldn't find $log_file" >&2 && return 1 + [ ! -f $log_file ] && echo "couldn't find $log_file" >&2 && return 1 [ ! -d .git ] && echo "the current directory doesn't contain .git" >&2 && return 1 - ! remote_url >/dev/null && echo "cannot find remote url for this repo" >&2 && return 1 - - return 0 + remote_url >/dev/null && return + echo "cannot find remote url for this repo" >&2 && return 1 } bump_version() { @@ -299,7 +303,7 @@ bump_version() { #### Public #### tag_and_push() { - ! startup_checks && return 1 + startup_checks || return 1 latest_log_tag=$(latest_log_tag) repo_url=$(remote_url) @@ -324,12 +328,13 @@ save_auth() { } post_release() { - ! startup_checks && return 1 + startup_checks || return 1 [ ! -e "$auth_file" ] && echo "you need to save a token with the auth" >&2 && return 1 auth_token=$(base64 --decode <"$auth_file") latest_log_tag=$(latest_log_tag) latest_log_ver=$(rm_tag_prefix "$latest_log_tag") + # TODO: create function for this repo_url=$(remote_url_clean | sed "s|\(^.*\)\(github.com\)|\1api.\2/repos|") diff=$(sed -n "/^## \[$latest_log_ver\] -.*/,/^## \[.*\] -.*/P" $log_file | sed "\$d") @@ -340,6 +345,7 @@ post_release() { [ "$dry_run" = "true" ] && printf "%s\n%s\n%s\n" "$repo_url" "$latest_log_tag" "$body" && return + # TODO: improve escaping curl -X POST "$repo_url/releases" \ -H "Authorization: token $auth_token" \ -H "Content-Type: application/json" \ @@ -348,7 +354,7 @@ post_release() { create_log() { [ ! -d .git ] && echo "the current directory doesn't contain .git" >&2 && return 1 - [ -e "$log_file" ] && echo "$log_file already exists" >&2 && return 1 + [ -e $log_file ] && echo "$log_file already exists" >&2 && return 1 first_tag=$(semver_git_tags | tail -n 1) [ ! "$first_tag" ] && echo "couldn't find any valid version tags" >&2 && return 2 @@ -374,15 +380,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 $(format_commits "$first_commits") [Unreleased]: $repo_url/compare/$first_tag...HEAD -[$first_ver]: $repo_url/releases/tag/$first_tag" >"$log_file" +[$first_ver]: $repo_url/releases/tag/$first_tag" >$log_file - [ ! -e "$log_file" ] && - echo "something when wrong while creating $log_file" >&2 && return 1 + [ ! -s $log_file ] && + echo "something went wrong while creating $log_file" >&2 && return 1 echo "created $log_file" } update_log() { - ! startup_checks && return 1 + startup_checks || return 1 latest_git_tag=$(semver_git_tags | head -1) latest_log_tag=$(latest_log_tag) @@ -404,7 +410,7 @@ update_log() { run_all() { save_file=$(cat $log_file) - ! update_log && return 1 + update_log || return 1 first_change=$(stat -qf "%Sc" $log_file) ${EDITOR:-vi} $log_file last_change=$(stat -qf "%Sc" $log_file) From 3449fc296b512c5c5b066cdba941bde4fb861abe Mon Sep 17 00:00:00 2001 From: Adam Abrams Date: Fri, 30 Aug 2024 18:15:18 -0500 Subject: [PATCH 07/12] refactor functions --- change | 366 +++++++++++++++++++++++++++------------------------------ 1 file changed, 175 insertions(+), 191 deletions(-) diff --git a/change b/change index 77df3aa..a9ebfdc 100755 --- a/change +++ b/change @@ -55,22 +55,33 @@ semver_git_tags() { git tag --list --sort='-v:refname' | filter_semver } -inc_tag_major() { +tag_exists() { tag=$1 - major=$(echo "$tag" | semver_select '\2') - echo "$tag" | semver_select "\1$((major + 1)).0.0\5\9" + semver_git_tags | grep -q "^$tag$" && return + return 1 } -inc_tag_minor() { - tag=$1 - minor=$(echo "$tag" | semver_select '\3') - echo "$tag" | semver_select "\1\2.$((minor + 1)).0\5\9" +latest_inc_major() { + latest=$(semver_git_tags | head -1) + major=$(echo "$latest" | semver_select '\2') + echo "$latest" | semver_select "\1$((major + 1)).0.0\5\9" +} + +latest_inc_minor() { + latest=$(semver_git_tags | head -1) + minor=$(echo "$latest" | semver_select '\3') + echo "$latest" | semver_select "\1\2.$((minor + 1)).0\5\9" +} + +latest_inc_patch() { + latest=$(semver_git_tags | head -1) + patch=$(echo "$latest" | semver_select '\4') + echo "$latest" | semver_select "\1\2.\3.$((patch + 1))\5\9" } -inc_tag_patch() { +trim_tag() { tag=$1 - patch=$(echo "$tag" | semver_select '\4') - echo "$tag" | semver_select "\1\2.\3.$((patch + 1))\5\9" + echo "$tag" | semver_select '\2.\3.\4' } rm_tag_suffix() { @@ -89,7 +100,7 @@ tag_date_or_today() { date +%F } -latest_log_tag() { +top_log_tag() { sed -En 's,^\[Unreleased\]:.*/(.*)\.\.\.HEAD$,\1,p' $log_file } @@ -104,16 +115,16 @@ remote_url_clean() { -e 's,\.git$,,' \ -e 's,^https://.*@(.*),https://\1,' \ -e 's,^git@(.*):(.*),https://\1/\2,' - } #### Commits #### -filter_major_commits() { - args="-e" # send arg "-v" for inverse and "-q" for quiet - [ "$1" ] && args="${1}e" +# TODO: refine regex +grep_commits_major() { + flags="-e" # use '-v' for inverse or '-q' for quiet + [ "$1" ] && flags="${1}e" - grep "$args" "^[A-Za-z]*!: " \ + grep "$flags" "^[A-Za-z]*!: " \ -e "^[A-Za-z]*(.*)!: " \ -e "^[*-] *[A-Za-z]*!: " \ -e "^[*-] *[A-Za-z]*(.*)!: " \ @@ -122,26 +133,29 @@ filter_major_commits() { -e "^BREAKING[ -]CHANGE" } -filter_minor_commits() { - args="-e" # send arg "-v" for inverse and "-q" for quiet - [ "$1" ] && args="${1}e" +# TODO: refine regex +grep_commits_minor() { + flags="-e" # use '-v' for inverse or '-q' for quiet + [ "$1" ] && flags="${1}e" - grep -i "$args" "^feat: " \ + grep -i "$flags" "^feat: " \ -e "^feat(.*): " \ -e "^[*-] *feat: " \ -e "^[*-] *feat(.*): " } -filter_patch_commits() { - args="-e" # send arg "-v" for inverse and "-q" for quiet - [ "$1" ] && args="${1}e" +# TODO: refine regex +grep_commits_patch() { + flags="-e" # use '-v' for inverse or '-q' for quiet + [ "$1" ] && flags="${1}e" - grep -i "$args" "^fix: " \ + grep -i "$flags" "^fix: " \ -e "^fix(.*): " \ -e "^[*-] *fix: " \ -e "^[*-] *fix(.*): " } +# TODO: refine regex filter_conv_commits() { grep -e "^[A-Za-z]*: " -e "^[A-Za-z]*(.*): " \ -e "^[*-] *[A-Za-z]*: " -e "^[*-] *[A-Za-z]*(.*): " \ @@ -151,6 +165,8 @@ filter_conv_commits() { -e "^BREAKING[ -]CHANGE" } +# TODO: refine regex +# TODO: reconsider use of sed fmt_conv_type() { sed -e "s|^[A-Za-z]*: \(.*$\)|\1|g" \ -e "s|^[A-Za-z]*(\(.*\)): \(.*$\)|\2 for \1|g" \ @@ -165,56 +181,50 @@ fmt_conv_type() { -e "s|^BREAKING[ -]CHANGE|a breaking change|g" } -# TODO: remove var from sed expression fmt_commits() { while read -r commit; do - first_char=$(echo "$commit" | cut -c1 | tr '[:lower:]' '[:upper:]') - echo "$commit" | sed -e "s,^.\(.*$\),- $first_char\1," -e 's,\(.*[^.!?]\)$,\1.,' + char=$(echo "$commit" | cut -c1 | tr '[:lower:]' '[:upper:]') + rest=$(echo "$commit" | cut -c2- | sed -E 's,(.*[^.!?])$,\1.,') + echo "- $char$rest" done - printf '\n' } -commits_between() { - log_format=$1 - latest_git_tag=$2 - latest_log_tag=$3 - needed_tag=$4 - - highest_tag=$(printf '%s\n%s\n' "$latest_git_tag" "$needed_tag" | sort -Vr | head -1) +next_commit_range() { + next_tag=$1 - tag_range="$latest_log_tag.." - [ "$latest_git_tag" = "$highest_tag" ] && tag_range="$latest_log_tag..$needed_tag" + top_tag=$(top_log_tag) + range="$top_tag.." + tag_exists "$next_tag" && range="$top_tag..$next_tag" - # git log --pretty=format:"$log_format" "$tag_range" | sed "s|^M$||" - git log --pretty=format:"$log_format" "$tag_range" | sed "s|\r$||" + echo "$range" } #### Utils #### -new_tag() { +calc_new_tag() { latest_changes=$1 - latest_git_tag=$2 - echo "$latest_changes" | filter_major_commits -q && inc_tag_major "$latest_git_tag" && return - echo "$latest_changes" | filter_minor_commits -q && inc_tag_minor "$latest_git_tag" && return - echo "$latest_changes" | filter_patch_commits -q && inc_tag_patch "$latest_git_tag" && return + echo "$latest_changes" | grep_commits_major -q && latest_inc_major && return + echo "$latest_changes" | grep_commits_minor -q && latest_inc_minor && return + echo "$latest_changes" | grep_commits_patch -q && latest_inc_patch && return } format_commits() { - commits_head=$1 - commits_body=$(echo "$2" | filter_conv_commits) + commit_range=$1 - list=$(printf "%s\n%s" "$commits_head" "$commits_body") + sub=$(git log --pretty=format:"%s" "$commit_range") + bod=$(git log --pretty=format:"%b" "$commit_range" | filter_conv_commits) + list=$(printf '%s\n%s' "$sub" "$bod") - dep=$(echo "$list" | filter_major_commits) - list=$(echo "$list" | filter_major_commits -v) + dep=$(echo "$list" | grep_commits_major) + list=$(echo "$list" | grep_commits_major -v) - feat=$(echo "$list" | filter_minor_commits) - list=$(echo "$list" | filter_minor_commits -v) + feat=$(echo "$list" | grep_commits_minor) + list=$(echo "$list" | grep_commits_minor -v) - fix=$(echo "$list" | filter_patch_commits) - list=$(echo "$list" | filter_patch_commits -v) + fix=$(echo "$list" | grep_commits_patch) + list=$(echo "$list" | grep_commits_patch -v) [ "$dep" ] && echo "### BREAKING CHANGE" && echo "$dep" | fmt_conv_type | fmt_commits [ "$feat" ] && echo "### Added" && echo "$feat" | fmt_conv_type | fmt_commits @@ -222,212 +232,186 @@ format_commits() { [ "$list" ] && echo "### Changed" && echo "$list" | fmt_conv_type | fmt_commits } -# TODO: remove var from sed expression -needed_tags() { - latest_git_tag=$1 - latest_log_tag=$2 +# TODO: fix suffix handling +unlogged_tags() { + top_tag=$(top_log_tag) + tag_exists "$top_tag" || return 0 + semver_git_tags | sed -n "/^$top_tag$/q;p" | sort -V - tags=$(semver_git_tags) - - echo "$tags" | grep -q "^${latest_log_tag}$" || return 0 - echo "$tags" | sed -n "/^${latest_log_tag}$/!p;//q" | sed '1!G;h;$!d' - - latest_changes=$(git log --pretty=format:"%B" "$latest_git_tag..") - [ ! "$latest_changes" ] && return + latest_tag=$(semver_git_tags | head -1) + unlogged_commits=$(git log --pretty=format:"%B" "$latest_tag..") + [ ! "$unlogged_commits" ] && return + calc_new_tag "$unlogged_commits" | rm_tag_suffix +} - new_tag "$latest_changes" "$latest_git_tag" | rm_tag_suffix +startup_checks() { + [ ! -f $log_file ] && echo "couldn't find $log_file" >&2 && return 1 + [ ! -d .git ] && echo "the current directory doesn't contain .git" >&2 && return 1 + remote_url >/dev/null && return + echo "cannot find remote url for this repo" >&2 && return 1 } -# TODO: remove var from sed expression -update_diffs() { - latest_git_tag=$1 - latest_log_tag=$2 - needed_tag=$3 +bump_version() { + [ ! "$bump_script" ] && return + [ ! -f "$bump_script" ] && echo "script not found: $bump_script" >&2 && return 1 - latest_log_ver=$(rm_tag_prefix "$latest_log_tag") - needed_ver=$(rm_tag_prefix "$needed_tag") + top_tag=$(top_log_tag) + [ -x "$bump_script" ] && + echo "executing $bump_script $top_tag" && "$bump_script" "$top_tag" + [ ! -x "$bump_script" ] && + echo "executing sh $bump_script $top_tag" && sh "$bump_script" "$top_tag" +} - new_commits_head=$(commits_between "%s" "$latest_git_tag" "$latest_log_tag" "$needed_tag") - new_commits_body=$(commits_between "%b" "$latest_git_tag" "$latest_log_tag" "$needed_tag") +initial_log() { + oldest_tag=$1 - new_section="## [$needed_ver] - $(tag_date_or_today "$needed_tag")\\ -$(format_commits "$new_commits_head" "$new_commits_body" | sed -e "s|\([&\\]\)|\\\\\1|g" -e "s|$|\\\\|")" + oldest_ver=$(rm_tag_prefix "$oldest_tag") + date=$(tag_date_or_today "$oldest_tag") - nl=" -" - regex="s|\(^## \[$latest_log_ver\] -.*$\)|$new_section$nl\1|" - sed -i.backup "$regex" $log_file && rm "$log_file.backup" -} + echo "# Changelog +All notable changes to this project will be documented in this file. -# TODO: remove var from sed expression -update_links() { - latest_log_tag=$1 - needed_tag=$2 +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - latest_log_ver=$(rm_tag_prefix "$latest_log_tag") - needed_ver=$(rm_tag_prefix "$needed_tag") - repo_url=$(remote_url_clean) +## [Unreleased] - new_link="[$needed_ver]: $repo_url/compare/$latest_log_tag...$needed_tag" +## [$oldest_ver] - $date" - nl=" -" - regex="s|\(^\[$latest_log_ver\]:.*$\)|$new_link\\$nl\1|" - sed -i.backup "$regex" $log_file && rm "$log_file.backup" + format_commits "$oldest_tag" - regex="s|\(^\[Unreleased\]:.*\)/$latest_log_tag\.\.\.HEAD$|\1/$needed_tag...HEAD|" - sed -i.backup "$regex" $log_file && rm "$log_file.backup" + echo "[Unreleased]: $repo_url/compare/$oldest_tag...HEAD +[$oldest_ver]: $repo_url/releases/tag/$oldest_tag" } -startup_checks() { - [ ! -f $log_file ] && echo "couldn't find $log_file" >&2 && return 1 - [ ! -d .git ] && echo "the current directory doesn't contain .git" >&2 && return 1 - remote_url >/dev/null && return - echo "cannot find remote url for this repo" >&2 && return 1 -} - -bump_version() { - [ ! "$bump_script" ] && return - latest_log_tag=$(latest_log_tag) +# TODO: handle prefix and suffix trimming +add_to_log() { + next_tag=$1 - [ -x "$bump_script" ] && - echo "executing $bump_script $latest_log_tag" && "$bump_script" "$latest_log_tag" && return - - [ ! -x "$bump_script" ] && [ -f "$bump_script" ] && - echo "executing sh $bump_script $latest_log_tag" && sh "$bump_script" "$latest_log_tag" && return + top_tag=$(top_log_tag) + top_ver=$(rm_tag_prefix "$top_tag") + next_ver=$(rm_tag_prefix "$next_tag") + date=$(tag_date_or_today "$next_tag") + repo_url=$(remote_url_clean) + commit_range=$(next_commit_range "$next_tag") - [ ! -f "$bump_script" ] && - echo "script not found: $bump_script" >&2 && return 1 + sed -n "/^## \[$top_ver\] - [1-9][0-9-]*$/q;p" $log_file + echo "## [$next_ver] - $date" + format_commits "$commit_range" + sed -n "/^## \[$top_ver\] - [1-9][0-9-]*$/,/^\[Unreleased\]: https:[^ ]*$/p;" $log_file | + sed '$d' + echo "[Unreleased]: $repo_url/compare/$next_tag...HEAD" + echo "[$next_ver]: $repo_url/compare/$top_tag...$next_tag" + sed -n "/^\[$top_ver\]: https:[^ ]*$/,\$p" $log_file } -#### Public #### +#### Commands #### -tag_and_push() { +tag_command() { startup_checks || return 1 - latest_log_tag=$(latest_log_tag) + top_tag=$(top_log_tag) repo_url=$(remote_url) - git tag "$latest_log_tag" || return 1 - echo "tagged latest commit as $latest_log_tag" + git tag "$top_tag" || return 1 + echo "tagged latest commit as $top_tag" - [ "$push_tag" = "true" ] && - git push --quiet "$repo_url" "$latest_log_tag" && - echo "pushed $latest_log_tag to $repo_url" && - return 0 + [ "$push_tag" ] && + git push --quiet "$repo_url" "$top_tag" && echo "pushed $top_tag to $repo_url" && return - printf "to push the latest tag use:\n\tgit push %s %s\n" "$repo_url" "$latest_log_tag" + printf "to push the latest tag use:\n\tgit push %s %s\n" "$repo_url" "$top_tag" printf "to push all local tags use:\n\tgit push %s --tags\n" "$repo_url" } -save_auth() { +auth_command() { [ ! "$token" ] && echo "enter a personal access token" && printf "token: " && read -r token [ ! "$token" ] && echo "invalid token" >&2 && return 1 [ ! -d "$auth_dir" ] && mkdir -p "$auth_dir" - echo "$token" | base64 >"$auth_file" && echo "git auth saved" } -post_release() { +# TODO: test +post_command() { startup_checks || return 1 [ ! -e "$auth_file" ] && echo "you need to save a token with the auth" >&2 && return 1 - auth_token=$(base64 --decode <"$auth_file") - latest_log_tag=$(latest_log_tag) - latest_log_ver=$(rm_tag_prefix "$latest_log_tag") - # TODO: create function for this - repo_url=$(remote_url_clean | sed "s|\(^.*\)\(github.com\)|\1api.\2/repos|") + auth_token=$(<"$auth_file" base64 --decode) + top_tag=$(top_log_tag) + top_ver=$(rm_tag_prefix "$top_tag") + api_url=$(remote_url_clean | sed -E "s|^(.*)(github.com)|\1api.\2/repos|") - diff=$(sed -n "/^## \[$latest_log_ver\] -.*/,/^## \[.*\] -.*/P" $log_file | sed "\$d") - link=$(grep "^\[$latest_log_ver\]: .*" $log_file) - # body=$(echo "$diff$nl$nl$link" | sed -e "s|\([\"\\]\)|\\\\\1|g" -e "s|$|\\\\|" | tr '\n' 'n') - body=$(printf '%s\n\n%s\n' "$diff" "$link" | sed -e "s|\([\"\\]\)|\\\\\1|g" -e "s|$|\\\\|" | tr '\n' 'n') + link=$(grep "^\[$top_ver\]: https:[^ ]*$" $log_file) + entry=$(sed -n "/^## \[$top_ver\] - [1-9][0-9-]*$/,/^## \[..*\] - [1-9][0-9-]*$/p" $log_file | + sed '$d') - [ "$dry_run" = "true" ] && - printf "%s\n%s\n%s\n" "$repo_url" "$latest_log_tag" "$body" && return + # body=$(printf '%s\n\n%s\n' "$entry" "$link") + body=$(printf '%s\n\n%s\n' "$entry" "$link" | + sed -E -e "s,([\"\\]),\\\\\1,g" -e "s,$,\\\\," | tr '\n' 'n') - # TODO: improve escaping - curl -X POST "$repo_url/releases" \ + [ "$dry_run" ] && printf "%s\n%s\n%s\n" "$api_url" "$top_tag" "$body" && return + + # data=$(printf '{"tag_name": "%s", "name": "%s", "body": "%s"}' "$top_tag" "$top_tag" "$body") + + curl -X POST "$api_url/releases" \ -H "Authorization: token $auth_token" \ -H "Content-Type: application/json" \ - -d "{\"tag_name\": \"$latest_log_tag\", \"name\": \"$latest_log_tag\", \"body\": \"$body\" }" + -d "{\"tag_name\": \"$top_tag\", \"name\": \"$top_tag\", \"body\": \"$body\" }" + # --data-urlencode "=$data" } -create_log() { +init_command() { [ ! -d .git ] && echo "the current directory doesn't contain .git" >&2 && return 1 [ -e $log_file ] && echo "$log_file already exists" >&2 && return 1 - first_tag=$(semver_git_tags | tail -n 1) - [ ! "$first_tag" ] && echo "couldn't find any valid version tags" >&2 && return 2 - - first_ver=$(rm_tag_prefix "$first_tag") - [ ! "$first_ver" ] && echo "couldn't get first version" >&2 && return 2 - - first_commits=$(git log --pretty=format:"%s" "$first_tag") - [ ! "$first_commits" ] && echo "couldn't get first commits" >&2 && return 2 + oldest_tag=$(semver_git_tags | tail -n 1) + [ ! "$oldest_tag" ] && echo "couldn't find any valid version tags" >&2 && return 2 repo_url=$(remote_url_clean) [ ! "$repo_url" ] && echo "remote url isn't set for this repo" >&2 && return 1 - echo "# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [$first_ver] - $(tag_date_or_today "$first_tag") -$(format_commits "$first_commits") + initial_log "$oldest_tag" >$log_file -[Unreleased]: $repo_url/compare/$first_tag...HEAD -[$first_ver]: $repo_url/releases/tag/$first_tag" >$log_file - - [ ! -s $log_file ] && - echo "something went wrong while creating $log_file" >&2 && return 1 + [ ! -s $log_file ] && echo "something went wrong while creating $log_file" >&2 && return 1 echo "created $log_file" } -update_log() { +update_command() { startup_checks || return 1 + tags=$(unlogged_tags) + [ ! "$tags" ] && echo "no new versions to add" && return 1 - latest_git_tag=$(semver_git_tags | head -1) - latest_log_tag=$(latest_log_tag) - all_needed_tags=$(needed_tags "$latest_git_tag" "$latest_log_tag") - - [ ! "$all_needed_tags" ] && echo "no new versions to add" && return 1 + echo "$tags" | while read -r next_tag; do + new_log=$(add_to_log "$next_tag") + echo "$new_log" >$log_file - echo "$all_needed_tags" | while read -r needed_tag; do - update_diffs "$latest_git_tag" "$latest_log_tag" "$needed_tag" - update_links "$latest_log_tag" "$needed_tag" - echo "added $needed_tag to $log_file" - - latest_log_tag="$needed_tag" + echo "added $next_tag to $log_file" done [ "$command" ] && return 0 bump_version } -run_all() { - save_file=$(cat $log_file) - update_log || return 1 - first_change=$(stat -qf "%Sc" $log_file) +# TODO: refactor +all_command() { + backup=$(cat $log_file) + update_command || return 1 + + last_changed=$(stat -qf "%Sc" $log_file) ${EDITOR:-vi} $log_file - last_change=$(stat -qf "%Sc" $log_file) - [ "$first_change" = "$last_change" ] && + [ $(stat -qf "%Sc" $log_file) = $last_changed ] && echo "cancelling because no manual edits were made to $log_file" >&2 && - echo "$save_file" >$log_file && + echo "$backup" >$log_file && return 1 + bump_version push_tag="true" git add $log_file && - git commit --quiet -m "docs: update changelog" && - git push --quiet && - tag_and_push && post_release + git commit --quiet -m "docs: update changelog" && git push --quiet && + tag_and_push && post_command } #### Args #### +# TODO: refactor command=$1 [ "$(expr "$command" : '-.*')" != 0 ] && command='' [ "$command" ] && shift @@ -449,12 +433,12 @@ while [ $# != 0 ]; do done case $command in -'') update_log ;; -'init') create_log ;; -'auth') save_auth ;; -'tag') tag_and_push ;; -'post') post_release ;; -'all') run_all ;; +'') update_command ;; +'init') init_command ;; +'auth') auth_command ;; +'tag') tag_command ;; +'post') post_command ;; +'all') all_command ;; 'help') usage ;; 'version') echo "$version" ;; *) echo "invalid command: $command" >&2 && exit 1 ;; From c45c548b27e87a5fe323fbc20601cd8e82081b0d Mon Sep 17 00:00:00 2001 From: Adam Abrams Date: Fri, 30 Aug 2024 18:23:57 -0500 Subject: [PATCH 08/12] refactor arg parsing --- change | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/change b/change index a9ebfdc..a7d840d 100755 --- a/change +++ b/change @@ -355,7 +355,7 @@ post_command() { -H "Authorization: token $auth_token" \ -H "Content-Type: application/json" \ -d "{\"tag_name\": \"$top_tag\", \"name\": \"$top_tag\", \"body\": \"$body\" }" - # --data-urlencode "=$data" + # --data-urlencode "=$data" } init_command() { @@ -390,30 +390,29 @@ update_command() { bump_version } -# TODO: refactor +# TODO: add prompt all_command() { backup=$(cat $log_file) update_command || return 1 - last_changed=$(stat -qf "%Sc" $log_file) + last_changed=$(stat -qf '%Sc' $log_file) ${EDITOR:-vi} $log_file - [ $(stat -qf "%Sc" $log_file) = $last_changed ] && + [ "$(stat -qf '%Sc' $log_file)" = "$last_changed" ] && echo "cancelling because no manual edits were made to $log_file" >&2 && echo "$backup" >$log_file && return 1 bump_version - push_tag="true" + push_tag='true' git add $log_file && - git commit --quiet -m "docs: update changelog" && git push --quiet && + git commit --quiet -m 'docs: update changelog' && git push --quiet && tag_and_push && post_command } #### Args #### -# TODO: refactor command=$1 -[ "$(expr "$command" : '-.*')" != 0 ] && command='' +[ "${command#-}" != "$command" ] && command='' [ "$command" ] && shift while [ $# != 0 ]; do From cd590f01c2f79c5cbafa4bb6781a756bfb2c3cfc Mon Sep 17 00:00:00 2001 From: Adam Abrams Date: Fri, 30 Aug 2024 20:41:49 -0500 Subject: [PATCH 09/12] feat: more refactoring --- change | 147 ++++++++++++++++++++++++++------------------------------- 1 file changed, 68 insertions(+), 79 deletions(-) diff --git a/change b/change index a7d840d..af159a1 100755 --- a/change +++ b/change @@ -64,19 +64,19 @@ tag_exists() { latest_inc_major() { latest=$(semver_git_tags | head -1) major=$(echo "$latest" | semver_select '\2') - echo "$latest" | semver_select "\1$((major + 1)).0.0\5\9" + echo "$latest" | semver_select "\1$((major + 1)).0.0" } latest_inc_minor() { latest=$(semver_git_tags | head -1) minor=$(echo "$latest" | semver_select '\3') - echo "$latest" | semver_select "\1\2.$((minor + 1)).0\5\9" + echo "$latest" | semver_select "\1\2.$((minor + 1)).0" } latest_inc_patch() { latest=$(semver_git_tags | head -1) patch=$(echo "$latest" | semver_select '\4') - echo "$latest" | semver_select "\1\2.\3.$((patch + 1))\5\9" + echo "$latest" | semver_select "\1\2.\3.$((patch + 1))" } trim_tag() { @@ -84,10 +84,6 @@ trim_tag() { echo "$tag" | semver_select '\2.\3.\4' } -rm_tag_suffix() { - semver_select '\1\2.\3.\4' -} - rm_tag_prefix() { tag=$1 echo "${tag#v}" @@ -104,6 +100,25 @@ top_log_tag() { sed -En 's,^\[Unreleased\]:.*/(.*)\.\.\.HEAD$,\1,p' $log_file } +calc_new_tag() { + latest_changes=$1 + + echo "$latest_changes" | grep_commits_major -q && latest_inc_major && return + echo "$latest_changes" | grep_commits_minor -q && latest_inc_minor && return + echo "$latest_changes" | grep_commits_patch -q && latest_inc_patch && return +} + +unlogged_tags() { + top_tag=$(top_log_tag) + tag_exists "$top_tag" || return 0 + semver_git_tags | sed -n "/^$top_tag$/q;p" | sort -V + + latest_tag=$(semver_git_tags | head -1) + unlogged_commits=$(git log --pretty=format:"%B" "$latest_tag..") + [ ! "$unlogged_commits" ] && return + calc_new_tag "$unlogged_commits" +} + #### URLs #### remote_url() { @@ -119,7 +134,6 @@ remote_url_clean() { #### Commits #### -# TODO: refine regex grep_commits_major() { flags="-e" # use '-v' for inverse or '-q' for quiet [ "$1" ] && flags="${1}e" @@ -133,7 +147,6 @@ grep_commits_major() { -e "^BREAKING[ -]CHANGE" } -# TODO: refine regex grep_commits_minor() { flags="-e" # use '-v' for inverse or '-q' for quiet [ "$1" ] && flags="${1}e" @@ -144,7 +157,6 @@ grep_commits_minor() { -e "^[*-] *feat(.*): " } -# TODO: refine regex grep_commits_patch() { flags="-e" # use '-v' for inverse or '-q' for quiet [ "$1" ] && flags="${1}e" @@ -155,8 +167,7 @@ grep_commits_patch() { -e "^[*-] *fix(.*): " } -# TODO: refine regex -filter_conv_commits() { +grep_conv_commits() { grep -e "^[A-Za-z]*: " -e "^[A-Za-z]*(.*): " \ -e "^[*-] *[A-Za-z]*: " -e "^[*-] *[A-Za-z]*(.*): " \ -e "^[A-Za-z]*!: " -e "^[A-Za-z]*(.*)!: " \ @@ -165,20 +176,19 @@ filter_conv_commits() { -e "^BREAKING[ -]CHANGE" } -# TODO: refine regex -# TODO: reconsider use of sed fmt_conv_type() { - sed -e "s|^[A-Za-z]*: \(.*$\)|\1|g" \ - -e "s|^[A-Za-z]*(\(.*\)): \(.*$\)|\2 for \1|g" \ - -e "s|^[*-] *[A-Za-z]*: \(.*$\)|\1|g" \ - -e "s|^[*-] *[A-Za-z]*(\(.*\)): \(.*$\)|\2 for \1|g" \ - -e "s|^[A-Za-z]*!: \(.*$\)|\1|g" \ - -e "s|^[A-Za-z]*(\(.*\))!: \(.*$\)|\2 for \1|g" \ - -e "s|^[*-] *[A-Za-z]*!: \(.*$\)|\1|g" \ - -e "s|^[*-] *[A-Za-z]*(\(.*\))!: \(.*$\)|\2 for \1|g" \ - -e "s|^BREAKING[ -]CHANGE: \(.*$\)|\1|g" \ - -e "s|^[*-] *BREAKING[ -]CHANGE: \(.*$\)|\1|g" \ - -e "s|^BREAKING[ -]CHANGE|a breaking change|g" + sed -E \ + -e "s,^[A-Za-z]*: (.*$),\1,g" \ + -e "s,^[A-Za-z]*\((.*)\): (.*$),\2 for \1,g" \ + -e "s,^[*-] *[A-Za-z]*: (.*$),\1,g" \ + -e "s,^[*-] *[A-Za-z]*\((.*)\): (.*$),\2 for \1,g" \ + -e "s,^[A-Za-z]*!: (.*$),\1,g" \ + -e "s,^[A-Za-z]*\((.*)\)!: (.*$),\2 for \1,g" \ + -e "s,^[*-] *[A-Za-z]*!: (.*$),\1,g" \ + -e "s,^[*-] *[A-Za-z]*\((.*)\)!: (.*$),\2 for \1,g" \ + -e "s,^BREAKING[ -]CHANGE: (.*$),\1,g" \ + -e "s,^[*-] *BREAKING[ -]CHANGE: (.*$),\1,g" \ + -e "s,^BREAKING[ -]CHANGE,a breaking change,g" } fmt_commits() { @@ -200,21 +210,11 @@ next_commit_range() { echo "$range" } -#### Utils #### - -calc_new_tag() { - latest_changes=$1 - - echo "$latest_changes" | grep_commits_major -q && latest_inc_major && return - echo "$latest_changes" | grep_commits_minor -q && latest_inc_minor && return - echo "$latest_changes" | grep_commits_patch -q && latest_inc_patch && return -} - format_commits() { commit_range=$1 sub=$(git log --pretty=format:"%s" "$commit_range") - bod=$(git log --pretty=format:"%b" "$commit_range" | filter_conv_commits) + bod=$(git log --pretty=format:"%b" "$commit_range" | grep_conv_commits) list=$(printf '%s\n%s' "$sub" "$bod") dep=$(echo "$list" | grep_commits_major) @@ -232,35 +232,7 @@ format_commits() { [ "$list" ] && echo "### Changed" && echo "$list" | fmt_conv_type | fmt_commits } -# TODO: fix suffix handling -unlogged_tags() { - top_tag=$(top_log_tag) - tag_exists "$top_tag" || return 0 - semver_git_tags | sed -n "/^$top_tag$/q;p" | sort -V - - latest_tag=$(semver_git_tags | head -1) - unlogged_commits=$(git log --pretty=format:"%B" "$latest_tag..") - [ ! "$unlogged_commits" ] && return - calc_new_tag "$unlogged_commits" | rm_tag_suffix -} - -startup_checks() { - [ ! -f $log_file ] && echo "couldn't find $log_file" >&2 && return 1 - [ ! -d .git ] && echo "the current directory doesn't contain .git" >&2 && return 1 - remote_url >/dev/null && return - echo "cannot find remote url for this repo" >&2 && return 1 -} - -bump_version() { - [ ! "$bump_script" ] && return - [ ! -f "$bump_script" ] && echo "script not found: $bump_script" >&2 && return 1 - - top_tag=$(top_log_tag) - [ -x "$bump_script" ] && - echo "executing $bump_script $top_tag" && "$bump_script" "$top_tag" - [ ! -x "$bump_script" ] && - echo "executing sh $bump_script $top_tag" && sh "$bump_script" "$top_tag" -} +#### Utils #### initial_log() { oldest_tag=$1 @@ -284,7 +256,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [$oldest_ver]: $repo_url/releases/tag/$oldest_tag" } -# TODO: handle prefix and suffix trimming add_to_log() { next_tag=$1 @@ -305,6 +276,24 @@ add_to_log() { sed -n "/^\[$top_ver\]: https:[^ ]*$/,\$p" $log_file } +bump_version() { + [ ! "$bump_script" ] && return + [ ! -f "$bump_script" ] && echo "script not found: $bump_script" >&2 && return 1 + + top_tag=$(top_log_tag) + [ -x "$bump_script" ] && + echo "executing $bump_script $top_tag" && "$bump_script" "$top_tag" + [ ! -x "$bump_script" ] && + echo "executing sh $bump_script $top_tag" && sh "$bump_script" "$top_tag" +} + +startup_checks() { + [ ! -f $log_file ] && echo "couldn't find $log_file" >&2 && return 1 + [ ! -d .git ] && echo "the current directory doesn't contain .git" >&2 && return 1 + remote_url >/dev/null && return + echo "cannot find remote url for this repo" >&2 && return 1 +} + #### Commands #### tag_command() { @@ -329,7 +318,6 @@ auth_command() { echo "$token" | base64 >"$auth_file" && echo "git auth saved" } -# TODO: test post_command() { startup_checks || return 1 [ ! -e "$auth_file" ] && echo "you need to save a token with the auth" >&2 && return 1 @@ -343,19 +331,15 @@ post_command() { entry=$(sed -n "/^## \[$top_ver\] - [1-9][0-9-]*$/,/^## \[..*\] - [1-9][0-9-]*$/p" $log_file | sed '$d') - # body=$(printf '%s\n\n%s\n' "$entry" "$link") body=$(printf '%s\n\n%s\n' "$entry" "$link" | - sed -E -e "s,([\"\\]),\\\\\1,g" -e "s,$,\\\\," | tr '\n' 'n') + sed -E -e "s,([\"\\]),\\\\\1,g" -e "s,$,\\\\n," | tr -d '\n') [ "$dry_run" ] && printf "%s\n%s\n%s\n" "$api_url" "$top_tag" "$body" && return - # data=$(printf '{"tag_name": "%s", "name": "%s", "body": "%s"}' "$top_tag" "$top_tag" "$body") - - curl -X POST "$api_url/releases" \ + curl -L -X POST "$api_url/releases" \ -H "Authorization: token $auth_token" \ - -H "Content-Type: application/json" \ + -H "Accept: application/vnd.github+json" \ -d "{\"tag_name\": \"$top_tag\", \"name\": \"$top_tag\", \"body\": \"$body\" }" - # --data-urlencode "=$data" } init_command() { @@ -390,17 +374,22 @@ update_command() { bump_version } -# TODO: add prompt +# TODO: test all_command() { backup=$(cat $log_file) update_command || return 1 - last_changed=$(stat -qf '%Sc' $log_file) + old_last_changed=$(stat -qf '%Sc' $log_file) ${EDITOR:-vi} $log_file - [ "$(stat -qf '%Sc' $log_file)" = "$last_changed" ] && - echo "cancelling because no manual edits were made to $log_file" >&2 && + new_last_changed=$(stat -qf '%Sc' $log_file) + + [ "$old_last_changed" = "$new_last_changed" ] && + echo "no manual edits were made to $log_file" >&2 && + printf "do you want cancel? (Y/n) " >&2 && + read -r response && + [ "$response" != 'n' ] && echo "$backup" >$log_file && - return 1 + return bump_version push_tag='true' From b8f89848488f870b6606f1b01e158d1bc8874dac Mon Sep 17 00:00:00 2001 From: Adam Abrams Date: Fri, 30 Aug 2024 21:05:59 -0500 Subject: [PATCH 10/12] update tests --- tests/change-err-changelog/setup/exp-stderr | 2 +- tests/change-err-repo/setup/exp-stderr | 2 +- tests/init-err-repo/setup/exp-stderr | 2 +- tests/init-err-tag/setup/exp-stderr | 2 +- tests/init-err-url/setup/exp-stderr | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/change-err-changelog/setup/exp-stderr b/tests/change-err-changelog/setup/exp-stderr index f9bf4c6..64768e8 100644 --- a/tests/change-err-changelog/setup/exp-stderr +++ b/tests/change-err-changelog/setup/exp-stderr @@ -1 +1 @@ -couldn't find CHANGELOG.md +could not find CHANGELOG.md diff --git a/tests/change-err-repo/setup/exp-stderr b/tests/change-err-repo/setup/exp-stderr index 1facbeb..b4a8be9 100644 --- a/tests/change-err-repo/setup/exp-stderr +++ b/tests/change-err-repo/setup/exp-stderr @@ -1 +1 @@ -the current directory doesn't contain .git +the current directory does not contain .git diff --git a/tests/init-err-repo/setup/exp-stderr b/tests/init-err-repo/setup/exp-stderr index 1facbeb..b4a8be9 100644 --- a/tests/init-err-repo/setup/exp-stderr +++ b/tests/init-err-repo/setup/exp-stderr @@ -1 +1 @@ -the current directory doesn't contain .git +the current directory does not contain .git diff --git a/tests/init-err-tag/setup/exp-stderr b/tests/init-err-tag/setup/exp-stderr index d3ffd7f..65ce804 100644 --- a/tests/init-err-tag/setup/exp-stderr +++ b/tests/init-err-tag/setup/exp-stderr @@ -1 +1 @@ -couldn't find any valid version tags +could not find any valid version tags diff --git a/tests/init-err-url/setup/exp-stderr b/tests/init-err-url/setup/exp-stderr index 463175c..41a8830 100644 --- a/tests/init-err-url/setup/exp-stderr +++ b/tests/init-err-url/setup/exp-stderr @@ -1 +1 @@ -remote url isn't set for this repo +remote url is not set for this repo From 1997afd2b15a217c429a59d414725d484e1b332d Mon Sep 17 00:00:00 2001 From: Adam Abrams Date: Fri, 30 Aug 2024 21:06:14 -0500 Subject: [PATCH 11/12] make style consistent --- change | 130 +++++++++++++++++++++++++++------------------------------ 1 file changed, 62 insertions(+), 68 deletions(-) diff --git a/change b/change index af159a1..3e3e6aa 100755 --- a/change +++ b/change @@ -29,9 +29,9 @@ change post [--dry-run] optional: --dry-run prints url, version, and body without sending change all [--bump PATH] - runs change, then opens $log_file in \$EDITOR + runs 'change' then opens $log_file in \$EDITOR commits and pushes the $log_file - runs change tag -p and then runs change post + runs 'change tag -p' then 'change post' optional: --bump passes the newest version as an arg to a script at PATH change version @@ -79,11 +79,6 @@ latest_inc_patch() { echo "$latest" | semver_select "\1\2.\3.$((patch + 1))" } -trim_tag() { - tag=$1 - echo "$tag" | semver_select '\2.\3.\4' -} - rm_tag_prefix() { tag=$1 echo "${tag#v}" @@ -114,7 +109,7 @@ unlogged_tags() { semver_git_tags | sed -n "/^$top_tag$/q;p" | sort -V latest_tag=$(semver_git_tags | head -1) - unlogged_commits=$(git log --pretty=format:"%B" "$latest_tag..") + unlogged_commits=$(git log --pretty=format:'%B' "$latest_tag..") [ ! "$unlogged_commits" ] && return calc_new_tag "$unlogged_commits" } @@ -135,60 +130,60 @@ remote_url_clean() { #### Commits #### grep_commits_major() { - flags="-e" # use '-v' for inverse or '-q' for quiet + flags='-e' # use '-v' for inverse or '-q' for quiet [ "$1" ] && flags="${1}e" - grep "$flags" "^[A-Za-z]*!: " \ - -e "^[A-Za-z]*(.*)!: " \ - -e "^[*-] *[A-Za-z]*!: " \ - -e "^[*-] *[A-Za-z]*(.*)!: " \ - -e "^BREAKING[ -]CHANGE: " \ - -e "^[*-] *BREAKING[ -]CHANGE: " \ - -e "^BREAKING[ -]CHANGE" + grep "$flags" '^[A-Za-z]*!: ' \ + -e '^[A-Za-z]*(.*)!: ' \ + -e '^[*-] *[A-Za-z]*!: ' \ + -e '^[*-] *[A-Za-z]*(.*)!: ' \ + -e '^BREAKING[ -]CHANGE: ' \ + -e '^[*-] *BREAKING[ -]CHANGE: ' \ + -e '^BREAKING[ -]CHANGE' } grep_commits_minor() { - flags="-e" # use '-v' for inverse or '-q' for quiet + flags='-e' # use '-v' for inverse or '-q' for quiet [ "$1" ] && flags="${1}e" - grep -i "$flags" "^feat: " \ - -e "^feat(.*): " \ - -e "^[*-] *feat: " \ - -e "^[*-] *feat(.*): " + grep -i "$flags" '^feat: ' \ + -e '^feat(.*): ' \ + -e '^[*-] *feat: ' \ + -e '^[*-] *feat(.*): ' } grep_commits_patch() { - flags="-e" # use '-v' for inverse or '-q' for quiet + flags='-e' # use '-v' for inverse or '-q' for quiet [ "$1" ] && flags="${1}e" - grep -i "$flags" "^fix: " \ - -e "^fix(.*): " \ - -e "^[*-] *fix: " \ - -e "^[*-] *fix(.*): " + grep -i "$flags" '^fix: ' \ + -e '^fix(.*): ' \ + -e '^[*-] *fix: ' \ + -e '^[*-] *fix(.*): ' } grep_conv_commits() { - grep -e "^[A-Za-z]*: " -e "^[A-Za-z]*(.*): " \ - -e "^[*-] *[A-Za-z]*: " -e "^[*-] *[A-Za-z]*(.*): " \ - -e "^[A-Za-z]*!: " -e "^[A-Za-z]*(.*)!: " \ - -e "^[*-] *[A-Za-z]*!: " -e "^[*-] *[A-Za-z]*(.*)!: " \ - -e "^BREAKING[ -]CHANGE: " -e "^[*-] *BREAKING[ -]CHANGE: " \ - -e "^BREAKING[ -]CHANGE" + grep -e '^[A-Za-z]*: ' -e '^[A-Za-z]*(.*): ' \ + -e '^[*-] *[A-Za-z]*: ' -e '^[*-] *[A-Za-z]*(.*): ' \ + -e '^[A-Za-z]*!: ' -e '^[A-Za-z]*(.*)!: ' \ + -e '^[*-] *[A-Za-z]*!: ' -e '^[*-] *[A-Za-z]*(.*)!: ' \ + -e '^BREAKING[ -]CHANGE: ' -e '^[*-] *BREAKING[ -]CHANGE: ' \ + -e '^BREAKING[ -]CHANGE' } fmt_conv_type() { sed -E \ - -e "s,^[A-Za-z]*: (.*$),\1,g" \ - -e "s,^[A-Za-z]*\((.*)\): (.*$),\2 for \1,g" \ - -e "s,^[*-] *[A-Za-z]*: (.*$),\1,g" \ - -e "s,^[*-] *[A-Za-z]*\((.*)\): (.*$),\2 for \1,g" \ - -e "s,^[A-Za-z]*!: (.*$),\1,g" \ - -e "s,^[A-Za-z]*\((.*)\)!: (.*$),\2 for \1,g" \ - -e "s,^[*-] *[A-Za-z]*!: (.*$),\1,g" \ - -e "s,^[*-] *[A-Za-z]*\((.*)\)!: (.*$),\2 for \1,g" \ - -e "s,^BREAKING[ -]CHANGE: (.*$),\1,g" \ - -e "s,^[*-] *BREAKING[ -]CHANGE: (.*$),\1,g" \ - -e "s,^BREAKING[ -]CHANGE,a breaking change,g" + -e 's,^[A-Za-z]*: (.*$),\1,g' \ + -e 's,^[A-Za-z]*\((.*)\): (.*$),\2 for \1,g' \ + -e 's,^[*-] *[A-Za-z]*: (.*$),\1,g' \ + -e 's,^[*-] *[A-Za-z]*\((.*)\): (.*$),\2 for \1,g' \ + -e 's,^[A-Za-z]*!: (.*$),\1,g' \ + -e 's,^[A-Za-z]*\((.*)\)!: (.*$),\2 for \1,g' \ + -e 's,^[*-] *[A-Za-z]*!: (.*$),\1,g' \ + -e 's,^[*-] *[A-Za-z]*\((.*)\)!: (.*$),\2 for \1,g' \ + -e 's,^BREAKING[ -]CHANGE: (.*$),\1,g' \ + -e 's,^[*-] *BREAKING[ -]CHANGE: (.*$),\1,g' \ + -e 's,^BREAKING[ -]CHANGE,a breaking change,g' } fmt_commits() { @@ -213,8 +208,8 @@ next_commit_range() { format_commits() { commit_range=$1 - sub=$(git log --pretty=format:"%s" "$commit_range") - bod=$(git log --pretty=format:"%b" "$commit_range" | grep_conv_commits) + sub=$(git log --pretty=format:'%s' "$commit_range") + bod=$(git log --pretty=format:'%b' "$commit_range" | grep_conv_commits) list=$(printf '%s\n%s' "$sub" "$bod") dep=$(echo "$list" | grep_commits_major) @@ -226,10 +221,10 @@ format_commits() { fix=$(echo "$list" | grep_commits_patch) list=$(echo "$list" | grep_commits_patch -v) - [ "$dep" ] && echo "### BREAKING CHANGE" && echo "$dep" | fmt_conv_type | fmt_commits - [ "$feat" ] && echo "### Added" && echo "$feat" | fmt_conv_type | fmt_commits - [ "$fix" ] && echo "### Fixed" && echo "$fix" | fmt_conv_type | fmt_commits - [ "$list" ] && echo "### Changed" && echo "$list" | fmt_conv_type | fmt_commits + [ "$dep" ] && echo '### BREAKING CHANGE' && echo "$dep" | fmt_conv_type | fmt_commits + [ "$feat" ] && echo '### Added' && echo "$feat" | fmt_conv_type | fmt_commits + [ "$fix" ] && echo '### Fixed' && echo "$fix" | fmt_conv_type | fmt_commits + [ "$list" ] && echo '### Changed' && echo "$list" | fmt_conv_type | fmt_commits } #### Utils #### @@ -288,10 +283,10 @@ bump_version() { } startup_checks() { - [ ! -f $log_file ] && echo "couldn't find $log_file" >&2 && return 1 - [ ! -d .git ] && echo "the current directory doesn't contain .git" >&2 && return 1 + [ ! -f $log_file ] && echo "could not find $log_file" >&2 && return 1 + [ ! -d .git ] && echo 'the current directory does not contain .git' >&2 && return 1 remote_url >/dev/null && return - echo "cannot find remote url for this repo" >&2 && return 1 + echo 'cannot find remote url for this repo' >&2 && return 1 } #### Commands #### @@ -307,50 +302,50 @@ tag_command() { [ "$push_tag" ] && git push --quiet "$repo_url" "$top_tag" && echo "pushed $top_tag to $repo_url" && return - printf "to push the latest tag use:\n\tgit push %s %s\n" "$repo_url" "$top_tag" - printf "to push all local tags use:\n\tgit push %s --tags\n" "$repo_url" + printf 'to push the latest tag use:\n\tgit push %s %s\n' "$repo_url" "$top_tag" + printf 'to push all local tags use:\n\tgit push %s --tags\n' "$repo_url" } auth_command() { - [ ! "$token" ] && echo "enter a personal access token" && printf "token: " && read -r token - [ ! "$token" ] && echo "invalid token" >&2 && return 1 + [ ! "$token" ] && echo 'enter a personal access token' && printf 'token: ' && read -r token + [ ! "$token" ] && echo 'invalid token' >&2 && return 1 [ ! -d "$auth_dir" ] && mkdir -p "$auth_dir" - echo "$token" | base64 >"$auth_file" && echo "git auth saved" + echo "$token" | base64 >"$auth_file" && echo 'git auth saved' } post_command() { startup_checks || return 1 - [ ! -e "$auth_file" ] && echo "you need to save a token with the auth" >&2 && return 1 + [ ! -e "$auth_file" ] && echo 'you need to save a token with the auth command' >&2 && return 1 auth_token=$(<"$auth_file" base64 --decode) top_tag=$(top_log_tag) top_ver=$(rm_tag_prefix "$top_tag") - api_url=$(remote_url_clean | sed -E "s|^(.*)(github.com)|\1api.\2/repos|") + api_url=$(remote_url_clean | sed -E 's|^(.*)(github.com)|\1api.\2/repos|') link=$(grep "^\[$top_ver\]: https:[^ ]*$" $log_file) entry=$(sed -n "/^## \[$top_ver\] - [1-9][0-9-]*$/,/^## \[..*\] - [1-9][0-9-]*$/p" $log_file | sed '$d') body=$(printf '%s\n\n%s\n' "$entry" "$link" | - sed -E -e "s,([\"\\]),\\\\\1,g" -e "s,$,\\\\n," | tr -d '\n') + sed -E -e 's,(["\]),\\\1,g' -e 's,$,\\n,' | tr -d '\n') - [ "$dry_run" ] && printf "%s\n%s\n%s\n" "$api_url" "$top_tag" "$body" && return + [ "$dry_run" ] && printf '%s\n%s\n%s\n' "$api_url" "$top_tag" "$body" && return curl -L -X POST "$api_url/releases" \ -H "Authorization: token $auth_token" \ - -H "Accept: application/vnd.github+json" \ + -H 'Accept: application/vnd.github+json' \ -d "{\"tag_name\": \"$top_tag\", \"name\": \"$top_tag\", \"body\": \"$body\" }" } init_command() { - [ ! -d .git ] && echo "the current directory doesn't contain .git" >&2 && return 1 + [ ! -d .git ] && echo 'the current directory does not contain .git' >&2 && return 1 [ -e $log_file ] && echo "$log_file already exists" >&2 && return 1 oldest_tag=$(semver_git_tags | tail -n 1) - [ ! "$oldest_tag" ] && echo "couldn't find any valid version tags" >&2 && return 2 + [ ! "$oldest_tag" ] && echo 'could not find any valid version tags' >&2 && return 2 repo_url=$(remote_url_clean) - [ ! "$repo_url" ] && echo "remote url isn't set for this repo" >&2 && return 1 + [ ! "$repo_url" ] && echo 'remote url is not set for this repo' >&2 && return 1 initial_log "$oldest_tag" >$log_file @@ -361,7 +356,7 @@ init_command() { update_command() { startup_checks || return 1 tags=$(unlogged_tags) - [ ! "$tags" ] && echo "no new versions to add" && return 1 + [ ! "$tags" ] && echo 'no new versions to add' && return 1 echo "$tags" | while read -r next_tag; do new_log=$(add_to_log "$next_tag") @@ -374,7 +369,6 @@ update_command() { bump_version } -# TODO: test all_command() { backup=$(cat $log_file) update_command || return 1 @@ -385,7 +379,7 @@ all_command() { [ "$old_last_changed" = "$new_last_changed" ] && echo "no manual edits were made to $log_file" >&2 && - printf "do you want cancel? (Y/n) " >&2 && + printf 'do you want cancel? (Y/n) ' >&2 && read -r response && [ "$response" != 'n' ] && echo "$backup" >$log_file && From 67020ca72e7b3c63a7a7f71c06e6615db84176cd Mon Sep 17 00:00:00 2001 From: Adam Abrams Date: Fri, 30 Aug 2024 21:13:29 -0500 Subject: [PATCH 12/12] add bar to symbols test --- tests/change-symbols/setup/CHANGELOG-template.md | 2 +- tests/change-symbols/setup/run-test | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/change-symbols/setup/CHANGELOG-template.md b/tests/change-symbols/setup/CHANGELOG-template.md index 0496f8b..736144c 100644 --- a/tests/change-symbols/setup/CHANGELOG-template.md +++ b/tests/change-symbols/setup/CHANGELOG-template.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Asterisk * escaped asterisk \*. - Exclamation ! four backslashes \. - 'apostrophe pair' `escaped tick pair`. -- Tilda ~ escaped tick ` ampersand &. +- Tilda ~ escaped tick ` ampersand & bar | end. ### Changed - [file link](./run-test). diff --git a/tests/change-symbols/setup/run-test b/tests/change-symbols/setup/run-test index 3b586f6..b9117d3 100755 --- a/tests/change-symbols/setup/run-test +++ b/tests/change-symbols/setup/run-test @@ -9,7 +9,7 @@ git commit --quiet -am "fix: add second file" git tag 0.1.0 git add exp-stderr -git commit --quiet -am "fix: tilda ~ escaped tick \` ampersand &" +git commit --quiet -am "fix: tilda ~ escaped tick \` ampersand & bar | end" git rm --quiet --cached exp-stderr git commit --quiet -am "chore: escaped apostrophe \' escaped quote \"" @@ -38,7 +38,6 @@ git commit --quiet -am "fix: (paren pair) {curly pair} [square pair]" git rm --quiet --cached exp-stderr git commit --quiet -am "chore: [file link](./run-test)" - git remote add origin https://github.com/adamtabrams/change.git change >act-stdout 2>act-stderr