1#!/bin/bash 2# Copyright 2022 Google LLC 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15################################################################################ 16 17# This script performs a source release on GitHub for a given repo, that is: 18# - Creates a release branch (if it does not yet exist), 19# - Creates a release tag. 20set -eo pipefail 21 22# Parameters and arguments. These will be populated/modified by args parsing. 23 24# Options. 25# Whether to actually create a release. This is false by default and meant to 26# prevent accidental releases. 27DO_RUN_ACTION="false" 28# Commit at which to make the release. If unspecified, the release is made from 29# HEAD. 30COMMIT_HASH= 31# Optional personal access token. 32ACCESS_TOKEN= 33 34# Arguments. 35# Action to be performed. 36ACTION= 37# This must be of the form `MAJOR.MINOR.PATCH`. 38VERSION= 39# Repo name after github.com/tink-crypto/, e.g., tink-cc. 40REPO_NAME= 41 42# Derived variables. 43GITHUB_REPO_URL= 44RELEASE_BRANCH= 45TAG= 46GITHUB_REFS= 47BRANCH_EXISTS="false" 48 49# Constants. 50readonly GITHUB_ORG_URL="github.com/tink-crypto" 51 52usage() { 53 echo "Usage: $0 [-rh] [-c <commit hash>] [-t <access token>] <action> \\" 54 echo " <version> <repository>" 55 echo " <action>: The action to be performed (crete_branch|create_tag)." 56 echo " <version>: The version identifier in MAJOR.MINOR.PATCH format." 57 echo " <repository>: The name of the repository (e.g. \"tink-cc\")." 58 echo " -c: Commit hash to use as HEAD of the release branch (optional)." 59 echo " -t: Access token. Without this, the default is SSH (optional)." 60 echo " -r: Whether to actually create a release; this is false by default." 61 echo " -h: Show this help message." 62 exit 1 63} 64 65process_params() { 66 while getopts "rhc:t:" opt; do 67 case "${opt}" in 68 r) DO_RUN_ACTION="true" ;; 69 c) COMMIT_HASH="${OPTARG}" ;; 70 t) ACCESS_TOKEN="${OPTARG}" ;; 71 *) usage ;; 72 esac 73 done 74 shift $((OPTIND - 1)) 75 readonly DO_RUN_ACTION 76 readonly COMMIT_HASH 77 readonly ACCESS_TOKEN 78 79 ACTION="$1" 80 if [[ ! "${ACTION}" =~ create_branch|create_tag ]]; then 81 echo "ERROR: Expected (create_branch|create_tag) got ${ACTION}" >&2 82 usage 83 fi 84 readonly ACTION 85 86 VERSION="$2" 87 if [[ ! "${VERSION}" =~ ^[0-9]+.[0-9]+.[0-9]+$ ]]; then 88 echo "ERROR: Invalid version format: expected MAJOR.MINOR.PATCH, got \ 89${VERSION}" >&2 90 usage 91 fi 92 readonly VERSION 93 94 REPO_NAME="$3" 95 if [[ -z "${REPO_NAME}" ]]; then 96 echo "ERROR: Repo name must be specified." >&2 97 usage 98 fi 99 readonly REPO_NAME 100 101 # Use SSH by default. 102 local protocol_and_credentials="ssh://git" 103 if [[ -n "${ACCESS_TOKEN}" ]]; then 104 protocol_and_credentials="https://ise-crypto:${ACCESS_TOKEN}" 105 fi 106 readonly protocol_and_credentials 107 GITHUB_REPO_URL="${protocol_and_credentials}@${GITHUB_ORG_URL}/${REPO_NAME}" 108 readonly GITHUB_REPO_URL 109 110 # Release branch is only MAJOR.MINOR. 111 readonly RELEASE_BRANCH="$(echo "${VERSION}" | cut -d'.' -f1,2)" 112 113 # Splitting declaration and assignment guarantees correct propagation of the 114 # exit code of the subshell. 115 local GITHUB_REFS 116 GITHUB_REFS="$(git ls-remote "${GITHUB_REPO_URL}")" 117 readonly GITHUB_REFS 118 119 local -r expected_release_branch="refs/heads/${RELEASE_BRANCH}" 120 if echo "${GITHUB_REFS}" | grep "${expected_release_branch}" > /dev/null; then 121 BRANCH_EXISTS="true" 122 fi 123 readonly BRANCH_EXISTS 124 125 if [[ "${ACTION}" == "create_tag" ]]; then 126 if [[ "${BRANCH_EXISTS}" == "false" ]]; then 127 echo "ERROR: The release branch does not exist in \ 128${GITHUB_ORG_URL}/${REPO_NAME}." >&2 129 return 1 130 fi 131 local -r release_tag="v${VERSION}" 132 local -r expected_release_tag="refs/tags/${release_tag}" 133 if echo "${GITHUB_REFS}" | grep "${expected_release_tag}" > /dev/null; then 134 echo "ERROR The tag \"${release_tag}\" already exists in \ 135${GITHUB_ORG_URL}/${REPO_NAME}." >&2 136 return 1 137 fi 138 139 fi 140} 141 142####################################### 143# Prints a command 144# 145# Args: 146# Command to execute. 147# 148####################################### 149print_command() { 150 printf '%q ' '+' "$@" 151 echo 152} 153 154####################################### 155# Runs a command if DO_RUN_ACTION is true. 156# 157# Args: 158# Command to execute. 159# Globals: 160# DO_RUN_ACTION 161# 162####################################### 163run_command() { 164 if [[ "${DO_RUN_ACTION}" == "false" ]]; then 165 echo " *** Dry run, command not executed. ***" 166 return 0 167 fi 168 # Actually run the command. 169 "$@" 170 return $? 171} 172 173####################################### 174# Prints and runs a command. 175# 176# Args: 177# Command to execute. 178# 179####################################### 180print_and_run_command() { 181 print_command "$@" 182 run_command "$@" 183} 184 185####################################### 186# Creates and checks out to the release branch. 187# 188# If COMMIT_HASH is specified, use COMMIT_HASH as HEAD for the branch. 189# 190# Globals: 191# RELEASE_BRANCH 192# COMMIT_HASH 193# 194####################################### 195git_create_release_branch() { 196 if [[ "${BRANCH_EXISTS}" == "true" ]]; then 197 echo "WARNING: The release branch already exists. Nothing to do." 198 return 0 199 fi 200 # Target branch does not exist so we create the release branch. 201 if [[ -n "${COMMIT_HASH:-}" ]]; then 202 # Use COMMIT_HASH as HEAD for this branch. 203 print_and_run_command git branch "${RELEASE_BRANCH}" "${COMMIT_HASH}" 204 else 205 print_and_run_command git branch "${RELEASE_BRANCH}" 206 fi 207 print_and_run_command git push origin "${RELEASE_BRANCH}" 208} 209 210####################################### 211# Creates a release tag. 212# 213# Globals: 214# RELEASE_BRANCH 215# REPO_NAME 216# VERSION 217# 218####################################### 219git_create_release_tag() { 220 if [[ "${BRANCH_EXISTS}" == "false" ]]; then 221 echo "ERROR: The release branch does not exist in \ 222${GITHUB_ORG_URL}/${REPO_NAME}." >&2 223 return 1 224 fi 225 local -r release_tag="v${VERSION}" 226 local -r expected_release_tag="refs/tags/${release_tag}" 227 if echo "${GITHUB_REFS}" | grep "${expected_release_tag}" > /dev/null; then 228 echo "ERROR The tag \"${release_tag}\" already exists in \ 229${GITHUB_ORG_URL}/${REPO_NAME}." >&2 230 return 1 231 fi 232 print_and_run_command git checkout "${RELEASE_BRANCH}" 233 print_and_run_command git tag -a "${release_tag}" \ 234 -m "${REPO_NAME} version ${VERSION}" 235 print_and_run_command git push origin "${release_tag}" 236} 237 238main() { 239 process_params "$@" 240 # Avoid logging the full URL; replace GIT_URL with a version that omits user 241 # and access token. 242 local -r protocol="$(echo "${GITHUB_REPO_URL}" | cut -d':' -f1)" 243 local -r github_repo="$(echo "${GITHUB_REPO_URL}" | cut -d'@' -f2)" 244 print_command git clone "${protocol}://...@${github_repo}" 245 run_command git clone "${GITHUB_REPO_URL}" 246 print_and_run_command cd "${REPO_NAME}" 247 248 case "${ACTION}" in 249 create_branch) git_create_release_branch ;; 250 create_tag) git_create_release_tag ;; 251 esac 252} 253 254main "$@" 255