blob: 221c9de4d2def5637f1b64e442891a395bdd240c [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
21# It uses the RELNOTES tag in the history to knows the important changes to
22# report:
23# RELNOTES: indicates a change important the user.
24# RELNOTES[NEW]: introduces a new feature.
25# RELNOTES[INC]: indicates an incompatible change.
26# The previous releases base is detected using the CHANGELOG file from the
27# repository.
28RELNOTES_TYPES=("INC" "NEW" "")
29RELNOTES_DESC=("Incompatible changes" "New features" "Important changes")
30
31# Get the baseline version and cherry-picks of the previous release
32# Parameter: $1 is the path to the changelog file
33# Output: "${BASELINE} ${CHERRYPICKS}"
34# BASELINE is the hash of the baseline commit of the latest release
35# CHERRYPICKS is the list of hash of cherry-picked commits of the latest release
36# return 1 if there is no initial release
37function get_last_release() {
38 local changelog=$1
39 [ -f "$changelog" ] || return 1 # No changelog = initial release
40 local BASELINE_LINE=$(grep -m 1 -n '^Baseline: ' "$changelog") || return 1
41 [ -n "${BASELINE_LINE}" ] || return 1 # No baseline = initial release
42 local BASELINE_LINENB=$(echo "${BASELINE_LINE}" | cut -d ":" -f 1)
43 BASELINE=$(echo "${BASELINE_LINE}" | cut -d " " -f 2)
Kristina Chodorowe7be8392016-04-22 20:34:37 +000044 local CHERRYPICK_LINE=$(($BASELINE_LINENB + 3))
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +000045 # grep -B999 looks for all lines before the empty line and after that we
46 # restrict to only lines with the cherry picked hash then finally we cut
47 # the hash.
48 local CHERRY_PICKS=$(tail -n +${CHERRYPICK_LINE} "$changelog" \
49 | grep -m 1 "^$" -B999 \
50 | grep -E '^ \+ [a-z0-9]+:' \
51 | cut -d ":" -f 1 | cut -d "+" -f 2)
52 echo $BASELINE $CHERRY_PICKS
53 return 0
54}
55
56# Now get the list of commit with a RELNOTES since latest release baseline ($1)
57# discarding cherry_picks ($2..) and rollbacks. The returned list of commits is
58# from the oldest to the newest
59function get_release_notes_commits() {
60 local baseline=$1
61 shift
62 local cherry_picks="$@"
63 local rollback_commits=$(git log --oneline -E --grep='^Rollback of commit [a-z0-9]+.$' ${baseline}.. \
64 | grep -E '^[a-z0-9]+ Rollback of commit [a-z0-9]+.$')
65 local rollback_hashes=$(echo "$rollback_commits" | cut -d " " -f 1)
66 local rolledback_hashes=$(echo "$rollback_commits" | cut -d " " -f 5 | sed -E 's/^(.......).*$/\1/')
67 local exclude_hashes=$(echo $cherry_picks $rollback_hashes $rolledback_hashes | xargs echo | sed 's/ /|/g')
68 git log --reverse --pretty=format:%h ${baseline}.. -E --grep='^RELNOTES(\[[^\]+\])?:' \
69 | grep -Ev "^(${exclude_hashes})"
70}
71
72# Extract the release note from a commit hash ($1). It extracts
73# the RELNOTES([??]): lines. A new empty line ends the relnotes tag.
74# It adds the relnotes, if not "None" ("None.") or "n/a" ("n/a.") to
75# the correct array:
76# RELNOTES_INC for incompatible changes
77# RELNOTES_NEW for new features changes
78# RELNOTES for other changes
79function extract_release_note() {
80 local relnote="$(git show -s $1 --pretty=format:%B | awk '/^RELNOTES(\[[^\]]+\])?:/,/^$/')"
81 local regex="^RELNOTES(\[([a-zA-Z]*)\])?:[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$"
82 if [[ "$relnote" =~ $regex ]]; then
83 local relnote_kind=${BASH_REMATCH[2]}
84 local relnote_text="${BASH_REMATCH[3]}"
85 if [[ ! "$(echo $relnote_text | awk '{print tolower($0)}')" =~ ^(none|n/a)?.?$ ]]; then
86 eval "RELNOTES_${relnote_kind}+=(\"\${relnote_text}\")"
87 fi
88 fi
89}
90
91# Build release notes arrays from a list of commits ($@) and return the release
92# note in an array of array.
93function get_release_notes() {
94 for i in "${RELNOTES_TYPES[@]}"; do
95 eval "RELNOTES_${i}=()"
96 done
97 for i in $@; do
98 extract_release_note $i
99 done
100}
101
Damien Martin-Guillerez8dc5fb22015-08-26 12:42:54 +0000102# fmt behaves differently on *BSD and on GNU/Linux, use fold.
103function wrap_text() {
104 fold -s -w $1 | sed 's/ *$//'
105}
Damien Martin-Guillerez3b61b2c2015-07-27 10:05:43 +0000106
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000107# Returns the list of release notes in arguments into a list of points in
108# a markdown list. The release notes are wrapped to 70 characters so it
109# displays nicely in a git history.
110function format_release_notes() {
111 local i
112 for (( i=1; $i <= $#; i=$i+1 )); do
113 local relnote="${!i}"
Damien Martin-Guillerez3b61b2c2015-07-27 10:05:43 +0000114 local lines=$(echo "$relnote" | wrap_text 66) # wrap to 70 counting the 4 leading spaces.
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000115 echo " - $lines" | head -1
116 echo "$lines" | tail -n +2 | sed 's/^/ /'
117 done
118}
119
120# Create the release notes since commit $1 ($2...${[#]} are the cherry-picks,
121# so the commits to ignore.
122function release_notes() {
123 local i
124 local commits=$(get_release_notes_commits $@)
125 local length="${#RELNOTES_TYPES[@]}"
126 get_release_notes "$commits"
127 for (( i=0; $i < $length; i=$i+1 )); do
128 local relnotes_title="${RELNOTES_DESC[$i]}"
129 local relnotes_type=${RELNOTES_TYPES[$i]}
130 local relnotes="RELNOTES_${relnotes_type}[@]"
131 local nb_relnotes=$(eval "echo \${#$relnotes}")
132 if (( "${nb_relnotes}" > 0 )); then
133 echo "${relnotes_title}:"
134 echo
135 format_release_notes "${!relnotes}"
136 echo
137 fi
138 done
139}
140
141# A wrapper around all the previous function, using the CHANGELOG.md
142# file in $1 to compute the last release commit hash.
143function create_release_notes() {
144 local last_release=$(get_last_release "$1") || \
145 { echo "Initial release."; return 0; }
146 [ -n "${last_release}" ] || { echo "Initial release."; return 0; }
147 release_notes ${last_release}
148}
149
150# Create the revision information given a list of commits. The first
151# commit should be the baseline, and the other one are the cherry-picks.
152# The result is of the form:
153# Baseline: BASELINE_COMMIT
Kristina Chodorowe7be8392016-04-22 20:34:37 +0000154#
155# Cherry picks:
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000156# + CHERRY_PICK1: commit message summary of the CHERRY_PICK1. This
157# message will be wrapped into 70 columns.
158# + CHERRY_PICK2: commit message summary of the CHERRY_PICK2.
159function create_revision_information() {
Damien Martin-Guillerez0052b802015-10-07 14:58:06 +0000160 echo "Baseline: $(git rev-parse --short "${1}")"
Kristina Chodorowe7be8392016-04-22 20:34:37 +0000161 first=1
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000162 shift
163 while [ -n "${1-}" ]; do
Kristina Chodorowe7be8392016-04-22 20:34:37 +0000164 if [[ "$first" -eq 1 ]]; then
165 echo -e "\nCherry picks:"
166 first=0
167 fi
168
Damien Martin-Guillerez0052b802015-10-07 14:58:06 +0000169 local hash="$(git rev-parse --short "${1}")"
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000170 local subject=$(git show -s --pretty=format:%s $hash)
Damien Martin-Guillerez3b61b2c2015-07-27 10:05:43 +0000171 local lines=$(echo "$subject" | wrap_text 56) # 14 leading spaces.
Damien Martin-Guillerezd019eea2015-07-24 12:40:48 +0000172 echo " + $hash: $lines" | head -1
173 echo "$lines" | tail -n +2 | sed 's/^/ /'
174 shift
175 done
176}