blob: 7cd6d0b61e6e85993c7ffb3b6d54f8b480e33feb [file] [log] [blame]
Philipp Wollermanna5afe952016-06-21 14:58:09 +00001#!/bin/bash
Damien Martin-Guillerezfa15d392015-07-28 15:54:40 +00002
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00003# Copyright 2015 The Bazel Authors. All rights reserved.
Damien Martin-Guillerezfa15d392015-07-28 15:54:40 +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-Guillerezfa15d392015-07-28 15:54:40 +000019# Some common method for release scripts
20
21# A release candidate is created from a branch named "release-%name%"
22# where %name% is the name of the release. Once promoted to a release,
23# A tag %name% will be created from this branch and the corresponding
24# branch removed.
25# The last commit of the release branch is always a commit containing
26# the release notes in the commit message and updating the CHANGELOG.md.
27# This last commit will be cherry-picked back in the master branch
28# when the release candidate is promoted to a release.
29# To follow tracks and to support how CI systems fetch the refs, we
30# store two commit notes: the release name and the candidate number.
31
32# Returns the branch name of the current git repository
33function git_get_branch() {
34 git symbolic-ref --short HEAD
35}
36
37# Show the commit message of the ref specified in argument
38function git_commit_msg() {
39 git show -s --pretty=format:%B "$@"
40}
41
42# Extract the release candidate number from the git notes
43function get_release_candidate() {
44 git notes --ref=release-candidate show "$@" 2>/dev/null | xargs echo || true
45}
46
47# Extract the release name from the git notes
48function get_release_name() {
49 git notes --ref=release show "$@" 2>/dev/null | xargs echo || true
50}
51
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +000052# Get the short hash of a commit
Damien Martin-Guillerez930fa9b2017-01-26 22:52:29 +000053function git_commit_hash() {
54 git rev-parse "${1}"
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +000055}
56
57# Get the subject (first line of the commit message) of a commit
58function git_commit_subject() {
59 git show -s --pretty=format:%s "$@"
60}
61
62# Get the list of commit hashes between two revisions
63function git_log_hash() {
64 local baseline="$1"
65 local head="$2"
66 shift 2
67 git log --pretty=format:%H "${baseline}".."${head}" "$@"
68}
69
70# Extract the full release name from the git notes
71function get_full_release_name() {
72 local name="$(get_release_name "$@")"
73 local rc="$(get_release_candidate "$@")"
74 if [ -n "${rc}" ]; then
75 echo "${name}rc${rc}"
76 else
77 echo "${name}"
78 fi
79}
80
Damien Martin-Guillerez0edd3542016-11-22 15:16:38 +000081# Extract the release notes from the git notes
82function get_release_notes() {
83 git notes --ref=release-notes show "$@" 2>/dev/null || true
84}
85
Damien Martin-Guillerezfa15d392015-07-28 15:54:40 +000086# Returns the info from the branch of the release. It is the current branch
87# but it errors out if the current branch is not a release branch. This
88# method returns the tag of the release and the number of the current
89# candidate in this release.
90function get_release_branch() {
91 local branch_name=$(git_get_branch)
92 if [ -z "$(get_release_name)" -o -z "$(get_release_candidate)" ]; then
93 echo "Not a release branch: ${branch_name}." >&2
94 exit 1
95 fi
96 echo "${branch_name}"
97}
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +000098
99# fmt behaves differently on *BSD and on GNU/Linux, use fold.
100function wrap_text() {
101 fold -s -w $1 | sed 's/ *$//'
102}
103
104# Create the revision information given a list of commits. The first
105# commit should be the baseline, and the other ones are the cherry-picks.
106# The result is of the form:
107# Baseline: BASELINE_COMMIT
108#
109# Cherry picks:
110# + CHERRY_PICK1: commit message summary of the CHERRY_PICK1. This
111# message will be wrapped into 70 columns.
112# + CHERRY_PICK2: commit message summary of the CHERRY_PICK2.
113function create_revision_information() {
Damien Martin-Guillerez930fa9b2017-01-26 22:52:29 +0000114 echo "Baseline: $(git_commit_hash "${1}")"
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +0000115 local first=1
116 shift
117 while [ -n "${1-}" ]; do
118 if [[ "$first" -eq 1 ]]; then
119 echo -e "\nCherry picks:"
120 first=0
121 fi
Damien Martin-Guillerez930fa9b2017-01-26 22:52:29 +0000122 local hash="$(git_commit_hash "${1}")"
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +0000123 local subject="$(git_commit_subject $hash)"
Damien Martin-Guillerez930fa9b2017-01-26 22:52:29 +0000124 local lines=$(echo "$subject" | wrap_text 65) # 5 leading spaces.
125 echo " + $hash:"
126 echo "$lines" | sed 's/^/ /'
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +0000127 shift
128 done
129}
130
Damien Martin-Guillerez55c97bc2016-12-20 07:41:59 +0000131# Get the master commit
132# Some machine might not have a "master" branch, use "origin/master" in that case
133function get_master_ref() {
134 git rev-parse --verify master 2>/dev/null || git rev-parse --verify origin/master
135}
136
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +0000137# Get the baseline of master.
138# Args: $1: release branch, default to HEAD
139function get_release_baseline() {
Damien Martin-Guillerez55c97bc2016-12-20 07:41:59 +0000140 git merge-base $(get_master_ref) "${1:-HEAD}"
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +0000141}
142
Damien Martin-Guillerez353ce472017-01-10 14:23:10 +0000143# Returns the list of (commit hash, patch-id) from $1..$2
144# Args:
145# $1: the first commit in the list (excluded)
146# $2: the last commit in the list
147function get_patch_ids() {
148 git_log_hash "$1" "$2" | xargs git show | git patch-id
149}
150
151# Returns the original commit a commit was cherry-picked from master
152# Args:
153# $1: the commit to find
154# $2: the baseline from which to look for (up to master)
155# $3: master ref (optional, default master)
156# $4: The list of master changes as returned by get_patch_ids (optional)
157function get_cherrypick_origin() {
158 local master=${3:-$(get_master_ref)}
159 local master_changes="${4-$(get_patch_ids "${2}" "${master}")}"
160}
161
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +0000162# Get the list of cherry-picks since master
163# Args:
164# $1: branch, default to HEAD
165# $2: baseline change, default to $(get_release_baseline $1)
166function get_cherrypicks() {
167 local branch="${1:-HEAD}"
Damien Martin-Guillerez55c97bc2016-12-20 07:41:59 +0000168 local master=$(get_master_ref)
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +0000169 local baseline="${2:-$(get_release_baseline "${branch}")}"
170 # List of changes since the baseline on the release branch
171 local changes="$(git_log_hash "${baseline}" "${branch}" --reverse)"
172 # List of changes since the baseline on the master branch, and their patch-id
Damien Martin-Guillerez55c97bc2016-12-20 07:41:59 +0000173 local master_changes="$(git_log_hash "${baseline}" "${master}" | xargs git show | git patch-id)"
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +0000174 # Now for each changes on the release branch
175 for i in ${changes}; do
Damien Martin-Guillerez353ce472017-01-10 14:23:10 +0000176 local hash=$(git notes --ref=cherrypick show "$i" 2>/dev/null || true)
177 if [ -z "${hash}" ]; then
178 # Find the change with the same patch-id on the master branch if the note is not present
179 hash=$(echo "${master_changes}" \
180 | grep "^$(git show "$i" | git patch-id | cut -d " " -f 1)" \
181 | cut -d " " -f 2)
182 fi
183 if [ -z "${hash}" ]; then
184 # We don't know which cherry-pick it is coming from, fall back to the new commit hash.
185 echo "$i"
186 else
187 echo "${hash}"
188 fi
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +0000189 done
190}
191
192# Generate the title of the release with the date from the release name ($1).
193function get_release_title() {
194 echo "Release ${1} ($(date +%Y-%m-%d))"
195}
196
197# Generate the release message to be added to the changelog
198# from the release notes for release $1
Damien Martin-Guillerez9f915542017-01-10 15:25:29 +0000199# Args:
200# $1: release name
201# $2: release ref (default HEAD)
202# $3: delimiter around the revision information (default none)
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +0000203function generate_release_message() {
204 local release_name="$1"
205 local branch="${2:-HEAD}"
Damien Martin-Guillerez9f915542017-01-10 15:25:29 +0000206 local delimiter="${3-}"
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +0000207 local baseline="$(get_release_baseline "${branch}")"
208 local cherrypicks="$(get_cherrypicks "${branch}" "${baseline}")"
209
Damien Martin-Guillerez9f915542017-01-10 15:25:29 +0000210 get_release_title "$release_name"
211 echo
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +0000212
Damien Martin-Guillerez9f915542017-01-10 15:25:29 +0000213 if [ -n "${delimiter}" ]; then
214 echo "${delimiter}"
215 fi
216 create_revision_information $baseline $cherrypicks
217 if [ -n "${delimiter}" ]; then
218 echo "${delimiter}"
219 fi
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +0000220
Damien Martin-Guillerez9f915542017-01-10 15:25:29 +0000221 echo
222 get_release_notes "${branch}"
Damien Martin-Guillerez0d352612016-12-02 13:48:14 +0000223}
224
225# Returns the release notes for the CHANGELOG.md taken from either from
226# the notes for a release candidate or from the commit message for a
227# full release.
228function get_full_release_notes() {
229 local release_name="$(get_full_release_name "$@")"
230
231 if [[ "${release_name}" =~ rc[0-9]+$ ]]; then
232 # Release candidate, we need to generate from the notes
233 generate_release_message "${release_name}" "$@"
234 else
235 # Full release, returns the commit message
236 git_commit_msg "$@"
237 fi
238}
Damien Martin-Guillerezb8bc49f2016-12-05 10:47:57 +0000239
240# Merge three release notes using branch $1 as a base
241# Args:
242# $1 the branch name to use to play on
243# $2 the new generated release notes
244# $3 the last generated release notes
245# $4 the last edited release notes
246function merge_release_notes() {
247 local branch_name="$1"
248 local relnotes="$2"
249 local last_relnotes="$3"
250 local last_savedrelnotes="$4"
251 if [ "${last_relnotes}" == "${last_savedrelnotes}" ]; then
252 echo "${relnotes}"
253 else
254 # Merge the three release notes, use git merge for it
255 git checkout -q -b "${branch_name}-merge-notes-1"
256 echo "${last_relnotes}" >.relnotes
257 git add .relnotes
258 git commit -q -m "last_relnotes" --allow-empty
259 echo "${last_savedrelnotes}" >.relnotes
260 git add .relnotes
261 git commit -q -m "last_savedrelnotes" --allow-empty
262 git checkout -q -b "${branch_name}-merge-notes-2" HEAD~
263 echo "${relnotes}" >.relnotes
264 git add .relnotes
265 git commit -q -m "relnotes" --allow-empty
266 git merge -q --no-commit "${branch_name}-merge-notes-1" &>/dev/null || true
267 cat .relnotes
268
269 # Clean-up
270 git merge --abort || true &>/dev/null
271 git checkout -q "${branch_name}"
272 git branch -D ${branch_name}-merge-notes-{1,2} >/dev/null
273 fi
274}