1*cc02d7e2SAndroid Build Coastguard Worker#!/bin/bash 2*cc02d7e2SAndroid Build Coastguard Worker#Copyright 2021 The gRPC authors. 3*cc02d7e2SAndroid Build Coastguard Worker# 4*cc02d7e2SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 5*cc02d7e2SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 6*cc02d7e2SAndroid Build Coastguard Worker# You may obtain a copy of the License at 7*cc02d7e2SAndroid Build Coastguard Worker# 8*cc02d7e2SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 9*cc02d7e2SAndroid Build Coastguard Worker# 10*cc02d7e2SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 11*cc02d7e2SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 12*cc02d7e2SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*cc02d7e2SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 14*cc02d7e2SAndroid Build Coastguard Worker# limitations under the License. 15*cc02d7e2SAndroid Build Coastguard Worker 16*cc02d7e2SAndroid Build Coastguard Workerset -euo pipefail 17*cc02d7e2SAndroid Build Coastguard Worker 18*cc02d7e2SAndroid Build Coastguard Workerensure_command () { 19*cc02d7e2SAndroid Build Coastguard Worker if command -v "$1" 1>/dev/null 2>&1; then 20*cc02d7e2SAndroid Build Coastguard Worker return 0 21*cc02d7e2SAndroid Build Coastguard Worker else 22*cc02d7e2SAndroid Build Coastguard Worker echo "$1 is not installed. Please install it to proceed." 1>&2 23*cc02d7e2SAndroid Build Coastguard Worker exit 1 24*cc02d7e2SAndroid Build Coastguard Worker fi 25*cc02d7e2SAndroid Build Coastguard Worker} 26*cc02d7e2SAndroid Build Coastguard Worker 27*cc02d7e2SAndroid Build Coastguard Workerdisplay_usage () { 28*cc02d7e2SAndroid Build Coastguard Worker cat << EOF >/dev/stderr 29*cc02d7e2SAndroid Build Coastguard WorkerUSAGE: $0 PR_ID GITHUB_USER BACKPORT_BRANCHES REVIEWERS [-c PER_BACKPORT_COMMAND] 30*cc02d7e2SAndroid Build Coastguard Worker PR_ID: The ID of the PR to be backported. 31*cc02d7e2SAndroid Build Coastguard Worker GITHUB_USER: Your GitHub username. 32*cc02d7e2SAndroid Build Coastguard Worker BACKPORT_BRANCHES: A space-separated list of branches to which the source PR will be backported. 33*cc02d7e2SAndroid Build Coastguard Worker REVIEWERS: A comma-separated list of users to add as both reviewer and assignee. 34*cc02d7e2SAndroid Build Coastguard Worker PER_BACKPORT_COMMAND : An optional command to run after cherrypicking the PR to the target branch. 35*cc02d7e2SAndroid Build Coastguard Worker If you use this option, ensure your working directory is clean, as "git add -A" will be used to 36*cc02d7e2SAndroid Build Coastguard Worker incorporate any generated files. Try running "git clean -xdff" beforehand. 37*cc02d7e2SAndroid Build Coastguard Worker 38*cc02d7e2SAndroid Build Coastguard WorkerExample: $0 25456 gnossen "v1.30.x v1.31.x v1.32.x v1.33.x v1.34.x v1.35.x v1.36.x" "menghanl,gnossen" 39*cc02d7e2SAndroid Build Coastguard WorkerExample: $0 25493 gnossen "\$(seq 30 33 | xargs -n1 printf 'v1.%s.x ')" "menghanl" -c ./tools/dockerfile/push_testing_images.sh 40*cc02d7e2SAndroid Build Coastguard WorkerEOF 41*cc02d7e2SAndroid Build Coastguard Worker exit 1 42*cc02d7e2SAndroid Build Coastguard Worker} 43*cc02d7e2SAndroid Build Coastguard Worker 44*cc02d7e2SAndroid Build Coastguard Workerensure_command "curl" 45*cc02d7e2SAndroid Build Coastguard Workerensure_command "egrep" 46*cc02d7e2SAndroid Build Coastguard Workerensure_command "hub" 47*cc02d7e2SAndroid Build Coastguard Workerensure_command "jq" 48*cc02d7e2SAndroid Build Coastguard Worker 49*cc02d7e2SAndroid Build Coastguard Workerif [ "$#" -lt "4" ]; then 50*cc02d7e2SAndroid Build Coastguard Worker display_usage 51*cc02d7e2SAndroid Build Coastguard Workerfi 52*cc02d7e2SAndroid Build Coastguard Worker 53*cc02d7e2SAndroid Build Coastguard WorkerPR_ID="$1" 54*cc02d7e2SAndroid Build Coastguard WorkerGITHUB_USER="$2" 55*cc02d7e2SAndroid Build Coastguard WorkerBACKPORT_BRANCHES="$3" 56*cc02d7e2SAndroid Build Coastguard WorkerREVIEWERS="$4" 57*cc02d7e2SAndroid Build Coastguard Workershift 4 58*cc02d7e2SAndroid Build Coastguard Worker 59*cc02d7e2SAndroid Build Coastguard WorkerPER_BACKPORT_COMMAND="" 60*cc02d7e2SAndroid Build Coastguard Workerwhile getopts "c:" OPT; do 61*cc02d7e2SAndroid Build Coastguard Worker case "$OPT" in 62*cc02d7e2SAndroid Build Coastguard Worker c ) 63*cc02d7e2SAndroid Build Coastguard Worker PER_BACKPORT_COMMAND="$OPTARG" 64*cc02d7e2SAndroid Build Coastguard Worker ;; 65*cc02d7e2SAndroid Build Coastguard Worker \? ) 66*cc02d7e2SAndroid Build Coastguard Worker echo "Invalid option: $OPTARG" >/dev/stderr 67*cc02d7e2SAndroid Build Coastguard Worker display_usage 68*cc02d7e2SAndroid Build Coastguard Worker ;; 69*cc02d7e2SAndroid Build Coastguard Worker : ) 70*cc02d7e2SAndroid Build Coastguard Worker echo "Invalid option: $OPTARG requires an argument." >/dev/stderr 71*cc02d7e2SAndroid Build Coastguard Worker display_usage 72*cc02d7e2SAndroid Build Coastguard Worker ;; 73*cc02d7e2SAndroid Build Coastguard Worker esac 74*cc02d7e2SAndroid Build Coastguard Workerdone 75*cc02d7e2SAndroid Build Coastguard Worker 76*cc02d7e2SAndroid Build Coastguard Workerif [[ ! -z "$(git status --porcelain)" && ! -z "$PER_BACKPORT_COMMAND" ]]; then 77*cc02d7e2SAndroid Build Coastguard Worker echo "Your working directory is not clean. Try running `git clean -xdff`. Warning: This is irreversible." > /dev/stderr 78*cc02d7e2SAndroid Build Coastguard Worker exit 1 79*cc02d7e2SAndroid Build Coastguard Workerfi 80*cc02d7e2SAndroid Build Coastguard Worker 81*cc02d7e2SAndroid Build Coastguard Workerif [ -z "$GITHUB_TOKEN" ]; then 82*cc02d7e2SAndroid Build Coastguard Worker echo "A GitHub token is required to run this script. See " \ 83*cc02d7e2SAndroid Build Coastguard Worker "https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token" \ 84*cc02d7e2SAndroid Build Coastguard Worker " for more information" >/dev/stderr 85*cc02d7e2SAndroid Build Coastguard Worker exit 1 86*cc02d7e2SAndroid Build Coastguard Workerfi 87*cc02d7e2SAndroid Build Coastguard Worker 88*cc02d7e2SAndroid Build Coastguard Workerecho "This script will create a collection of backport PRs. You will probably " \ 89*cc02d7e2SAndroid Build Coastguard Worker "have to touch your gnubby a frustrating number of times. C'est la vie." 90*cc02d7e2SAndroid Build Coastguard Workerprintf "Press any key to continue." 91*cc02d7e2SAndroid Build Coastguard Workerread -r RESPONSE </dev/tty 92*cc02d7e2SAndroid Build Coastguard Workerprintf "\n" 93*cc02d7e2SAndroid Build Coastguard Worker 94*cc02d7e2SAndroid Build Coastguard Worker 95*cc02d7e2SAndroid Build Coastguard WorkerPR_DATA=$(curl -s -u "$GITHUB_USER:$GITHUB_TOKEN" \ 96*cc02d7e2SAndroid Build Coastguard Worker -H "Accept: application/vnd.github.v3+json" \ 97*cc02d7e2SAndroid Build Coastguard Worker "https://api.github.com/repos/grpc/grpc/pulls/$PR_ID") 98*cc02d7e2SAndroid Build Coastguard Worker 99*cc02d7e2SAndroid Build Coastguard WorkerSTATE=$(echo "$PR_DATA" | jq -r '.state') 100*cc02d7e2SAndroid Build Coastguard Workerif [ "$STATE" != "open" ]; then 101*cc02d7e2SAndroid Build Coastguard Worker TARGET_COMMITS=$(echo "$PR_DATA" | jq -r '.merge_commit_sha') 102*cc02d7e2SAndroid Build Coastguard Worker FETCH_HEAD_REF=$(echo "$PR_DATA" | jq -r '.base.ref') 103*cc02d7e2SAndroid Build Coastguard Worker SOURCE_REPO=$(echo "$PR_DATA" | jq -r '.base.repo.full_name') 104*cc02d7e2SAndroid Build Coastguard Workerelse 105*cc02d7e2SAndroid Build Coastguard Worker COMMITS_URL=$(echo "$PR_DATA" | jq -r '.commits_url') 106*cc02d7e2SAndroid Build Coastguard Worker COMMITS_DATA=$(curl -s -u "$GITHUB_USER:$GITHUB_TOKEN" \ 107*cc02d7e2SAndroid Build Coastguard Worker -H "Accept: application/vnd.github.v3+json" \ 108*cc02d7e2SAndroid Build Coastguard Worker "$COMMITS_URL") 109*cc02d7e2SAndroid Build Coastguard Worker TARGET_COMMITS=$(echo "$COMMITS_DATA" | jq -r '. | map(.sha) | join(" ")') 110*cc02d7e2SAndroid Build Coastguard Worker FETCH_HEAD_REF=$(echo "$PR_DATA" | jq -r '.head.sha') 111*cc02d7e2SAndroid Build Coastguard Worker SOURCE_REPO=$(echo "$PR_DATA" | jq -r '.head.repo.full_name') 112*cc02d7e2SAndroid Build Coastguard Workerfi 113*cc02d7e2SAndroid Build Coastguard WorkerPR_TITLE=$(echo "$PR_DATA" | jq -r '.title') 114*cc02d7e2SAndroid Build Coastguard WorkerPR_DESCRIPTION=$(echo "$PR_DATA" | jq -r '.body') 115*cc02d7e2SAndroid Build Coastguard WorkerLABELS=$(echo "$PR_DATA" | jq -r '.labels | map(.name) | join(",")') 116*cc02d7e2SAndroid Build Coastguard Worker 117*cc02d7e2SAndroid Build Coastguard Workerset -x 118*cc02d7e2SAndroid Build Coastguard Worker 119*cc02d7e2SAndroid Build Coastguard Workergit fetch "[email protected]:$SOURCE_REPO.git" "$FETCH_HEAD_REF" 120*cc02d7e2SAndroid Build Coastguard Worker 121*cc02d7e2SAndroid Build Coastguard WorkerBACKPORT_PRS="" 122*cc02d7e2SAndroid Build Coastguard Workerfor BACKPORT_BRANCH in $BACKPORT_BRANCHES; do 123*cc02d7e2SAndroid Build Coastguard Worker echo "Backporting $TARGET_COMMITS to $BACKPORT_BRANCH." 124*cc02d7e2SAndroid Build Coastguard Worker 125*cc02d7e2SAndroid Build Coastguard Worker git checkout "origin/$BACKPORT_BRANCH" 126*cc02d7e2SAndroid Build Coastguard Worker 127*cc02d7e2SAndroid Build Coastguard Worker BRANCH_NAME="backport_${PR_ID}_to_${BACKPORT_BRANCH}" 128*cc02d7e2SAndroid Build Coastguard Worker 129*cc02d7e2SAndroid Build Coastguard Worker # To make the script idempotent. 130*cc02d7e2SAndroid Build Coastguard Worker git branch -D "$BRANCH_NAME" || true 131*cc02d7e2SAndroid Build Coastguard Worker git checkout "$BACKPORT_BRANCH" 132*cc02d7e2SAndroid Build Coastguard Worker git checkout -b "$BRANCH_NAME" 133*cc02d7e2SAndroid Build Coastguard Worker 134*cc02d7e2SAndroid Build Coastguard Worker for TARGET_COMMIT in $TARGET_COMMITS; do 135*cc02d7e2SAndroid Build Coastguard Worker git cherry-pick -m 1 "$TARGET_COMMIT" 136*cc02d7e2SAndroid Build Coastguard Worker done 137*cc02d7e2SAndroid Build Coastguard Worker 138*cc02d7e2SAndroid Build Coastguard Worker if [[ ! -z "$PER_BACKPORT_COMMAND" ]]; then 139*cc02d7e2SAndroid Build Coastguard Worker git submodule update --init --recursive 140*cc02d7e2SAndroid Build Coastguard Worker 141*cc02d7e2SAndroid Build Coastguard Worker # To remove dangling submodules. 142*cc02d7e2SAndroid Build Coastguard Worker git clean -xdff 143*cc02d7e2SAndroid Build Coastguard Worker eval "$PER_BACKPORT_COMMAND" 144*cc02d7e2SAndroid Build Coastguard Worker git add -A 145*cc02d7e2SAndroid Build Coastguard Worker git commit --amend --no-edit 146*cc02d7e2SAndroid Build Coastguard Worker fi 147*cc02d7e2SAndroid Build Coastguard Worker 148*cc02d7e2SAndroid Build Coastguard Worker BACKPORT_PR=$(hub pull-request -p -m "[Backport] $PR_TITLE" \ 149*cc02d7e2SAndroid Build Coastguard Worker -m "*Beep boop. This is an automatically generated backport of #${PR_ID}.*" \ 150*cc02d7e2SAndroid Build Coastguard Worker -m "$PR_DESCRIPTION" \ 151*cc02d7e2SAndroid Build Coastguard Worker -l "$LABELS" \ 152*cc02d7e2SAndroid Build Coastguard Worker -b "$GITHUB_USER:$BACKPORT_BRANCH" \ 153*cc02d7e2SAndroid Build Coastguard Worker -r "$REVIEWERS" \ 154*cc02d7e2SAndroid Build Coastguard Worker -a "$REVIEWERS" | tail -n 1) 155*cc02d7e2SAndroid Build Coastguard Worker BACKPORT_PRS+="$BACKPORT_PR\n" 156*cc02d7e2SAndroid Build Coastguard Worker 157*cc02d7e2SAndroid Build Coastguard Worker # TODO: Turn on automerge once the Github API allows it. 158*cc02d7e2SAndroid Build Coastguard Workerdone 159*cc02d7e2SAndroid Build Coastguard Worker 160*cc02d7e2SAndroid Build Coastguard Workerprintf "Your backport PRs have been created:\n$BACKPORT_PRS" 161