blob: cacd4ba6533ec9416d4bdf8cb5f9d5f7832ce8d9 [file] [log] [blame]
Philipp Wollermanna5afe952016-06-21 14:58:09 +00001#!/bin/bash
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +00002
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00003# Copyright 2015 The Bazel Authors. All rights reserved.
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +00004#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Philipp Wollermanna5afe952016-06-21 14:58:09 +000017set -eu
18
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +000019# Generate the release notes from the git history.
20
Philipp Wollermann5fabb432018-03-27 04:37:23 -070021SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
22source ${SCRIPT_DIR}/common.sh
23
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +000024# It uses the RELNOTES tag in the history to knows the important changes to
25# report:
26# RELNOTES: indicates a change important the user.
27# RELNOTES[NEW]: introduces a new feature.
28# RELNOTES[INC]: indicates an incompatible change.
29# The previous releases base is detected using the CHANGELOG file from the
30# repository.
31RELNOTES_TYPES=("INC" "NEW" "")
32RELNOTES_DESC=("Incompatible changes" "New features" "Important changes")
33
34# Get the baseline version and cherry-picks of the previous release
35# Parameter: $1 is the path to the changelog file
36# Output: "${BASELINE} ${CHERRYPICKS}"
37# BASELINE is the hash of the baseline commit of the latest release
38# CHERRYPICKS is the list of hash of cherry-picked commits of the latest release
39# return 1 if there is no initial release
Philipp Wollermann5fabb432018-03-27 04:37:23 -070040function __get_last_release() {
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +000041 local changelog=$1
42 [ -f "$changelog" ] || return 1 # No changelog = initial release
43 local BASELINE_LINE=$(grep -m 1 -n '^Baseline: ' "$changelog") || return 1
44 [ -n "${BASELINE_LINE}" ] || return 1 # No baseline = initial release
45 local BASELINE_LINENB=$(echo "${BASELINE_LINE}" | cut -d ":" -f 1)
46 BASELINE=$(echo "${BASELINE_LINE}" | cut -d " " -f 2)
Kristina Chodorowe7be8392016-04-22 20:34:37 +000047 local CHERRYPICK_LINE=$(($BASELINE_LINENB + 3))
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +000048 # grep -B999 looks for all lines before the empty line and after that we
49 # restrict to only lines with the cherry picked hash then finally we cut
50 # the hash.
51 local CHERRY_PICKS=$(tail -n +${CHERRYPICK_LINE} "$changelog" \
52 | grep -m 1 "^$" -B999 \
53 | grep -E '^ \+ [a-z0-9]+:' \
54 | cut -d ":" -f 1 | cut -d "+" -f 2)
55 echo $BASELINE $CHERRY_PICKS
56 return 0
57}
58
59# Now get the list of commit with a RELNOTES since latest release baseline ($1)
60# discarding cherry_picks ($2..) and rollbacks. The returned list of commits is
61# from the oldest to the newest
Philipp Wollermann5fabb432018-03-27 04:37:23 -070062function __get_release_notes_commits() {
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +000063 local baseline=$1
64 shift
65 local cherry_picks="$@"
66 local rollback_commits=$(git log --oneline -E --grep='^Rollback of commit [a-z0-9]+.$' ${baseline}.. \
Damien Martin-Guillerezb62f7172017-10-16 15:19:27 +020067 | grep -E '^[a-z0-9]+ Rollback of commit [a-z0-9]+.$' || true)
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +000068 local rollback_hashes=$(echo "$rollback_commits" | cut -d " " -f 1)
69 local rolledback_hashes=$(echo "$rollback_commits" | cut -d " " -f 5 | sed -E 's/^(.......).*$/\1/')
Damien Martin-Guillereza97f9142017-10-16 16:20:20 +020070 local exclude_hashes=$(echo DUMMY $cherry_picks $rollback_hashes $rolledback_hashes | xargs echo | sed 's/ /|/g')
Damien Martin-Guillerezdbdaf832017-01-26 14:41:04 +000071 git log --reverse --pretty=format:%H ${baseline}.. -E --grep='^RELNOTES(\[[^\]+\])?:' \
Damien Martin-Guillerezb62f7172017-10-16 15:19:27 +020072 | grep -Ev "^(${exclude_hashes})" || true
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +000073}
74
75# Extract the release note from a commit hash ($1). It extracts
76# the RELNOTES([??]): lines. A new empty line ends the relnotes tag.
77# It adds the relnotes, if not "None" ("None.") or "n/a" ("n/a.") to
78# the correct array:
79# RELNOTES_INC for incompatible changes
80# RELNOTES_NEW for new features changes
81# RELNOTES for other changes
Philipp Wollermann5fabb432018-03-27 04:37:23 -070082function __extract_release_note() {
Marcel Hlopkodb136252017-05-04 16:04:23 +020083 local find_relnote_awk_script="
84 BEGIN { in_relnote = 0 }
85 /^$/ { in_relnote = 0 }
86 /^PiperOrigin-RevId:.*$/ { in_relnote = 0 }
87 /^RELNOTES(\[[^\]]+\])?:/ { in_relnote = 1 }
88 { if (in_relnote) { print } }"
89 local relnote="$(git show -s $1 --pretty=format:%B | awk "${find_relnote_awk_script}")"
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +000090 local regex="^RELNOTES(\[([a-zA-Z]*)\])?:[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$"
91 if [[ "$relnote" =~ $regex ]]; then
92 local relnote_kind=${BASH_REMATCH[2]}
93 local relnote_text="${BASH_REMATCH[3]}"
Damien Martin-Guillerez445a8792017-10-17 14:06:58 +020094 if [[ ! "$(echo $relnote_text | awk '{print tolower($0)}')" =~ ^((none|n/a|no)(\.( .*)?)?|\.)$ ]]; then
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +000095 eval "RELNOTES_${relnote_kind}+=(\"\${relnote_text}\")"
96 fi
97 fi
98}
99
100# Build release notes arrays from a list of commits ($@) and return the release
101# note in an array of array.
Philipp Wollermann5fabb432018-03-27 04:37:23 -0700102function __generate_release_notes() {
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000103 for i in "${RELNOTES_TYPES[@]}"; do
104 eval "RELNOTES_${i}=()"
105 done
Androbin9c78a792017-11-29 01:31:47 -0800106 for commit in $@; do
Philipp Wollermann5fabb432018-03-27 04:37:23 -0700107 __extract_release_note "${commit}"
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000108 done
109}
110
111# Returns the list of release notes in arguments into a list of points in
112# a markdown list. The release notes are wrapped to 70 characters so it
113# displays nicely in a git history.
Philipp Wollermann5fabb432018-03-27 04:37:23 -0700114function __format_release_notes() {
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000115 local i
116 for (( i=1; $i <= $#; i=$i+1 )); do
117 local relnote="${!i}"
Damien Martin-Guillerez3b61b2c2015-07-27 10:05:43 +0000118 local lines=$(echo "$relnote" | wrap_text 66) # wrap to 70 counting the 4 leading spaces.
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000119 echo " - $lines" | head -1
120 echo "$lines" | tail -n +2 | sed 's/^/ /'
121 done
122}
123
124# Create the release notes since commit $1 ($2...${[#]} are the cherry-picks,
125# so the commits to ignore.
Philipp Wollermann5fabb432018-03-27 04:37:23 -0700126function __release_notes() {
John Cater9225a132018-10-31 10:38:42 -0700127 local last_release=$1
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000128 local i
John Cater9225a132018-10-31 10:38:42 -0700129 local commits=$(__get_release_notes_commits $last_release)
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000130 local length="${#RELNOTES_TYPES[@]}"
Philipp Wollermann5fabb432018-03-27 04:37:23 -0700131 __generate_release_notes "$commits"
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000132 for (( i=0; $i < $length; i=$i+1 )); do
133 local relnotes_title="${RELNOTES_DESC[$i]}"
134 local relnotes_type=${RELNOTES_TYPES[$i]}
135 local relnotes="RELNOTES_${relnotes_type}[@]"
136 local nb_relnotes=$(eval "echo \${#$relnotes}")
137 if (( "${nb_relnotes}" > 0 )); then
138 echo "${relnotes_title}:"
139 echo
Philipp Wollermann5fabb432018-03-27 04:37:23 -0700140 __format_release_notes "${!relnotes}"
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000141 echo
142 fi
143 done
John Cater9225a132018-10-31 10:38:42 -0700144
145 # Add a list of contributors to thank.
John Caterf3fa8762018-11-26 11:55:43 -0800146 # Stages:
147 # 1. Get the list of authors from the last release til now, both name and
148 # email.
149 # 2. Sort and uniqify.
150 # 3. Remove googlers. (This is why the email is needed)
151 # 4. Cut the email address, leaving only the name.
152 # 5-n. Remove trailing spaces and newlines, substituting with a comman and a
153 # space, removing any trailing spaces again.
John Cater9225a132018-10-31 10:38:42 -0700154 local external_authors=$(git log $last_release..HEAD --format="%aN <%aE>" \
155 | sort \
156 | uniq \
157 | grep -v "google.com" \
John Caterf3fa8762018-11-26 11:55:43 -0800158 | cut -d'<' -f 1 \
John Cater9225a132018-10-31 10:38:42 -0700159 | sed -e 's/[[:space:]]$//' \
160 | tr '\n' ',' \
161 | sed -e 's/,$/\n/' \
162 | sed -e 's/,/, /g')
163 echo "This release contains contributions from many people at Google, as well as ${external_authors}."
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000164}
165
166# A wrapper around all the previous function, using the CHANGELOG.md
167# file in $1 to compute the last release commit hash.
168function create_release_notes() {
Philipp Wollermann5fabb432018-03-27 04:37:23 -0700169 local last_release=$(__get_last_release "$1") || \
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000170 { echo "Initial release."; return 0; }
171 [ -n "${last_release}" ] || { echo "Initial release."; return 0; }
Philipp Wollermann5fabb432018-03-27 04:37:23 -0700172 __release_notes ${last_release}
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000173}