blob: cacd4ba6533ec9416d4bdf8cb5f9d5f7832ce8d9 [file] [log] [blame]
#!/bin/bash
# Copyright 2015 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -eu
# Generate the release notes from the git history.
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
source ${SCRIPT_DIR}/common.sh
# It uses the RELNOTES tag in the history to knows the important changes to
# report:
# RELNOTES: indicates a change important the user.
# RELNOTES[NEW]: introduces a new feature.
# RELNOTES[INC]: indicates an incompatible change.
# The previous releases base is detected using the CHANGELOG file from the
# repository.
RELNOTES_TYPES=("INC" "NEW" "")
RELNOTES_DESC=("Incompatible changes" "New features" "Important changes")
# Get the baseline version and cherry-picks of the previous release
# Parameter: $1 is the path to the changelog file
# Output: "${BASELINE} ${CHERRYPICKS}"
# BASELINE is the hash of the baseline commit of the latest release
# CHERRYPICKS is the list of hash of cherry-picked commits of the latest release
# return 1 if there is no initial release
function __get_last_release() {
local changelog=$1
[ -f "$changelog" ] || return 1 # No changelog = initial release
local BASELINE_LINE=$(grep -m 1 -n '^Baseline: ' "$changelog") || return 1
[ -n "${BASELINE_LINE}" ] || return 1 # No baseline = initial release
local BASELINE_LINENB=$(echo "${BASELINE_LINE}" | cut -d ":" -f 1)
BASELINE=$(echo "${BASELINE_LINE}" | cut -d " " -f 2)
local CHERRYPICK_LINE=$(($BASELINE_LINENB + 3))
# grep -B999 looks for all lines before the empty line and after that we
# restrict to only lines with the cherry picked hash then finally we cut
# the hash.
local CHERRY_PICKS=$(tail -n +${CHERRYPICK_LINE} "$changelog" \
| grep -m 1 "^$" -B999 \
| grep -E '^ \+ [a-z0-9]+:' \
| cut -d ":" -f 1 | cut -d "+" -f 2)
echo $BASELINE $CHERRY_PICKS
return 0
}
# Now get the list of commit with a RELNOTES since latest release baseline ($1)
# discarding cherry_picks ($2..) and rollbacks. The returned list of commits is
# from the oldest to the newest
function __get_release_notes_commits() {
local baseline=$1
shift
local cherry_picks="$@"
local rollback_commits=$(git log --oneline -E --grep='^Rollback of commit [a-z0-9]+.$' ${baseline}.. \
| grep -E '^[a-z0-9]+ Rollback of commit [a-z0-9]+.$' || true)
local rollback_hashes=$(echo "$rollback_commits" | cut -d " " -f 1)
local rolledback_hashes=$(echo "$rollback_commits" | cut -d " " -f 5 | sed -E 's/^(.......).*$/\1/')
local exclude_hashes=$(echo DUMMY $cherry_picks $rollback_hashes $rolledback_hashes | xargs echo | sed 's/ /|/g')
git log --reverse --pretty=format:%H ${baseline}.. -E --grep='^RELNOTES(\[[^\]+\])?:' \
| grep -Ev "^(${exclude_hashes})" || true
}
# Extract the release note from a commit hash ($1). It extracts
# the RELNOTES([??]): lines. A new empty line ends the relnotes tag.
# It adds the relnotes, if not "None" ("None.") or "n/a" ("n/a.") to
# the correct array:
# RELNOTES_INC for incompatible changes
# RELNOTES_NEW for new features changes
# RELNOTES for other changes
function __extract_release_note() {
local find_relnote_awk_script="
BEGIN { in_relnote = 0 }
/^$/ { in_relnote = 0 }
/^PiperOrigin-RevId:.*$/ { in_relnote = 0 }
/^RELNOTES(\[[^\]]+\])?:/ { in_relnote = 1 }
{ if (in_relnote) { print } }"
local relnote="$(git show -s $1 --pretty=format:%B | awk "${find_relnote_awk_script}")"
local regex="^RELNOTES(\[([a-zA-Z]*)\])?:[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$"
if [[ "$relnote" =~ $regex ]]; then
local relnote_kind=${BASH_REMATCH[2]}
local relnote_text="${BASH_REMATCH[3]}"
if [[ ! "$(echo $relnote_text | awk '{print tolower($0)}')" =~ ^((none|n/a|no)(\.( .*)?)?|\.)$ ]]; then
eval "RELNOTES_${relnote_kind}+=(\"\${relnote_text}\")"
fi
fi
}
# Build release notes arrays from a list of commits ($@) and return the release
# note in an array of array.
function __generate_release_notes() {
for i in "${RELNOTES_TYPES[@]}"; do
eval "RELNOTES_${i}=()"
done
for commit in $@; do
__extract_release_note "${commit}"
done
}
# Returns the list of release notes in arguments into a list of points in
# a markdown list. The release notes are wrapped to 70 characters so it
# displays nicely in a git history.
function __format_release_notes() {
local i
for (( i=1; $i <= $#; i=$i+1 )); do
local relnote="${!i}"
local lines=$(echo "$relnote" | wrap_text 66) # wrap to 70 counting the 4 leading spaces.
echo " - $lines" | head -1
echo "$lines" | tail -n +2 | sed 's/^/ /'
done
}
# Create the release notes since commit $1 ($2...${[#]} are the cherry-picks,
# so the commits to ignore.
function __release_notes() {
local last_release=$1
local i
local commits=$(__get_release_notes_commits $last_release)
local length="${#RELNOTES_TYPES[@]}"
__generate_release_notes "$commits"
for (( i=0; $i < $length; i=$i+1 )); do
local relnotes_title="${RELNOTES_DESC[$i]}"
local relnotes_type=${RELNOTES_TYPES[$i]}
local relnotes="RELNOTES_${relnotes_type}[@]"
local nb_relnotes=$(eval "echo \${#$relnotes}")
if (( "${nb_relnotes}" > 0 )); then
echo "${relnotes_title}:"
echo
__format_release_notes "${!relnotes}"
echo
fi
done
# Add a list of contributors to thank.
# Stages:
# 1. Get the list of authors from the last release til now, both name and
# email.
# 2. Sort and uniqify.
# 3. Remove googlers. (This is why the email is needed)
# 4. Cut the email address, leaving only the name.
# 5-n. Remove trailing spaces and newlines, substituting with a comman and a
# space, removing any trailing spaces again.
local external_authors=$(git log $last_release..HEAD --format="%aN <%aE>" \
| sort \
| uniq \
| grep -v "google.com" \
| cut -d'<' -f 1 \
| sed -e 's/[[:space:]]$//' \
| tr '\n' ',' \
| sed -e 's/,$/\n/' \
| sed -e 's/,/, /g')
echo "This release contains contributions from many people at Google, as well as ${external_authors}."
}
# A wrapper around all the previous function, using the CHANGELOG.md
# file in $1 to compute the last release commit hash.
function create_release_notes() {
local last_release=$(__get_last_release "$1") || \
{ echo "Initial release."; return 0; }
[ -n "${last_release}" ] || { echo "Initial release."; return 0; }
__release_notes ${last_release}
}