Skip to content

Instantly share code, notes, and snippets.

@tueda
Last active June 13, 2026 09:02
Show Gist options
  • Select an option

  • Save tueda/20aec9c93e30dcc0c122305967902c8b to your computer and use it in GitHub Desktop.

Select an option

Save tueda/20aec9c93e30dcc0c122305967902c8b to your computer and use it in GitHub Desktop.
{
"formatter": {
"useEditorconfig": true
}
}
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{json,sh}]
indent_size = 2
indent_style = space
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
[.pre-commit-config.yaml]
indent_size = 4
indent_style = space
[general]
ignore=body-is-missing
contrib=contrib-title-conventional-commits
[title-max-length]
line-length=80
[body-max-line-length]
line-length=80
[ignore-by-title]
regex=^Merge
[ignore-by-author-name]
regex=dependabot
[contrib-title-conventional-commits]
types = build,chore,ci,docs,feat,fix,perf,refactor,revert,style,test
default_install_hook_types: [pre-commit, commit-msg]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-added-large-files
- id: check-case-conflict
- id: check-illegal-windows-names
- id: check-json
- id: check-merge-conflict
- id: check-symlinks
- id: check-vcs-permalinks
- id: check-yaml
args: [--allow-multiple-documents]
- id: destroyed-symlinks
- id: detect-private-key
- id: end-of-file-fixer
- id: fix-byte-order-marker
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://github.com/scop/pre-commit-shfmt
rev: v3.13.1-1
hooks:
- id: shfmt
files: ^core.make-release\.sh$
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.11.0.1
hooks:
- id: shellcheck
args: [-x]
files: ^core.make-release\.sh$
- repo: https://github.com/biomejs/pre-commit
rev: v2.4.15
hooks:
- id: biome-format
types: [json]
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
rev: 3.6.1
hooks:
- id: editorconfig-checker
args: [-disable-indent-size]
files: ^core.make-release\.sh$
- repo: https://github.com/streetsidesoftware/cspell-cli
rev: v10.0.0
hooks:
- id: cspell
files: ^core.make-release\.sh$
- id: cspell
name: check commit message spelling
args:
- --no-must-find-files
- --no-progress
- --no-summary
- --files
- .git/COMMIT_EDITMSG
stages: [commit-msg]
- repo: https://github.com/jorisroovers/gitlint
rev: v0.19.1
hooks:
- id: gitlint
#!/bin/bash
#
# Create a new release.
#
# Usage:
# make-release.sh
# make-release.sh NEW-VERSION
# make-release.sh NEW-VERSION NEW-DEV-VERSION
#
# Project-independent script core version: 2026.05.23
#
set -euo pipefail
### Project-specific configuration ###
# Tag prefix.
v='' # '' or 'v'
# pre_version_message <current_version_number> <version_number> <dev_version_number>:
# a hook function that prints a message before updating the version.
pre_version_message() {
:
}
# get_current_version: prints the current version.
get_current_version() {
false
}
# get_next_version <current_version_number>: prints the next version.
get_next_version() {
false
}
# get_next_dev_version <current_version_number> <next_version_number>:
# prints the next development version.
get_next_dev_version() {
false
}
# version_bump <version_number>: sets the release version.
version_bump() {
dev_version_bump "$1"
}
# dev_version_bump <dev_version_number>: sets the development version.
dev_version_bump() {
false
}
# release_commit_message <version_number>: generates the release commit message.
release_commit_message() {
echo "chore(release): $1"
}
# dev_commit_message <dev_version_number>: generates the development-version commit message.
dev_commit_message() {
echo "chore(version): set version to $1"
}
### Project-independent logic ###
# spell-checker: ignore numstat, toplevel
# Trap ERR to print a stack trace when a command fails.
# See: https://gist.github.com/ahendrix/7030300
_errexit() {
local err=$?
set +o xtrace
local code="${1:-1}"
echo "Error in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}: '${BASH_COMMAND}' exited with status $err" >&2
# Print a stack trace from $FUNCNAME.
if [ ${#FUNCNAME[@]} -gt 2 ]; then
echo 'Traceback:' >&2
for ((i = 1; i < ${#FUNCNAME[@]} - 1; i++)); do
echo " [$i]: at ${BASH_SOURCE[$i + 1]}:${BASH_LINENO[$i]} in function ${FUNCNAME[$i]}" >&2
done
fi
echo "Exiting with status ${code}" >&2
exit "${code}"
}
trap '_errexit' ERR
set -o errtrace
# abort <message>: aborts the program with the given message.
abort() {
echo "error: $*" 1>&2
exit 1
}
# is_clean: checks if the working tree is clean (untracked files are ignored).
is_clean() {
git diff --quiet && git diff --cached --quiet
}
# sed_i: portable implementation of sed -i for GNU and BSD.
# Usage: sed_i <options...> <script> <file>
# Note: works only with a single file.
sed_i() {
local file="${!#}"
local temp="$file.$$.$RANDOM"
if sed "$@" >"$temp"; then
mv "$temp" "$file"
else
rm -f "$temp"
return 1
fi
}
# check_file_changed <file> <expected_added_lines_count> <expected_deleted_lines_count>
check_file_changed() {
local stat added_lines_count deleted_lines_count
stat=$(git diff --numstat "$1")
if [[ $stat =~ ([0-9]+)[[:blank:]]+([0-9]+) ]]; then
added_lines_count="${BASH_REMATCH[1]}"
deleted_lines_count="${BASH_REMATCH[2]}"
else
added_lines_count=0
deleted_lines_count=0
fi
if [[ $added_lines_count != "$2" || $deleted_lines_count != "$3" ]]; then
abort "$1 changed unexpectedly: $added_lines_count added, $deleted_lines_count deleted (expected: $2 added, $3 deleted)"
fi
}
# Ensure that the git command is available.
command -v git >/dev/null || abort 'git not available'
# Abort if the working tree is dirty.
is_clean || abort 'working tree is dirty'
# Ensure that we are in the project root.
cd "$(git rev-parse --show-toplevel)"
# Determine the current version.
current_version=$(get_current_version)
[[ -n $current_version ]] || abort 'could not determine current version'
# Determine the next version.
if [[ $# == 0 ]]; then
next_version=$(get_next_version "$current_version")
[[ -n $next_version ]] || abort 'could not determine next version'
else
next_version=$1
fi
# Determine the next development version.
if [[ $# -lt 2 ]]; then
next_dev_version=$(get_next_dev_version "$current_version" "$next_version")
[[ -n $next_dev_version ]] || abort 'could not determine next development version'
else
next_dev_version=$2
fi
# Print the versions.
pre_version_message "$current_version" "$next_version" "$next_dev_version"
echo 'This script will create a release and bump the development version.'
echo " current commit : $(git rev-parse --short HEAD)"
echo " current version : $current_version"
echo " next version : $next_version"
echo " next development version : $next_dev_version"
# Abort if the next version tag already exists.
if git rev-parse -q --verify "refs/tags/$v$next_version" >/dev/null; then
abort "tag already exists: $v$next_version"
fi
# User confirmation.
while :; do
read -r -p 'Proceed? (y/N): ' yn
case "$yn" in
[yY]*)
break
;;
[nN]*)
echo 'Aborted' >&2
exit 1
;;
*)
;;
esac
done
# Create the release and bump the development version.
version_bump "$next_version"
git commit -a -m "$(release_commit_message "$next_version")"
git tag "$v$next_version"
dev_version_bump "$next_dev_version"
git commit -a -m "$(dev_commit_message "$next_dev_version")"
# Print a completion summary.
echo "Release tag $v$next_version was successfully created."
echo "The current development version is now $next_dev_version."
echo
echo "To push the release tag to origin:"
echo " git push origin $v$next_version"
#!/bin/bash
#
# Generate release notes from a changelog.
#
# Usage:
# generate-release-notes.sh [-v] [CHANGELOG_FILE]
#
# Options:
# -v Print the parsed release version instead of the release notes.
#
# Requirements:
# - parse-changelog: https://github.com/taiki-e/parse-changelog
# - jq: https://github.com/jqlang/jq (required only with -v)
#
set -euo pipefail
abort() {
echo "error: $*" 1>&2
exit 1
}
changelog_file=CHANGELOG.md
print_version=false
args=()
while [[ $# -gt 0 ]]; do
case "$1" in
-v)
print_version=:
shift
;;
*)
args+=("$1")
shift
;;
esac
done
if [[ ${#args[@]} -gt 0 ]]; then
# Only the first positional argument is used for now.
changelog_file=${args[0]}
fi
parse() {
command -v parse-changelog &>/dev/null || abort 'parse-changelog was not found'
if parse-changelog "$@" &>/dev/null; then
parse-changelog "$@"
elif parse-changelog "$@" Unreleased &>/dev/null; then
parse-changelog "$@" Unreleased
else
abort "failed to parse the changelog using parse-changelog $*"
fi
}
rstrip_newlines() {
# See: https://unix.stackexchange.com/a/666549
awk '
NF {
print s $0
s = ""
next
}
{
s = s ORS
}
'
}
if $print_version; then
command -v jq &>/dev/null || abort 'jq was not found'
parse --json "$changelog_file" | jq -r 'to_entries[0].value.version'
else
parse "$changelog_file" |
sed '/^<a name="[^"]*"><\/a>$/d' |
sed '/^\[[^]]*]: https:\/\/github\.com\/.*$/d' |
sed '/^<!--.*-->$/d' |
rstrip_newlines
fi
update:
wget -O makefile4latex.make-release.sh https://raw.githubusercontent.com/tueda/makefile4latex/refs/heads/master/scripts/make-release.sh
wget -O pierre.make-release.sh https://raw.githubusercontent.com/tueda/pierre/refs/heads/main/scripts/make-release.sh
wget -O polybench.make-release.sh https://raw.githubusercontent.com/tueda/polybench/refs/heads/main/scripts/make-release.sh
#!/bin/bash
#
# Make a release.
#
# Usage:
# make-release.sh
# make-release.sh NEW-VERSION
# make-release.sh NEW-VERSION NEW-DEV-VERSION
#
# Script Core Version: 2025.12.03
#
set -euo pipefail
### Project-specific configuration ###
# Tag prefix.
v='v'
# pre_version_message <current_version_number> <version_number> <dev_version_number>:
# a hook function to print some message before bumping the version number.
pre_version_message() {
echo 'Please make sure that CHANGELOG.md is up-to-date.'
echo 'You can use the output of the following command:'
echo
echo " git-chglog --next-tag $v$2"
echo
}
# get_current_version: prints the current version.
get_current_version() {
# Extract the current version number from the Makefile.
local main_file=Makefile
[[ -f $main_file ]] || abort "$main_file not found"
grep MAKEFILE4LATEX_VERSION $main_file | head -1 | sed 's/.*= *//' || :
}
# get_next_version <current_version_number>: prints the next version.
get_next_version() {
# Remove the "-dev" suffix from the current version number.
[[ $1 == *-dev ]] || abort "current version doesn't end with -dev: $1"
echo "${1%-dev}"
}
# get_next_dev_version <current_version_number> <next_version_number>: prints the next dev-version.
get_next_dev_version() {
# Increase the patch number and add the "-dev" suffix.
local next_version_xyz=${2%-*} # remove any suffix
local a
IFS=. read -r -a a <<<"$next_version_xyz"
[[ ${#a[@]} == 3 ]] || abort "next version should be semantic: $2"
((a[2]++)) || :
echo "${a[0]}.${a[1]}.${a[2]}-dev"
}
# version_bump <version_number>: a hook function to bump the version for documents.
version_bump() {
dev_version_bump "$1"
sed_i 's|makefile4latex/v[^/]*/Makefile|makefile4latex/v'"$1"'/Makefile|' README.md
check_file_changed README.md 2 2
}
# dev_version_bump <dev_version_number>: a hook function to bump the version for code.
dev_version_bump() {
sed_i "s/^MAKEFILE4LATEX_VERSION *= .*$/MAKEFILE4LATEX_VERSION = $1/" Makefile
check_file_changed Makefile 1 1
}
# release_commit_message <version_number>: generates the commit message for a release version.
release_commit_message() {
echo "chore(release): bump version to $1"
}
# dev_commit_message <version_number>: generates the commit message for a development version.
dev_commit_message() {
echo "chore: bump version to $1"
}
### Project-independent logic ###
# Trap ERR to print the stack trace when a command fails.
# See: https://gist.github.com/ahendrix/7030300
_errexit() {
local err=$?
set +o xtrace
local code="${1:-1}"
echo "Error in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}: '${BASH_COMMAND}' exited with status $err" >&2
# Print out the stack trace described by $FUNCNAME
if [ ${#FUNCNAME[@]} -gt 2 ]; then
echo 'Traceback:' >&2
for ((i=1;i<${#FUNCNAME[@]}-1;i++)); do
echo " [$i]: at ${BASH_SOURCE[$i+1]}:${BASH_LINENO[$i]} in function ${FUNCNAME[$i]}" >&2
done
fi
echo "Exiting with status ${code}" >&2
exit "${code}"
}
trap '_errexit' ERR
set -o errtrace
# abort <message>: aborts the program with the given message.
abort() {
echo "error: $*" 1>&2
exit 1
}
# is_clean: checks if the working repository is clean (untracked files are ignored).
is_clean() {
git diff --quiet && git diff --cached --quiet
}
# sed_i: portable sed -i for GNU/BSD.
# Usage: sed_i <options...> <script> <file>
# Note: works only with a single file.
sed_i() {
local file="${!#}"
local temp="$file.$$.$RANDOM"
if sed "$@" >"$temp"; then
mv "$temp" "$file"
else
rm -f "$temp"
return 1
fi
}
# check_file_changed <file> <expected_added_lines_count> <expected_deleted_lines_count>
check_file_changed() {
local stat added_lines_count deleted_lines_count
stat=$(git diff --numstat "$1")
if [[ $stat =~ ([0-9]+)[[:blank:]]+([0-9]+) ]]; then
added_lines_count="${BASH_REMATCH[1]}"
deleted_lines_count="${BASH_REMATCH[2]}"
else
added_lines_count=0
deleted_lines_count=0
fi
if [[ $added_lines_count != "$2" || $deleted_lines_count != "$3" ]]; then
abort "$1 changed unexpectedly: $added_lines_count added, $deleted_lines_count deleted (expected: $2 added, $3 deleted)"
fi
}
# Require the git command.
command -v git >/dev/null || abort 'git not available'
# Stop if the working directory is dirty.
is_clean || abort 'working directory is dirty'
# Ensure that we are in the project root.
cd "$(git rev-parse --show-toplevel)"
# Determine the current version.
current_version=$(get_current_version)
[[ -n $current_version ]] || abort 'current version not determined'
# Determine the next version.
if [[ $# == 0 ]]; then
next_version=$(get_next_version "$current_version")
[[ -n $next_version ]] || abort 'next version not determined'
else
next_version=$1
fi
# Determine the next dev-version.
if [[ $# -lt 2 ]]; then
next_dev_version=$(get_next_dev_version "$current_version" "$next_version")
[[ -n $next_dev_version ]] || abort 'next dev-version not determined'
else
next_dev_version=$2
fi
# Print the versions.
pre_version_message "$current_version" "$next_version" "$next_dev_version"
echo 'This script will bump the version number.'
echo " current commit : $(git rev-parse --short HEAD)"
echo " current version : $current_version"
echo " next version : $next_version"
echo " next dev-version : $next_dev_version"
# Abort if the next version tag already exists.
if git rev-parse -q --verify "refs/tags/$v$next_version" >/dev/null; then
abort "tag already exists: $v$next_version"
fi
# User confirmation.
while :; do
read -r -p 'ok? (y/N): ' yn
case "$yn" in
[yY]*)
break
;;
[nN]*)
echo 'Aborted' >&2
exit 1
;;
*)
;;
esac
done
# Bump the version.
version_bump "$next_version"
git commit -a -m "$(release_commit_message "$next_version")"
git tag "$v$next_version"
dev_version_bump "$next_dev_version"
git commit -a -m "$(dev_commit_message "$next_dev_version")"
# Completed. Print summary.
echo "A release tag $v$next_version was successfully created."
echo "The current development version is now $next_dev_version"
echo
echo "To push it to the origin:"
echo " git push origin $v$next_version"
#!/bin/bash
#
# Create a new release.
#
# Usage:
# make-release.sh
# make-release.sh NEW-VERSION
# make-release.sh NEW-VERSION NEW-DEV-VERSION
#
# Project-independent script core version: 2026.05.23
#
set -euo pipefail
### Project-specific configuration ###
# Tag prefix.
v='v'
# pre_version_message <current_version_number> <version_number> <dev_version_number>:
# a hook function that prints a message before updating the version.
pre_version_message() {
echo 'Please make sure that CHANGELOG.md is up to date.'
echo 'You can use the output of the following command to update it:'
echo
echo " git cliff --unreleased --tag $v$2"
echo
}
# get_current_version: prints the current version.
get_current_version() {
grep PROJECT_FULL_VERSION CMakeLists.txt | head -1 | sed 's/[^"]*"//' | sed 's/".*//' || :
}
# get_next_version <current_version_number>: prints the next version.
get_next_version() {
if command -v git-cliff >/dev/null 2>&1; then
git-cliff --bumped-version 2>/dev/null | sed 's/^v//'
else
[[ $1 == *-dev ]] || abort "current version doesn't end with -dev: $1"
echo "${1%-dev}"
fi
}
# get_next_dev_version <current_version_number> <next_version_number>:
# prints the next development version.
get_next_dev_version() {
local next_version_xyz=${2%%-*}
next_version_xyz=${next_version_xyz%%a*}
next_version_xyz=${next_version_xyz%%b*}
next_version_xyz=${next_version_xyz%%rc*}
next_version_xyz=${next_version_xyz%%.post*}
next_version_xyz=${next_version_xyz%%.dev*}
local a
IFS=. read -r -a a <<<"$next_version_xyz"
[[ ${#a[@]} == 3 ]] || abort "next version must be a semantic version: $2"
((a[2]++)) || :
echo "${a[0]}.${a[1]}.${a[2]}-dev"
}
# version_bump <version_number>: sets the release version.
version_bump() {
dev_version_bump "$1"
}
# dev_version_bump <dev_version_number>: sets the development version.
dev_version_bump() {
sed_i "s/^set(PROJECT_FULL_VERSION .*$/set(PROJECT_FULL_VERSION \"$1\")/" CMakeLists.txt
check_file_changed CMakeLists.txt 1 1
}
# release_commit_message <version_number>: generates the release commit message.
release_commit_message() {
echo "chore(release): $1"
}
# dev_commit_message <dev_version_number>: generates the development-version commit message.
dev_commit_message() {
echo "chore(version): set version to $1"
}
### Project-independent logic ###
# spell-checker: ignore numstat, toplevel
# Trap ERR to print a stack trace when a command fails.
# See: https://gist.github.com/ahendrix/7030300
_errexit() {
local err=$?
set +o xtrace
local code="${1:-1}"
echo "Error in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}: '${BASH_COMMAND}' exited with status $err" >&2
# Print a stack trace from $FUNCNAME.
if [ ${#FUNCNAME[@]} -gt 2 ]; then
echo 'Traceback:' >&2
for ((i = 1; i < ${#FUNCNAME[@]} - 1; i++)); do
echo " [$i]: at ${BASH_SOURCE[$i + 1]}:${BASH_LINENO[$i]} in function ${FUNCNAME[$i]}" >&2
done
fi
echo "Exiting with status ${code}" >&2
exit "${code}"
}
trap '_errexit' ERR
set -o errtrace
# abort <message>: aborts the program with the given message.
abort() {
echo "error: $*" 1>&2
exit 1
}
# is_clean: checks if the working tree is clean (untracked files are ignored).
is_clean() {
git diff --quiet && git diff --cached --quiet
}
# sed_i: portable implementation of sed -i for GNU and BSD.
# Usage: sed_i <options...> <script> <file>
# Note: works only with a single file.
sed_i() {
local file="${!#}"
local temp="$file.$$.$RANDOM"
if sed "$@" >"$temp"; then
mv "$temp" "$file"
else
rm -f "$temp"
return 1
fi
}
# check_file_changed <file> <expected_added_lines_count> <expected_deleted_lines_count>
check_file_changed() {
local stat added_lines_count deleted_lines_count
stat=$(git diff --numstat "$1")
if [[ $stat =~ ([0-9]+)[[:blank:]]+([0-9]+) ]]; then
added_lines_count="${BASH_REMATCH[1]}"
deleted_lines_count="${BASH_REMATCH[2]}"
else
added_lines_count=0
deleted_lines_count=0
fi
if [[ $added_lines_count != "$2" || $deleted_lines_count != "$3" ]]; then
abort "$1 changed unexpectedly: $added_lines_count added, $deleted_lines_count deleted (expected: $2 added, $3 deleted)"
fi
}
# Ensure that the git command is available.
command -v git >/dev/null || abort 'git not available'
# Abort if the working tree is dirty.
is_clean || abort 'working tree is dirty'
# Ensure that we are in the project root.
cd "$(git rev-parse --show-toplevel)"
# Determine the current version.
current_version=$(get_current_version)
[[ -n $current_version ]] || abort 'could not determine current version'
# Determine the next version.
if [[ $# == 0 ]]; then
next_version=$(get_next_version "$current_version")
[[ -n $next_version ]] || abort 'could not determine next version'
else
next_version=$1
fi
# Determine the next development version.
if [[ $# -lt 2 ]]; then
next_dev_version=$(get_next_dev_version "$current_version" "$next_version")
[[ -n $next_dev_version ]] || abort 'could not determine next development version'
else
next_dev_version=$2
fi
# Print the versions.
pre_version_message "$current_version" "$next_version" "$next_dev_version"
echo 'This script will create a release and bump the development version.'
echo " current commit : $(git rev-parse --short HEAD)"
echo " current version : $current_version"
echo " next version : $next_version"
echo " next development version : $next_dev_version"
# Abort if the next version tag already exists.
if git rev-parse -q --verify "refs/tags/$v$next_version" >/dev/null; then
abort "tag already exists: $v$next_version"
fi
# User confirmation.
while :; do
read -r -p 'Proceed? (y/N): ' yn
case "$yn" in
[yY]*)
break
;;
[nN]*)
echo 'Aborted' >&2
exit 1
;;
*)
;;
esac
done
# Create the release and bump the development version.
version_bump "$next_version"
git commit -a -m "$(release_commit_message "$next_version")"
git tag "$v$next_version"
dev_version_bump "$next_dev_version"
git commit -a -m "$(dev_commit_message "$next_dev_version")"
# Print a completion summary.
echo "Release tag $v$next_version was successfully created."
echo "The current development version is now $next_dev_version."
echo
echo "To push the release tag to origin:"
echo " git push origin $v$next_version"
#!/bin/bash
#
# Create a new release.
#
# Usage:
# make-release.sh
# make-release.sh NEW-VERSION
# make-release.sh NEW-VERSION NEW-DEV-VERSION
#
# Project-independent script core version: 2026.05.23
#
set -euo pipefail
### Project-specific configuration ###
# Tag prefix.
v=''
# pre_version_message <current_version_number> <version_number> <dev_version_number>:
# a hook function that prints a message before updating the version.
pre_version_message() {
echo 'Please make sure that CHANGELOG.md is up-to-date.'
echo 'You can use the output of the following command:'
echo
echo " git cliff --unreleased --tag $v$2"
echo
}
# get_current_version: prints the current version.
get_current_version() {
poetry version -s 2>/dev/null
}
# get_next_version <current_version_number>: prints the next version.
get_next_version() {
if command -v git-cliff >/dev/null 2>&1; then
git-cliff --bumped-version 2>/dev/null
else
local next_version
poetry version patch >/dev/null 2>/dev/null
next_version=$(poetry version -s 2>/dev/null)
git restore pyproject.toml
echo "$next_version"
fi
}
# get_next_dev_version <current_version_number> <next_version_number>:
# prints the next development version.
get_next_dev_version() {
local next_dev_version
poetry version "$2" >/dev/null 2>&1
poetry version prepatch >/dev/null 2>&1
next_dev_version=$(poetry version -s 2>/dev/null)
git restore pyproject.toml
echo "$next_dev_version"
}
# version_bump <version_number>: sets the release version.
version_bump() {
dev_version_bump "$1"
}
# dev_version_bump <dev_version_number>: sets the development version.
dev_version_bump() {
poetry version "$1" >/dev/null 2>&1
}
# release_commit_message <version_number>: generates the release commit message.
release_commit_message() {
echo "chore(release): $1"
}
# dev_commit_message <dev_version_number>: generates the development-version commit message.
dev_commit_message() {
echo "chore(version): set version to $1"
}
### Project-independent logic ###
# spell-checker: ignore numstat, toplevel
# Trap ERR to print a stack trace when a command fails.
# See: https://gist.github.com/ahendrix/7030300
_errexit() {
local err=$?
set +o xtrace
local code="${1:-1}"
echo "Error in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}: '${BASH_COMMAND}' exited with status $err" >&2
# Print a stack trace from $FUNCNAME.
if [ ${#FUNCNAME[@]} -gt 2 ]; then
echo 'Traceback:' >&2
for ((i = 1; i < ${#FUNCNAME[@]} - 1; i++)); do
echo " [$i]: at ${BASH_SOURCE[$i + 1]}:${BASH_LINENO[$i]} in function ${FUNCNAME[$i]}" >&2
done
fi
echo "Exiting with status ${code}" >&2
exit "${code}"
}
trap '_errexit' ERR
set -o errtrace
# abort <message>: aborts the program with the given message.
abort() {
echo "error: $*" 1>&2
exit 1
}
# is_clean: checks if the working tree is clean (untracked files are ignored).
is_clean() {
git diff --quiet && git diff --cached --quiet
}
# sed_i: portable implementation of sed -i for GNU and BSD.
# Usage: sed_i <options...> <script> <file>
# Note: works only with a single file.
sed_i() {
local file="${!#}"
local temp="$file.$$.$RANDOM"
if sed "$@" >"$temp"; then
mv "$temp" "$file"
else
rm -f "$temp"
return 1
fi
}
# check_file_changed <file> <expected_added_lines_count> <expected_deleted_lines_count>
check_file_changed() {
local stat added_lines_count deleted_lines_count
stat=$(git diff --numstat "$1")
if [[ $stat =~ ([0-9]+)[[:blank:]]+([0-9]+) ]]; then
added_lines_count="${BASH_REMATCH[1]}"
deleted_lines_count="${BASH_REMATCH[2]}"
else
added_lines_count=0
deleted_lines_count=0
fi
if [[ $added_lines_count != "$2" || $deleted_lines_count != "$3" ]]; then
abort "$1 changed unexpectedly: $added_lines_count added, $deleted_lines_count deleted (expected: $2 added, $3 deleted)"
fi
}
# Ensure that the git command is available.
command -v git >/dev/null || abort 'git not available'
# Abort if the working tree is dirty.
is_clean || abort 'working tree is dirty'
# Ensure that we are in the project root.
cd "$(git rev-parse --show-toplevel)"
# Determine the current version.
current_version=$(get_current_version)
[[ -n $current_version ]] || abort 'could not determine current version'
# Determine the next version.
if [[ $# == 0 ]]; then
next_version=$(get_next_version "$current_version")
[[ -n $next_version ]] || abort 'could not determine next version'
else
next_version=$1
fi
# Determine the next development version.
if [[ $# -lt 2 ]]; then
next_dev_version=$(get_next_dev_version "$current_version" "$next_version")
[[ -n $next_dev_version ]] || abort 'could not determine next development version'
else
next_dev_version=$2
fi
# Print the versions.
pre_version_message "$current_version" "$next_version" "$next_dev_version"
echo 'This script will create a release and bump the development version.'
echo " current commit : $(git rev-parse --short HEAD)"
echo " current version : $current_version"
echo " next version : $next_version"
echo " next development version : $next_dev_version"
# Abort if the next version tag already exists.
if git rev-parse -q --verify "refs/tags/$v$next_version" >/dev/null; then
abort "tag already exists: $v$next_version"
fi
# User confirmation.
while :; do
read -r -p 'Proceed? (y/N): ' yn
case "$yn" in
[yY]*)
break
;;
[nN]*)
echo 'Aborted' >&2
exit 1
;;
*)
;;
esac
done
# Create the release and bump the development version.
version_bump "$next_version"
git commit -a -m "$(release_commit_message "$next_version")"
git tag "$v$next_version"
dev_version_bump "$next_dev_version"
git commit -a -m "$(dev_commit_message "$next_dev_version")"
# Print a completion summary.
echo "Release tag $v$next_version was successfully created."
echo "The current development version is now $next_dev_version."
echo
echo "To push the release tag to origin:"
echo " git push origin $v$next_version"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment