#! /usr/bin/env bash # Copyright 2020, Mark Callow # SPDX-License-Identifier: Apache-2.0 # Generate release notes from git log. # # mkrelnotes [-b] [-c] [-d] [-e] [-l] [-p [-t] # # is the name of the tag identifying the previous # release. is the name to be given to the tag for the upcoming # release. N.B. this tag cannot be created until after ther release notes # have been generated and checked in. The release notes will include changes # in the two-dot commit range ..HEAD. # # is inserted between the title and the detailed change list # that is extracted with git log. It should be used to summarize # new features and known issues using level 3 (###) headings for those # sections. # # If -b is specified build system changes will be included in the # release notes. # # If -c is specified any previous $RELNOTES_FILE will be appended, with the # title lines stripped, to the release notes generated here, thus enabling # cumulative release notes. Otherwise any previous $RELNOTES_FILE file is # replaced. # # If -d is specified details of the changes will be included, otherwise only # the summary is shown. # # If -e is specified changes to `external` packages will be included in the # release notes. # # If -l is specified changes to the load test apps will be included in the # release notes. # # If -i is specified the release notes will be built interactively. That is, # for each change the user will be asked if it should be included. # # If -t is specified changes to the tests will be included in the release notes # otherwise they are omitted. # Depth of this script relative to the project root depth=.. RELNOTES_FILE="RELEASE_NOTES.md" # The author name in a commit message comes from whatever the user # has set as user.name in their git config. It usually has no relation # to their GitHub username so putting an @ before it has no meaning. # SUMMARY_FORMAT_W_AUTH="* %s (%h) (%an)" SUMMARY_FORMAT="* %s (%h)" function commit_summary() { local hash=$1 local summary local prjson pr=$(git log --oneline -n 1 $hash | grep -o -E "\(#[0-9]+\)" | grep -o -E "[0-9]+") # Even in PR's the commit has the full name of the user as known to GitHub, # not their GitHub username. We want to use the GitHub username so there will # be a link back to the person. We can retrieve this by requesting detailed # info from GitHub via HTTPS. summary=$(git log --pretty="$SUMMARY_FORMAT" -n 1 $hash) if [ -z "$pr" ]; then cmtjson="$(curl --netrc https://api.github.com/repos/KhronosGroup/KTX-Software/commits/$hash 2>/dev/null)" author="$(echo $cmtjson | jq -r -e '.author.login')" else prjson="$(curl --netrc https://api.github.com/repos/KhronosGroup/KTX-Software/pulls/$pr 2>/dev/null)" author="$(echo $prjson | jq -r -e '.user.login')" fi summary+=" (@$author)" # \_ avoids confusion with _ for italics. echo "$summary" | sed -e 's/_/\\_/g' if [ -n "$includeDetails" ]; then body="$(git log --pretty="%b" -n 1 $hash | sed -E 's/^(.+)/ \1/')" if [ -n "$body" ]; then echo "" # Remove CR which some multi-line commit bodies contain for unknown # reasons. echo "$body" | tr -d \\r | sed -e 's/_/\\_/g' fi fi } function revisions_in () { local range=$1; shift git rev-list $range $* } function log() { local part=$1; shift local log for rev in $(revisions_in "$range" $*); do if [ -n "$interactive" ]; then git log -1 $rev > /dev/tty processed=0 while [ $processed -eq 0 ]; do echo "Include this change? [d,i,s,?] ?" > /dev/tty read -n 1 opt echo "" > /dev/tty case $opt in d) git show --pretty=oneline $rev > /dev/tty ;; [is]) processed=1 ;; ?) echo "d - show diff of this commit" > /dev/tty echo "i - include this commit." > /dev/tty echo "s - skip this commit." > /dev/tty echo "? - display this help message." > /dev/tty ;; *) echo "Unknown option: $opt. Try again." > /dev/tty ;; esac done case $opt in i) log+="$(commit_summary $rev)" ;; esac else log+="$(commit_summary $rev)" fi # For reasons I do not understand I have been completely unable to prevent # trailing newlines from being removed from the output of commit_summary # so resorting to ANSI-C quoting to insert some new lines between summaries. log+=$'\n\n' done if [ -n "$log" ]; then echo "### $part" echo echo "$log" echo fi } function usage() { cat << EOU Usage: $0 [-b] [-c] [-d] [-i] [-p ] [-t] " Options: -b Include build system changes. -c Make cumulative release notes. -d Include details of changes. If absent only the summary is shown. -e Include changes to `external` packages. -i Interactively select the changes to include. -l Include changes to load test apps. Ignored if -t specified. -p Include the file before the list of changes. -t Include test changes. EOU } while true; do case $1 in -b) includeBuildSystem="true"; shift ;; -c) cumulative="true"; shift;; -d) includeDetails="true"; shift ;; -e) includeExternal="true"; shift ;; -i) interactive="true"; shift;; -l) includeLoadtests="true"; shift ;; -p) preface=$2; shift 2 ;; -t) includeTests="true"; shift ;; -*) usage; exit 1 ;; *) break ;; esac done if [ $# -ne 2 ]; then echo "$0: Need previous release tag and this release name, e.g. 'v4.0.0 v4.0.1'." exit 1 else range=$1.. lastrel=$1 thisrel=$2 fi #if [ $# -ne 1 ]; then # echo '$0: Need a two-dot revision range, e.g, `v4.0.0..v4.0.1`.' # exit 1 #else # range=$1 # if ! [[ $range =~ ([[:alnum:][:punct:]]+)\.\.([[:alnum:]][[:alnum:][:punct:]]*)? ]]; then # echo "$0: is not a two-dot range." # exit 1 # else # lastrel=${BASH_REMATCH[1]} # thisrel=${BASH_REMATCH[2]} # if [ -z "$lastrel" ]; then # lastrel=HEAD # fi # fi #fi # Change to the project root cd $(dirname $0)/$depth SAVED_RELNOTES_FILE="${RELNOTES_FILE%.md}-$lastrel.md" # Save or remove old relnotes. if [ -f $RELNOTES_FILE ]; then if [ -z "$cumulative" ]; then rm $RELNOTES_FILE; else mv $RELNOTES_FILE $SAVED_RELNOTES_FILE fi fi # Read preface file before cd. if [ -n "$preface" ]; then PREFACE=$(cat $preface) fi lib=$(log libktx lib) tools=$(log Tools tools) js_binding=$(log "JS Bindings" interface/js_binding) java_binding=$(log "Java Bindings" interface/java_binding) python_binding=$(log "Python Bindings" interface/python_binding) if [ -n "$includeBuildSystem" ]; then build=$(log "Build Scripts and CMake files" scripts $(find . \( -path ./build -o -path ./.git -o -path ./external -o -path ./other_include \) -prune -false -o -name CMakeLists.txt -o -name '*.cmake')) fi if [ -n "$includeExternal" ]; then external=$(log "External Package Dependencies" external) fi if [ -n "$includeTests" ]; then tests=$(log "Tests" tests) elif [ -n "$includeLoadtests" ]; then loadtests=$(log "Load test apps" tests/loadtests) fi cat > $RELNOTES_FILE <<- EOF $(date '+') Release Notes ============= ## Version ${thisrel#v} $PREFACE ### Changes since $lastrel (by part) $lib $tools $js_binding $java_binding $python_binding $external $tests $loadtests $build EOF if [ -f $SAVED_RELNOTES_FILE ]; then awk '! /Release Notes/ && ! /======/ && ! /