diff options
Diffstat (limited to 'scripts/release/release.sh')
-rwxr-xr-x | scripts/release/release.sh | 264 |
1 files changed, 146 insertions, 118 deletions
diff --git a/scripts/release/release.sh b/scripts/release/release.sh index 3b18fd80c2..1e8cbfeb90 100755 --- a/scripts/release/release.sh +++ b/scripts/release/release.sh @@ -18,42 +18,73 @@ set -eu # Generate the release branches and handle the release tags. -# Repositories to push the release branch and the release tag. -: ${RELEASE_REPOSITORIES:="git@github.com:bazelbuild/bazel"} +# Name of the default editor. +: ${EDITOR=vi} -# Repositories to push the master branch -: ${MASTER_REPOSITORIES:="https://bazel.googlesource.com/bazel"} +# Repositories to push the release branch and the release tag. +RELEASE_REPOSITORY="git@github.com:bazelbuild/bazel" -# Name of the default editor -: ${EDITOR=vi} +# Repositories to push the master branch. +MASTER_REPOSITORY="https://bazel.googlesource.com/bazel" -# Author of the release commits -: ${RELEASE_AUTHOR="Bazel Release System <noreply@google.com>"} +# Author of the release commits. +RELEASE_AUTHOR="Bazel Release System <noreply@google.com>" -# Load relnotes.sh +# Load relnotes.sh. SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) source ${SCRIPT_DIR}/relnotes.sh -# Load common.sh +# Load common.sh. source ${SCRIPT_DIR}/common.sh -# Editing release notes info for the user +# Editing release notes info for the user. RELEASE_NOTE_MESSAGE='# Editing release notes # Modify the release notes to make them suitable for the release. # Every line starting with a # will be removed as well as every # empty line at the start and at the end. ' -# Fetch everything from remote repositories to avoid conflicts -function fetch() { - for i in ${RELEASE_REPOSITORIES}; do - git fetch -f $i &>/dev/null || true - git fetch -f $i refs/notes/*:refs/notes/* &>/dev/null || true - done +# Merge three release notes using branch $1 as a base. +# Args: +# $1 the branch name to use to play on +# $2 the new generated release notes +# $3 the last generated release notes +# $4 the last edited release notes +function __merge_release_notes() { + local branch_name="$1" + local relnotes="$2" + local last_relnotes="$3" + local last_savedrelnotes="$4" + if [ "${last_relnotes}" == "${last_savedrelnotes}" ]; then + echo "${relnotes}" + else + # Merge the three release notes using "git merge". + git checkout -q -b "${branch_name}-merge-notes-1" + echo "${last_relnotes}" >.relnotes + git add .relnotes + git commit -q -m "last_relnotes" --allow-empty + + echo "${last_savedrelnotes}" >.relnotes + git add .relnotes + git commit -q -m "last_savedrelnotes" --allow-empty + git checkout -q -b "${branch_name}-merge-notes-2" HEAD~ + + echo "${relnotes}" >.relnotes + git add .relnotes + git commit -q -m "relnotes" --allow-empty + + git merge -q --no-commit "${branch_name}-merge-notes-1" &>/dev/null || true + cat .relnotes + + # Clean-up + git merge --abort || true &>/dev/null + git checkout -q "${branch_name}" + git branch -D ${branch_name}-merge-notes-{1,2} >/dev/null + fi } -# Set the release name $1 (and eventually the candidate number $2). -function set_release_name() { +# Set the release name to $1 (and eventually the candidate number to $2). +function __set_release_name() { git notes --ref=release remove 2>/dev/null || true git notes --ref=release-candidate remove 2>/dev/null || true git notes --ref=release append -m "$1" @@ -63,63 +94,59 @@ function set_release_name() { } # Trim empty lines at the beginning and the end of the buffer. -function trim_empty_lines() { - local f="$(echo $'\f')" # linefeed because OSX sed does not support \f +function __trim_empty_lines() { # Replace all new line by a linefeed, then using sed, remove the leading # and trailing linefeeds and convert them back to newline - tr '\n' '\f' | sed -e "s/^$f*//" -e "s/$f*$//" | tr '\f' '\n' + tr '\n' '\f' | sed -e "s/^\f*//" -e "s/\f*$//" | tr '\f' '\n' } -# Launch the editor and return the edited release notes -function release_note_editor() { +# Launch the editor and return the edited release notes. +function __release_note_editor() { local tmpfile="$1" local branch_name="${2-}" + $EDITOR ${tmpfile} || { echo "Editor failed, cancelling release creation..." >&2 return 1 } - # Stripping the release notes - local relnotes="$(cat ${tmpfile} | grep -v '^#' | trim_empty_lines)" + + # Strip the release notes. + local relnotes="$(cat ${tmpfile} | grep -v '^#' | __trim_empty_lines)" if [ -z "${relnotes}" ]; then echo "Release notes are empty, cancelling release creation..." >&2 return 1 fi - echo "${relnotes}" >${tmpfile} + + echo "${relnotes}" > "${tmpfile}" } # Create the release commit by changing the CHANGELOG file -function create_release_commit() { +function __create_release_commit() { local infos=$(generate_release_message "${1}" HEAD '```') local changelog_path="$PWD/CHANGELOG.md" - local master=$(get_master_ref) - # Get the changelog from master to avoid missing release notes - # from release that were in-between - git checkout -q ${master} CHANGELOG.md || true + # Get the CHANGELOG.md from master to avoid missing release notes from release + # that were in-between. + git checkout master CHANGELOG.md # CHANGELOG.md - local tmpfile="$(mktemp ${TMPDIR:-/tmp}/relnotes-XXXXXXXX)" - trap "rm -f ${tmpfile}" EXIT - echo -n "## ${infos}" >${tmpfile} - if [ -f "${changelog_path}" ]; then - { - echo - echo - cat "${changelog_path}" - echo - } >> ${tmpfile} - fi - cat "${tmpfile}" > ${changelog_path} - git add ${changelog_path} - rm -f "${tmpfile}" - trap - EXIT + local tmpfile="$(mktemp --tmpdir relnotes-XXXXXXXX)" + { + echo -n "## ${infos}" + echo + echo + cat "${changelog_path}" + echo + } >> ${tmpfile} + mv "${tmpfile}" "${changelog_path}" + git add "${changelog_path}" # Commit infos="$(echo "${infos}" | grep -Ev '^```$')" git commit --no-verify -m "${infos}" --no-edit --author "${RELEASE_AUTHOR}" } -function apply_cherry_picks() { +function __apply_cherry_picks() { echo "Applying cherry-picks" # Apply cherry-picks for commit in "$@"; do @@ -142,10 +169,9 @@ function apply_cherry_picks() { return 0 } -# Find out the last release since the fork between "${1:-HEAD}" from master. -function find_last_release() { - local branch="${1:-HEAD}" - local baseline="${2:-$(get_release_baseline "${branch}")}" +# Find out the last release since the fork between HEAD from master. +function __find_last_release() { + local baseline="${1:-$(get_release_baseline "HEAD")}" local changes="$(git log --pretty=format:%H "${baseline}~".."${branch}")" for change in ${changes}; do if git notes --ref=release show ${change} &>/dev/null; then @@ -157,7 +183,7 @@ function find_last_release() { # Execute the create command: # Create a new release named "$1" with "$2" as the baseline commit. -function create_release() { +function __create_release() { local force_rc= if [[ "$1" =~ ^--force_rc=([0-9]*)$ ]]; then force_rc=${BASH_REMATCH[1]} @@ -166,23 +192,24 @@ function create_release() { local release_name="$1" local baseline="$2" shift 2 - local origin_branch=$(git_get_branch) local branch_name="release-${release_name}" - fetch + # Fetch everything from remote repositories to avoid conflicts + git fetch -f "${RELEASE_REPOSITORY}" + git fetch -f "${RELEASE_REPOSITORY}" 'refs/notes/*:refs/notes/*' local last_release="$(git rev-parse --verify "${branch_name}" 2>/dev/null || true)" echo "Creating new release branch ${branch_name} for release ${release_name}" git checkout -B ${branch_name} ${baseline} - apply_cherry_picks $@ || { - git checkout ${origin_branch} + __apply_cherry_picks $@ || { + git checkout master git branch -D ${branch_name} exit 1 } - setup_git_notes "${force_rc}" "${release_name}" "${last_release}" || { - git checkout ${origin_branch} + __setup_git_notes "${force_rc}" "${release_name}" "${last_release}" || { + git checkout master git branch -D ${branch_name} exit 1 } @@ -203,16 +230,16 @@ function create_release() { # last release candidate or the branch name, e.g. if the branch # name is release-v1, then the name will be 'v1'). # $3: (optional) Specify the commit for last release. -function setup_git_notes() { +function __setup_git_notes() { local force_rc="$1" local branch_name="$(git_get_branch)" # Figure out where we are in release: find the rc, the baseline, cherrypicks # and release name. local rc=${force_rc:-1} - local baseline="$(get_release_baseline)" + local baseline="$(get_release_baseline "HEAD")" local cherrypicks="$(get_cherrypicks "HEAD" "${baseline}")" - local last_release="${3-$(find_last_release "HEAD" "${baseline}")}" + local last_release="${3-$(__find_last_release "${baseline}")}" local release_name="${2-}" if [ -n "${last_release}" ]; then if [ -z "${force_rc}" ]; then @@ -232,13 +259,14 @@ function setup_git_notes() { fi # Edit the release notes - local tmpfile=$(mktemp ${TMPDIR:-/tmp}/relnotes-XXXXXXXX) + local tmpfile=$(mktemp --tmpdir relnotes-XXXXXXXX) trap "rm -f ${tmpfile}" EXIT echo "Creating release notes" # Save the changelog so we compute the relnotes against HEAD. - git show master:CHANGELOG.md >${tmpfile} 2>/dev/null || echo >${tmpfile} + git show master:CHANGELOG.md > "${tmpfile}" + # Compute the new release notes local relnotes="$(create_release_notes "${tmpfile}" "${baseline}" ${cherrypicks})" @@ -250,66 +278,63 @@ function setup_git_notes() { local last_relnotes="$(create_release_notes "${tmpfile}")" git checkout -q "${branch_name}" local last_savedrelnotes="$(get_release_notes "${last_release}")" - relnotes="$(merge_release_notes "${branch_name}" "${relnotes}" \ + relnotes="$(__merge_release_notes "${branch_name}" "${relnotes}" \ "${last_relnotes}" "${last_savedrelnotes}")" fi - echo "${RELEASE_NOTE_MESSAGE}" > ${tmpfile} - echo "# $(get_release_title "${release_name}rc${rc}")" >> ${tmpfile} - echo >> ${tmpfile} - echo "${relnotes}" >>"${tmpfile}" - release_note_editor ${tmpfile} "${branch_name}" || return 1 + + echo "${RELEASE_NOTE_MESSAGE}" > "${tmpfile}" + echo "# $(get_release_title "${release_name}rc${rc}")" >> "${tmpfile}" + echo >> "${tmpfile}" + echo "${relnotes}" >> "${tmpfile}" + + __release_note_editor "${tmpfile}" "${branch_name}" || return 1 relnotes="$(cat ${tmpfile})" - # Add the git notes - set_release_name "${release_name}" "${rc}" + # Add the git notes. + git notes --ref=release add -f -m "${release_name}" + git notes --ref=release-candidate add -f -m "${rc}" git notes --ref=release-notes add -f -m "${relnotes}" - # Clean-up - rm -f ${tmpfile} + # Clean-up. + rm -f "${tmpfile}" trap - EXIT } # Force push a ref $2 to repo $1 if exists -function push_if_exists() { +function __push_if_exists() { if git show-ref -q "${2}"; then git push -f "${1}" "+${2}" fi } # Push release notes refs but also a given ref -function push_notes_and_ref() { +function __push_notes_and_ref() { local ref="$1" - for repo in ${RELEASE_REPOSITORIES}; do - push_if_exists "${repo}" "${ref}" - push_if_exists "${repo}" "refs/notes/release" - push_if_exists "${repo}" "refs/notes/release-candidate" - push_if_exists "${repo}" "refs/notes/release-notes" - push_if_exists "${repo}" "refs/notes/cherrypick" - done + __push_if_exists "${RELEASE_REPOSITORY}" "${ref}" + __push_if_exists "${RELEASE_REPOSITORY}" "refs/notes/release" + __push_if_exists "${RELEASE_REPOSITORY}" "refs/notes/release-candidate" + __push_if_exists "${RELEASE_REPOSITORY}" "refs/notes/release-notes" + __push_if_exists "${RELEASE_REPOSITORY}" "refs/notes/cherrypick" } # Push the release branch to the release repositories so a release # candidate can be created. -function push_release_candidate() { - push_notes_and_ref "$(get_release_branch)" +function __push_release_candidate() { + __push_notes_and_ref "$(get_release_branch)" } # Deletes the release branch after a release or abandoning the release -function cleanup_branches() { +function __cleanup_branches() { local tag_name=$1 - local i echo "Destroying the release branches for release ${tag_name}" - # Destroy branch, ignoring if it doesn't exist. - git branch -D release-${tag_name} &>/dev/null || true - for i in $RELEASE_REPOSITORIES; do - git push -f $i :release-${tag_name} &>/dev/null || true - done + git branch -D "release-${tag_name}" &>/dev/null || true + git push -f "${RELEASE_REPOSITORY}" ":release-${tag_name}" &>/dev/null || true } # Releases the current release branch, creating the necessary tag, # destroying the release branch, updating the master's CHANGELOG.md # and pushing everything to GitHub. -function do_release() { +function __do_release() { local branch=$(get_release_branch) local tag_name=$(get_release_name) @@ -317,52 +342,55 @@ function do_release() { read answer if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then echo "Creating the release commit" - create_release_commit "${tag_name}" - set_release_name "${tag_name}" + __create_release_commit "${tag_name}" + __set_release_name "${tag_name}" + git notes --ref=release add -f -m "${tag_name}" + git notes --ref=release-candidate remove || true + echo "Creating the tag" git tag ${tag_name} echo "Cherry-picking CHANGELOG.md modification into master" git checkout master + # Ensuring we are up to date for master - git pull --rebase $(echo "$MASTER_REPOSITORIES" | cut -d " " -f 1) master + git pull --rebase "$MASTER_REPOSITORY" master + # We do not cherry-pick because we might have conflict if the baseline # does not contains the latest CHANGELOG.md file, so trick it. local changelog_path="$PWD/CHANGELOG.md" - git show ${branch}:CHANGELOG.md >${changelog_path} - local tmpfile=$(mktemp ${TMPDIR:-/tmp}/relnotes-XXXXXXXX) + git show "${branch}:CHANGELOG.md" > "${changelog_path}" + local tmpfile=$(mktemp --tmpdir relnotes-XXXXXXXX) trap 'rm -f ${tmpfile}' EXIT - git_commit_msg ${branch} >${tmpfile} - git add ${changelog_path} - git commit --no-verify -F ${tmpfile} --no-edit --author "${RELEASE_AUTHOR}" - rm -f ${tmpfile} + git_commit_msg "${branch}" > "${tmpfile}" + git add "${changelog_path}" + git commit --no-verify -F "${tmpfile}" --no-edit --author "${RELEASE_AUTHOR}" + rm -f "${tmpfile}" trap - EXIT echo "Pushing the change to remote repositories" - for i in $MASTER_REPOSITORIES; do - git push $i +master - done - push_notes_and_ref "refs/tags/${tag_name}" - cleanup_branches ${tag_name} + git push "${MASTER_REPOSITORY}" +master + __push_notes_and_ref "refs/tags/${tag_name}" + __cleanup_branches "${tag_name}" fi } # Abandon the current release, deleting the branch on the local # repository and on GitHub, discarding all changes -function abandon_release() { +function __abandon_release() { local branch_info=$(get_release_branch) local tag_name=$(get_release_name) echo -n "You are about to abandon release ${tag_name}, confirm? [y/N] " read answer if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then - git notes --ref=release remove 2>/dev/null || true - git notes --ref=release-candidate remove 2>/dev/null || true + git notes --ref=release remove || true + git notes --ref=release-candidate remove || true git checkout -q master >/dev/null - cleanup_branches ${tag_name} + __cleanup_branches ${tag_name} fi } -function usage() { +function __usage() { cat >&2 <<EOF Usage: $1 command [arguments] Available commands are: @@ -418,13 +446,13 @@ shift || usage $progname case $cmd in create) (( $# >= 2 )) || usage $progname - create_release "$@" + __create_release "$@" ;; push) - push_release_candidate + __push_release_candidate ;; release) - do_release + __do_release ;; generate-rc) force_rc= @@ -432,12 +460,12 @@ case $cmd in force_rc=${BASH_REMATCH[1]} shift 1 fi - setup_git_notes "${force_rc}" + __setup_git_notes "${force_rc}" ;; abandon) - abandon_release + __abandon_release ;; *) - usage $progname + __usage $progname ;; esac |