631 lines
18 KiB
Bash
Executable File
631 lines
18 KiB
Bash
Executable File
#! /usr/bin/env bash
|
|
#
|
|
# Assembles a draft CHANGES entry out of revisions committed since the last
|
|
# entry was added. The entry is prepended to the current CHANGES file, and the
|
|
# user then gets a chance to further edit it in the editor before it gets
|
|
# committed.
|
|
#
|
|
# The script also maintains and updates a VERSION file.
|
|
#
|
|
# If the script finds a file called .update-changes.cfg it sources it at the
|
|
# beginning. That script can define a function "new_version_hook" that will be
|
|
# called with the new version number. It may use any of the replace_version_*
|
|
# functions defined below to update other files as necessary.
|
|
#
|
|
# If $1 is given, it's interpreted as a release version and a corresponding
|
|
# tag is created.
|
|
#
|
|
# To start using update-changes in a new project, proceed as follows:
|
|
#
|
|
# (1) Run "update-changes -I". This will initialize the CHANGES file and, if
|
|
# needed, establish suitable git tags that update-changes requires in order
|
|
# to start enumerating commits after a release. You can also prepare the
|
|
# initial version number in the VERSION file if you prefer that approach.
|
|
#
|
|
# (2) If you're planning to use an .update-changes.cfg file, add it as well as
|
|
# any corresponding changes it requires. Continue regular development, and
|
|
# when ready, run update-changes to reflect the first actual changeset in
|
|
# the CHANGES file.
|
|
#
|
|
file_changes="CHANGES" # The CHANGES file.
|
|
file_version="VERSION" # The VERSION file.
|
|
file_config=".update-changes.cfg" # This will be sourced if available.
|
|
new_version_hook="new_version_hook" # Function that will be called with new version number.
|
|
new_commit_msg="Updating CHANGES and VERSION." # Commit message when creating a new commit.
|
|
init_commit_msg="Starting CHANGES." # Commit message when we initialize CHANGES
|
|
show_authors=1 # Include author names with commit.
|
|
|
|
# The command line used to generate a revision's version string, such as
|
|
# v1.0.0-23-gabcdef. This relies on tags to work, which update-changes checks
|
|
# for. By default this only finds annotated tags; to allow lightweight ones as
|
|
# well, add --tags.
|
|
git_describe="git describe --tags" # {rev} will be added.
|
|
|
|
# The command line used to generate a revision's date. The revision will be
|
|
# appended. Not used with Zeek-style CHANGES file.
|
|
git_rev_date="git show -s --pretty=tformat:%ci"
|
|
|
|
# The command line used to generate the list of revisions between old and new
|
|
# state.
|
|
git_rev_list="git rev-list --topo-order HEAD" # ^{past-rev} will be added.
|
|
|
|
# The command line used to show the one-line summary of a revision before
|
|
# editing.
|
|
git_rev_summary="git show -s '--pretty=tformat: %h | %aN | %s'" # {rev} will be added.
|
|
|
|
# The command line used to get a revision's author.
|
|
git_author="git show -s --pretty=format:%aN" # {rev} will be added.
|
|
git_author_email="git show -s --pretty=format:%aE" # {rev} will be added.
|
|
|
|
# The command line used to get a revision's message.
|
|
git_msg=default_format_msg # {rev} will be added.
|
|
|
|
# Portable access to ERE, see e.g. https://unix.stackexchange.com/a/131940
|
|
if [ $(uname) == "Linux" ]; then
|
|
sed="sed -r"
|
|
else
|
|
sed="sed -E"
|
|
fi
|
|
|
|
function usage {
|
|
echo "usage: $(basename $0) [options]"
|
|
echo
|
|
echo " -p <rev> Explicitly name the past revision to compare with."
|
|
echo " -R <tag> Tag the current revision as a release. Update VERSION to use that."
|
|
echo " -B <tag> Tag the current revision as a beta release. Update VERSION to use that."
|
|
echo " -r Tag the current revision as a release, using the next point version as version tag."
|
|
echo " -I Initialize a new, initially empty CHANGES file."
|
|
echo " -c Check whether CHANGES is up to date."
|
|
echo " -n Do not amend the HEAD commit when feasible, create a new one."
|
|
echo
|
|
exit 1
|
|
}
|
|
|
|
# Takes a version string as input and turns it into a Python-styled one. For
|
|
# example, input "1.2-23" becomes "1.2.dev23". Other formats remain
|
|
# unchanged. See: https://peps.python.org/pep-0440/#version-scheme
|
|
function pythonic_version {
|
|
echo "$1" | $sed "s#-#.dev#"
|
|
}
|
|
|
|
### Functions that can be used to replace version strings in other files.
|
|
### To use them, create a file $file_config and define a function
|
|
### "new_version_hook" in there that does whatever is necessary, like calling
|
|
### any of these.
|
|
|
|
# Function that looks for lines of the form 'VERSION="1.2.3"' in $1. It will
|
|
# replace the version number with $2 and then git-adds the change.
|
|
function replace_version_in_script {
|
|
file=$1
|
|
version=$2
|
|
|
|
cat $file | $sed "s#^([[:blank:]]*VERSION[[:blank:]]*=[[:blank:]]*)\"([0-9.-]+)\"#\1\"$version\"#g" >$file.tmp
|
|
cat $file.tmp >$file
|
|
rm -f $file.tmp
|
|
git add $file
|
|
}
|
|
|
|
# Function that looks for lines of the form '.. |version| replace:: 0.3' in $1.
|
|
# It will replace the version number with $2 and then git-adds the change.
|
|
function replace_version_in_rst {
|
|
file=$1
|
|
version=$2
|
|
|
|
cat $file | $sed "s#^([[:blank:]]*\.\.[[:blank:]]*\|version\|[[:blank:]]*replace::[[:blank:]]*)([0-9a-zA-Z.-]+)#\1$version#g" >$file.tmp
|
|
cat $file.tmp >$file
|
|
rm -f $file.tmp
|
|
git add $file
|
|
}
|
|
|
|
# Function that checks file $1 for lines starting with 'version =
|
|
# "<version>"', where the version string can be of release form (e.g. "1.2.3")
|
|
# or a development one, expressed as "1.2-23" or the Python-styled "1.2.dev23".
|
|
# It will replace the version number with a Python-styled form of $2, then
|
|
# git-add the change.
|
|
function replace_version_in_setup_py {
|
|
file=$1
|
|
version=$(pythonic_version $2)
|
|
|
|
# The version string can be a sequence of digits and dots, optionally
|
|
# followed by either "-" or ".dev" plus at least one digit.
|
|
cat $file | $sed "s#^([[:blank:]]*version[[:blank:]]*=[[:blank:]]*)\"[0-9.]+((-|\.dev)[0-9]+)?\"#\1\"$version\"#g" >$file.tmp
|
|
cat $file.tmp >$file
|
|
rm -f $file.tmp
|
|
git add $file
|
|
}
|
|
|
|
# Function that checks file $1 for lines starting with '__version__ =
|
|
# "<version>"', where the version string can be of release form (e.g. "1.2.3")
|
|
# or a development one, expressed as "1.2-23" or the Python-styled "1.2.dev23".
|
|
# It will replace the version number with a Python-styled form of $2, then
|
|
# git-add the change.
|
|
function replace_version_in_python_package {
|
|
file=$1
|
|
version=$(pythonic_version $2)
|
|
|
|
# The version string can be a sequence of digits and dots, optionally
|
|
# followed by either "-" or ".dev" plus at least one digit.
|
|
cat $file | $sed "s#^([[:blank:]]*__version__[[:blank:]]*=[[:blank:]]*)\"[0-9.]+((-|\.dev)[0-9]+)?\"#\1\"$version\"#g" >$file.tmp
|
|
cat $file.tmp >$file
|
|
rm -f $file.tmp
|
|
git add $file
|
|
}
|
|
|
|
# Function that looks for lines of the form "#define .*VERSION "0.3"", with the
|
|
# number being "version * 100". It will replace the version with $2 and then
|
|
# git-adds the change.
|
|
function replace_version_in_c_header {
|
|
file=$1
|
|
version=$2
|
|
|
|
cat $file | $sed "s#([[:blank:]]*\#define[[:blank:]]*[_A-Za-z0-9]*_VERSION[[:blank:]]*)\"[0-9.-]+\"#\1\"$version\"#g" >$file.tmp
|
|
mv $file.tmp $file
|
|
git add $file
|
|
}
|
|
|
|
# Default function for preparing commit message. This scans the message for
|
|
# GitHub issue references to include.
|
|
function default_format_msg {
|
|
if command -v gawk &>/dev/null; then
|
|
# We need gawk for the match().
|
|
git show -s --pretty=format:%B $1 |
|
|
gawk '
|
|
match($0, "([Ii]ssue|[Gg][Hh]|#)[ _-]?([0-9]+)", x) { issues[x[2]] = 1; }
|
|
{ msg = msg $0 "\n"; }
|
|
END {
|
|
if ( ! match(msg, "^GH-[0-9]+") ) {
|
|
for ( i in issues )
|
|
printf("GH-%s: ", i);
|
|
}
|
|
|
|
print msg;
|
|
}'
|
|
else
|
|
git show -s --pretty=format:%B $1
|
|
fi
|
|
}
|
|
|
|
###
|
|
|
|
function version {
|
|
rev=$1
|
|
$git_describe $rev --match "v*" 2>/dev/null | $sed 's/^v//g' | $sed 's/-g.*//g' | $sed 's/-([[:alnum:]]+)-([0-9]+)$/-\1.\2/g'
|
|
}
|
|
|
|
function start_changes_entry {
|
|
version=$1
|
|
dst=$2
|
|
|
|
if [ "$zeek_style" == "0" ]; then
|
|
date=$($git_rev_date HEAD)
|
|
printf '%s | %s\n' "$version" "$date" >>$dst
|
|
else
|
|
date=$(date)
|
|
printf '%s %s\n' "$version" "$date" >>$dst
|
|
fi
|
|
}
|
|
|
|
function add_to_changes_entry {
|
|
rev=$1
|
|
dst=$2
|
|
msg=$3
|
|
|
|
author=""
|
|
|
|
if [ "$msg" == "" ]; then
|
|
if [ "$show_authors" == "1" ]; then
|
|
author_email=$($git_author_email $rev)
|
|
author=$($git_author $rev)
|
|
|
|
if [[ "$author_email" == *@corelight.com ]]; then
|
|
author=" ($author, Corelight)"
|
|
else
|
|
author=" ($author)"
|
|
fi
|
|
fi
|
|
|
|
msg=$($git_msg $rev)
|
|
fi
|
|
|
|
if [ "$msg" == "" ]; then
|
|
return 1
|
|
fi
|
|
|
|
if echo $msg | grep -q "^$new_commit_msg\$"; then
|
|
# Ignore our own automated commits.
|
|
return 1
|
|
fi
|
|
|
|
if [[ $(git show --no-patch --format='%P' "$rev" | wc -w) -gt 1 ]]; then
|
|
# Ignore merge commits, i.e., commits with more than one parent.
|
|
return 1
|
|
fi
|
|
|
|
echo >>$dst
|
|
|
|
if [ "$zeek_style" == "0" ]; then
|
|
bullet=" *"
|
|
else
|
|
bullet="-"
|
|
fi
|
|
|
|
echo -n "$msg" |
|
|
awk -v bullet="$bullet" -v author="$author" 'NR==1{printf "%s %s%s\n", bullet, $0, author; next }{printf " %s\n", $0}' |
|
|
$sed 's/[[:blank:]]*$//' >>$dst
|
|
|
|
return 0
|
|
}
|
|
|
|
function init_changes {
|
|
for rev in $(git rev-list HEAD); do
|
|
version=$(version $rev)
|
|
[ -n "$version" ] && break
|
|
done
|
|
|
|
git_version=$version
|
|
|
|
if [ "$version" == "" ] && [ -f $file_version ]; then
|
|
# git doesn't offer version info, but there's a VERSION file.
|
|
# Consider it if the user's okay with it.
|
|
version=$(cat $file_version | head -1)
|
|
if [ -n "$version" ]; then
|
|
echo "This git repo doesn't yet offer suitable version tags."
|
|
read -p "Use '$version' from $file_version? Y/n " -n 1 -r
|
|
echo
|
|
if [ -n "$REPLY" ] && [[ $REPLY != [Yy] ]]; then
|
|
version=
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [ "$version" == "" ]; then
|
|
read -p "No initial version available, please provide one (e.g. 0.1, 1.0.0): " -r version
|
|
if [[ "$version" == v* ]]; then
|
|
# We don't need a "v" prefix here, it only exists in the git tags.
|
|
version=${version:1}
|
|
fi
|
|
fi
|
|
|
|
# Subtle: if we're basing this CHANGES intro on a git tag, then we have a
|
|
# chicken-and-egg problem with correct numbering of the commit introducing
|
|
# CHANGES. It is itself going to be the _next_ commit. We could increment
|
|
# the git-derived version number arithmetically, but it's easier to just
|
|
# commit an empty CHANGES and then augment that below.
|
|
if [ -n "$git_version" ]; then
|
|
touch $file_changes
|
|
git add $file_changes
|
|
git commit -m "$init_commit_msg"
|
|
version=$(version HEAD)
|
|
flags="--amend"
|
|
fi
|
|
|
|
start_changes_entry $version $file_changes
|
|
echo >>$file_changes
|
|
echo " * Starting $file_changes." >>$file_changes
|
|
|
|
git add $file_changes
|
|
git commit $flags -m "$init_commit_msg"
|
|
|
|
if [ -z "$git_version" ]; then
|
|
git tag "v$version"
|
|
fi
|
|
}
|
|
|
|
function get_last_rev {
|
|
version=$(cat $file_changes | grep -E '^[0-9a-zA-Z.-]+ *\|' | head -1 | awk '{print $1}')
|
|
|
|
if echo $version | grep -q -- '-'; then
|
|
# version is now e.g. 1.0.4-14 -- find the revision with that number.
|
|
for rev in $(git rev-list HEAD); do
|
|
v=$(version $rev)
|
|
|
|
if [ "$v" == "$version" ]; then
|
|
echo $rev
|
|
return
|
|
fi
|
|
done
|
|
|
|
echo "Cannot determine revision for version $version." >/dev/stderr
|
|
exit 1
|
|
|
|
else
|
|
# A tag.
|
|
echo "v$version"
|
|
fi
|
|
}
|
|
|
|
function check_release_tag {
|
|
if [ "$release" != "" ]; then
|
|
git tag -d $release 2>/dev/null
|
|
git tag -a $release -m "Version tag"
|
|
echo "Tagged with new tag $release."
|
|
echo
|
|
echo "Push with: "
|
|
echo
|
|
echo " git push origin && git push origin $release"
|
|
echo
|
|
fi
|
|
}
|
|
|
|
function check_beta_tag {
|
|
if [ "$beta" != "" ]; then
|
|
git tag -d $beta 2>/dev/null
|
|
git tag -a $beta -m "Beta version tag"
|
|
echo "Tagged with new tag $beta."
|
|
echo
|
|
echo "Push with: "
|
|
echo
|
|
echo " git push origin && git push origin $beta"
|
|
echo
|
|
fi
|
|
}
|
|
|
|
function check_submodules {
|
|
if git submodule status --recursive | grep ^+; then
|
|
cat <<EOF
|
|
|
|
The revision recorded for the module(s) above does not
|
|
match the one currently checked out in the respective
|
|
subdirs.
|
|
|
|
Please either update or checkout the recorded revision(s).
|
|
|
|
Aborting.
|
|
EOF
|
|
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
function get_release_version {
|
|
# If $1 is provided, return that. Otherwise look for most recent release
|
|
# version in CHANGES and increase its point version.
|
|
test -n "$1" && echo "$1" && return
|
|
old=$(cat $file_changes | grep -E '^[0-9]+\.[0-9]+\.[0-9]+(-(dev\.)?[0-9]+)? ' | cut -d ' ' -f 1 | head -1)
|
|
test -z "${old}" && echo "" && return
|
|
point=$(echo ${old} | cut -d - -f 1 | cut -d . -f 3)
|
|
point=$((${point} + 1))
|
|
new="$(echo ${old} | cut -d . -f 1-2).${point}"
|
|
echo v${new}
|
|
}
|
|
|
|
######
|
|
|
|
last_rev=""
|
|
release=""
|
|
beta=""
|
|
init=0
|
|
check=0
|
|
quiet=0
|
|
no_amends=0
|
|
|
|
while getopts "hp:rR:B:Icn" opt; do
|
|
case "$opt" in
|
|
p) last_rev="$OPTARG" ;;
|
|
R) release="$OPTARG" ;;
|
|
r)
|
|
release=$(get_release_version)
|
|
if [ -z "${release}" ]; then
|
|
echo "Cannot determine release version."
|
|
exit 1
|
|
fi
|
|
;;
|
|
B) beta="$OPTARG" ;;
|
|
I) init=1 ;;
|
|
c)
|
|
check=1
|
|
quiet=1
|
|
;;
|
|
n) no_amends=1 ;;
|
|
*) usage ;;
|
|
esac
|
|
done
|
|
|
|
if [ -e $file_config ]; then
|
|
if [ "$quiet" != "1" ]; then
|
|
echo Reading $file_config ...
|
|
fi
|
|
source ./$file_config
|
|
fi
|
|
|
|
if [ "$release" != "" -a "$beta" != "" ]; then
|
|
echo "Cannot tag as both beta and release."
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$release" == "VERSION" ]; then
|
|
release="v$(cat VERSION)"
|
|
fi
|
|
|
|
if [ "$beta" == "VERSION" ]; then
|
|
beta="v$(cat VERSION)"
|
|
fi
|
|
|
|
zeek_style=0 # If 1, we use a slightly different format.
|
|
|
|
if [ "$init" != "0" ]; then
|
|
if [ -e $file_changes ]; then
|
|
echo "$file_changes already exists, remove it first."
|
|
exit 1
|
|
else
|
|
echo "Initializing $file_changes ..."
|
|
init_changes
|
|
exit 0
|
|
fi
|
|
else
|
|
if [ ! -e $file_changes ]; then
|
|
echo "$file_changes does not exist, initialize it with '-I'."
|
|
exit 1
|
|
else
|
|
# If we find this marker, it's Zeek-style CHANGES file.
|
|
grep -vq -- '-+-+-+-+-+-+-+-+-+-' $file_changes
|
|
zeek_style=$?
|
|
fi
|
|
fi
|
|
|
|
if [ "$release" != "" ]; then
|
|
if ! echo $release | grep -E -q '^v[0-9]+\.[0-9]+'; then
|
|
echo "Release tag must be of the form vX.Y[.Z]"
|
|
exit 1
|
|
fi
|
|
|
|
check_submodules
|
|
fi
|
|
|
|
if [ "$beta" != "" ]; then
|
|
if ! echo $beta | grep -E -q '^v[0-9]+\.[0-9]+(\.[0-9]+)?-(beta|rc)'; then
|
|
echo "Release tag must be of the form vX.Y[.Z]-(beta|rc)*"
|
|
exit 1
|
|
fi
|
|
|
|
check_submodules
|
|
fi
|
|
|
|
if [ "$last_rev" == "" ]; then
|
|
last_rev=$(get_last_rev)
|
|
fi
|
|
|
|
if [ "$last_rev" == "" ]; then
|
|
echo 'Cannot determine previous revision to compare with, specify with "-p <rev>".'
|
|
exit 1
|
|
fi
|
|
|
|
auto_version=$(version HEAD)
|
|
|
|
if [ "$auto_version" == "" ]; then
|
|
echo "Cannot determine version, checking HEAD did not return anything."
|
|
exit 1
|
|
fi
|
|
|
|
tmp=${file_changes}.$$.tmp
|
|
trap "rm -f $tmp" EXIT
|
|
rm -f $tmp
|
|
|
|
found=0
|
|
|
|
new_version=$auto_version
|
|
version=$(version $rev)
|
|
|
|
if [ "$version" == "" ]; then
|
|
echo "Cannot determine version for $rev."
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$release" != "" ]; then
|
|
new_version=$(echo $release | sed 's/v//g')
|
|
fi
|
|
|
|
if [ "$beta" != "" ]; then
|
|
new_version=$(echo $beta | sed 's/v//g')
|
|
fi
|
|
|
|
if [ "$quiet" != "1" ]; then
|
|
echo "New version is $new_version."
|
|
echo "Listing revisions committed since $(version $last_rev) ($last_rev) ... "
|
|
echo
|
|
fi
|
|
|
|
start_changes_entry $new_version $tmp
|
|
|
|
for rev in $($git_rev_list ^$last_rev); do
|
|
|
|
version=$(version $rev)
|
|
|
|
if [ "$version" == "" ]; then
|
|
version="<no-version>"
|
|
fi
|
|
|
|
# printf "%15s |" $version
|
|
|
|
if add_to_changes_entry $rev $tmp; then
|
|
found=1
|
|
|
|
if [ "$quiet" != "1" ]; then
|
|
eval "$git_rev_summary $rev | grep -v '^$' | cat"
|
|
fi
|
|
fi
|
|
|
|
done
|
|
|
|
if [ "$found" == "0" ]; then
|
|
if [ "$check" == "1" ]; then
|
|
echo "CHANGES is up to date."
|
|
exit 0
|
|
fi
|
|
|
|
echo " None."
|
|
echo
|
|
|
|
if [ "$release" != "" -o "$beta" != "" ]; then
|
|
add_to_changes_entry head $tmp "Release $new_version."
|
|
else
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
if [ "$check" == "1" ]; then
|
|
echo "CHANGES is NOT up to date."
|
|
exit 1
|
|
fi
|
|
|
|
echo >>$tmp
|
|
|
|
cat $file_changes >>$tmp
|
|
|
|
# If we are ahead of origin, we can amend. If not, we need to create a new
|
|
# commit even if the user wants otherwise. If the user requested -n (no
|
|
# amendments), we skip all of this.
|
|
amend=0
|
|
|
|
if [ $no_amends == "0" ] &&
|
|
git remote | grep -q origin &&
|
|
git rev-list origin/$(git rev-parse --abbrev-ref HEAD)..HEAD | grep -q .; then
|
|
amend=1
|
|
fi
|
|
|
|
echo
|
|
|
|
if [ "$amend" == "0" ]; then
|
|
echo Update to $file_changes will become a new commit.
|
|
else
|
|
echo Update to $file_changes will be amended to last commit.
|
|
fi
|
|
|
|
echo
|
|
echo Type Enter to edit new $file_changes, or CTRL-C to abort without any modifications.
|
|
read
|
|
|
|
# Run editor.
|
|
if [ -z "$EDITOR" ]; then
|
|
EDITOR=vi
|
|
fi
|
|
eval $EDITOR $tmp
|
|
|
|
# Put changes in place.
|
|
mv $tmp $file_changes
|
|
echo "Updated $file_changes."
|
|
|
|
if [ "$file_version" != "" ]; then
|
|
echo $new_version >$file_version
|
|
echo "Updated $version to $new_version."
|
|
fi
|
|
|
|
# Call hook function if it exists.
|
|
if type $new_version_hook >/dev/null 2>&1; then
|
|
$new_version_hook $new_version
|
|
fi
|
|
|
|
# Commit changes.
|
|
git add $file_changes $file_version
|
|
|
|
if [ "$amend" == "1" ]; then
|
|
git commit --amend
|
|
else
|
|
git commit -m "$new_commit_msg"
|
|
fi
|
|
|
|
echo "Updates committed."
|
|
|
|
check_release_tag
|
|
check_beta_tag
|