2018-04-16 12:31:44 -04:00
#!/usr/bin/env bash
2015-07-05 10:59:01 -04:00
2016-06-02 20:25:58 -04:00
# Copyright 2015 The Kubernetes Authors.
2015-07-05 10:59:01 -04:00
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
2019-05-03 16:17:36 -04:00
# Usage Instructions: https://git.k8s.io/community/contributors/devel/sig-release/cherry-picks.md
2019-03-03 22:20:41 -05:00
2015-07-05 10:59:01 -04:00
# Checkout a PR from GitHub. (Yes, this is sitting in a Git tree. How
# meta.) Assumes you care about pulls from remote "upstream" and
2018-10-09 09:14:17 -04:00
# checks them out to a branch named:
2016-07-29 16:03:20 -04:00
# automated-cherry-pick-of-<pr>-<target branch>-<timestamp>
2015-07-05 10:59:01 -04:00
set -o errexit
set -o nounset
set -o pipefail
2018-10-09 09:14:17 -04:00
REPO_ROOT = " $( git rev-parse --show-toplevel) "
declare -r REPO_ROOT
cd " ${ REPO_ROOT } "
2015-07-05 10:59:01 -04:00
2018-10-09 09:14:17 -04:00
STARTINGBRANCH = $( git symbolic-ref --short HEAD)
declare -r STARTINGBRANCH
declare -r REBASEMAGIC = " ${ REPO_ROOT } /.git/rebase-apply "
2016-07-29 16:03:20 -04:00
DRY_RUN = ${ DRY_RUN :- "" }
2017-06-26 16:46:31 -04:00
REGENERATE_DOCS = ${ REGENERATE_DOCS :- "" }
2016-10-17 15:03:50 -04:00
UPSTREAM_REMOTE = ${ UPSTREAM_REMOTE :- upstream }
FORK_REMOTE = ${ FORK_REMOTE :- origin }
2018-10-15 21:05:23 -04:00
MAIN_REPO_ORG = ${ MAIN_REPO_ORG :- $( git remote get-url " $UPSTREAM_REMOTE " | awk '{gsub(/http[s]:\/\/|git@/,"")}1' | awk -F'[@:./]' 'NR==1{print $3}' ) }
MAIN_REPO_NAME = ${ MAIN_REPO_NAME :- $( git remote get-url " $UPSTREAM_REMOTE " | awk '{gsub(/http[s]:\/\/|git@/,"")}1' | awk -F'[@:./]' 'NR==1{print $4}' ) }
2015-07-05 10:59:01 -04:00
2015-08-04 19:51:13 -04:00
if [ [ -z ${ GITHUB_USER :- } ] ] ; then
2016-07-18 22:47:10 -04:00
echo "Please export GITHUB_USER=<your-user> (or GH organization, if that's where your fork lives)"
2015-08-04 19:51:13 -04:00
exit 1
fi
2021-04-22 10:59:32 -04:00
if ! command -v gh > /dev/null; then
echo "Can't find 'gh' tool in PATH, please install from https://github.com/cli/cli"
2015-08-05 17:09:13 -04:00
exit 1
2015-08-04 19:51:13 -04:00
fi
2015-07-11 20:00:20 -04:00
if [ [ " $# " -lt 2 ] ] ; then
echo " ${ 0 } <remote branch> <pr-number>...: cherry pick one or more <pr> onto <remote branch> and leave instructions for proposing pull request "
2016-07-29 16:03:20 -04:00
echo
2015-07-11 20:00:20 -04:00
echo " Checks out <remote branch> and handles the cherry-pick of <pr> (possibly multiple) for you."
echo " Examples:"
echo " $0 upstream/release-3.14 12345 # Cherry-picks PR 12345 onto upstream/release-3.14 and proposes that as a PR. "
echo " $0 upstream/release-3.14 12345 56789 # Cherry-picks PR 12345, then 56789 and proposes the combination as a single PR. "
2016-07-29 16:03:20 -04:00
echo
echo " Set the DRY_RUN environment var to skip git push and creating PR."
echo " This is useful for creating patches to a release branch without making a PR."
echo " When DRY_RUN is set the script will leave you in a branch containing the commits you cherry-picked."
2016-10-17 15:03:50 -04:00
echo
2017-06-26 16:46:31 -04:00
echo " Set the REGENERATE_DOCS environment var to regenerate documentation for the target branch after picking the specified commits."
echo " This is useful when picking commits containing changes to API documentation."
echo
2019-09-16 12:49:56 -04:00
echo " Set UPSTREAM_REMOTE (default: upstream) and FORK_REMOTE (default: origin)"
echo " to override the default remote names to what you have locally."
echo
echo " For merge process info, see https://git.k8s.io/community/contributors/devel/sig-release/cherry-picks.md"
2015-07-05 10:59:01 -04:00
exit 2
fi
2021-04-22 10:59:32 -04:00
# Checks if you are logged in. Will error/bail if you are not.
gh auth status
2015-07-08 20:09:14 -04:00
if git_status = $( git status --porcelain --untracked= no 2>/dev/null) && [ [ -n " ${ git_status } " ] ] ; then
2015-07-05 10:59:01 -04:00
echo "!!! Dirty tree. Clean up and try again."
exit 1
fi
if [ [ -e " ${ REBASEMAGIC } " ] ] ; then
echo "!!! 'git rebase' or 'git am' in progress. Clean up and try again."
exit 1
fi
2015-07-11 20:00:20 -04:00
declare -r BRANCH = " $1 "
shift 1
declare -r PULLS = ( " $@ " )
function join { local IFS = " $1 " ; shift; echo " $* " ; }
2019-04-20 03:49:34 -04:00
PULLDASH = $( join - " ${ PULLS [@]/#/# } " ) # Generates something like "#12345-#56789"
declare -r PULLDASH
PULLSUBJ = $( join " " " ${ PULLS [@]/#/# } " ) # Generates something like "#12345 #56789"
declare -r PULLSUBJ
2015-07-11 20:00:20 -04:00
2015-07-05 10:59:01 -04:00
echo "+++ Updating remotes..."
2016-10-17 15:03:50 -04:00
git remote update " ${ UPSTREAM_REMOTE } " " ${ FORK_REMOTE } "
2015-07-05 10:59:01 -04:00
2015-07-08 20:50:46 -04:00
if ! git log -n1 --format= %H " ${ BRANCH } " >/dev/null 2>& 1; then
2016-10-17 15:03:50 -04:00
echo " !!! ' ${ BRANCH } ' not found. The second argument should be something like ${ UPSTREAM_REMOTE } /release-0.21. "
2015-07-08 20:50:46 -04:00
echo " (In particular, it needs to be a valid, existing remote branch that I can 'git checkout'.)"
exit 1
fi
2019-04-20 03:49:34 -04:00
NEWBRANCHREQ = " automated-cherry-pick-of- ${ PULLDASH } " # "Required" portion for tools.
declare -r NEWBRANCHREQ
NEWBRANCH = " $( echo " ${ NEWBRANCHREQ } - ${ BRANCH } " | sed 's/\//-/g' ) "
declare -r NEWBRANCH
NEWBRANCHUNIQ = " ${ NEWBRANCH } - $( date +%s) "
declare -r NEWBRANCHUNIQ
2015-07-05 10:59:01 -04:00
echo " +++ Creating local branch ${ NEWBRANCHUNIQ } "
cleanbranch = ""
gitamcleanup = false
function return_to_kansas {
if [ [ " ${ gitamcleanup } " = = "true" ] ] ; then
2016-07-29 16:03:20 -04:00
echo
echo "+++ Aborting in-progress git am."
2015-07-08 20:50:46 -04:00
git am --abort >/dev/null 2>& 1 || true
2015-07-05 10:59:01 -04:00
fi
2016-07-29 16:03:20 -04:00
# return to the starting branch and delete the PR text file
if [ [ -z " ${ DRY_RUN } " ] ] ; then
echo
echo " +++ Returning you to the ${ STARTINGBRANCH } branch and cleaning up. "
git checkout -f " ${ STARTINGBRANCH } " >/dev/null 2>& 1 || true
if [ [ -n " ${ cleanbranch } " ] ] ; then
git branch -D " ${ cleanbranch } " >/dev/null 2>& 1 || true
fi
2015-10-12 16:22:20 -04:00
fi
2015-07-05 10:59:01 -04:00
}
trap return_to_kansas EXIT
2016-10-04 16:45:22 -04:00
SUBJECTS = ( )
2024-08-26 13:58:51 -04:00
RELEASE_NOTES = ( )
2016-07-29 16:03:20 -04:00
function make-a-pr( ) {
2019-04-20 03:49:34 -04:00
local rel
rel = " $( basename " ${ BRANCH } " ) "
2016-07-29 16:03:20 -04:00
echo
echo " +++ Creating a pull request on GitHub at ${ GITHUB_USER } : ${ NEWBRANCH } "
2019-04-20 03:49:34 -04:00
local numandtitle
numandtitle = $( printf '%s\n' " ${ SUBJECTS [@] } " )
2021-04-22 10:59:32 -04:00
prtext = $( cat <<EOF
2016-07-29 16:03:20 -04:00
Cherry pick of ${ PULLSUBJ } on ${ rel } .
2016-10-04 16:45:22 -04:00
2017-11-13 04:15:55 -05:00
${ numandtitle }
2019-09-16 12:49:56 -04:00
For details on the cherry pick process, see the [ cherry pick requests] ( https://git.k8s.io/community/contributors/devel/sig-release/cherry-picks.md) page.
2021-07-28 15:13:23 -04:00
\` \` \` release-note
2024-08-26 13:58:51 -04:00
$( printf '%s\n' " ${ RELEASE_NOTES [@] } " )
2021-07-28 15:13:23 -04:00
\` \` \`
2016-07-29 16:03:20 -04:00
EOF
2021-04-22 10:59:32 -04:00
)
2016-07-29 16:03:20 -04:00
2021-04-22 10:59:32 -04:00
gh pr create --title= " Automated cherry pick of ${ numandtitle } " --body= " ${ prtext } " --head " ${ GITHUB_USER } : ${ NEWBRANCH } " --base " ${ rel } " --repo= " ${ MAIN_REPO_ORG } / ${ MAIN_REPO_NAME } "
2016-07-29 16:03:20 -04:00
}
2015-07-05 10:59:01 -04:00
git checkout -b " ${ NEWBRANCHUNIQ } " " ${ BRANCH } "
cleanbranch = " ${ NEWBRANCHUNIQ } "
gitamcleanup = true
2015-07-11 20:00:20 -04:00
for pull in " ${ PULLS [@] } " ; do
echo " +++ Downloading patch to /tmp/ ${ pull } .patch (in case you need to do this again) "
2018-10-09 09:14:17 -04:00
curl -o " /tmp/ ${ pull } .patch " -sSL " https://github.com/ ${ MAIN_REPO_ORG } / ${ MAIN_REPO_NAME } /pull/ ${ pull } .patch "
2015-07-11 20:00:20 -04:00
echo
echo "+++ About to attempt cherry pick of PR. To reattempt:"
echo " $ git am -3 /tmp/ ${ pull } .patch "
echo
git am -3 " /tmp/ ${ pull } .patch " || {
conflicts = false
while unmerged = $( git status --porcelain | grep ^U) && [ [ -n ${ unmerged } ] ] \
|| [ [ -e " ${ REBASEMAGIC } " ] ] ; do
conflicts = true # <-- We should have detected conflicts once
echo
echo "+++ Conflicts detected:"
echo
( git status --porcelain | grep ^U) || echo "!!! None. Did you git am --continue?"
echo
echo "+++ Please resolve the conflicts in another window (and remember to 'git add / git am --continue')"
2022-06-17 00:36:46 -04:00
read -p "+++ Proceed (anything other than 'y' aborts the cherry-pick)? [y/n] " -r
2015-07-11 20:00:20 -04:00
echo
if ! [ [ " ${ REPLY } " = ~ ^[ yY] $ ] ] ; then
echo "Aborting." >& 2
exit 1
fi
done
if [ [ " ${ conflicts } " != "true" ] ] ; then
echo "!!! git am failed, likely because of an in-progress 'git am' or 'git rebase'"
2015-07-05 10:59:01 -04:00
exit 1
fi
2015-07-11 20:00:20 -04:00
}
2016-12-14 16:24:13 -05:00
2016-10-04 16:45:22 -04:00
# set the subject
2024-08-26 13:58:51 -04:00
subject = $( gh pr view " $pull " --json title --jq '.["title"]' )
2016-10-04 16:45:22 -04:00
SUBJECTS += ( " # ${ pull } : ${ subject } " )
2016-12-14 16:24:13 -05:00
2024-08-26 13:58:51 -04:00
# set the release note
release_note = $( gh pr view " $pull " --json body --jq '.["body"]' | awk '/```release-note/{f=1;next} /```/{f=0} f' )
RELEASE_NOTES += ( " ${ release_note } " )
2016-12-14 16:24:13 -05:00
# remove the patch file from /tmp
rm -f " /tmp/ ${ pull } .patch "
2015-07-11 20:00:20 -04:00
done
2015-07-05 10:59:01 -04:00
gitamcleanup = false
2017-06-05 16:51:32 -04:00
# Re-generate docs (if needed)
2017-06-26 16:46:31 -04:00
if [ [ -n " ${ REGENERATE_DOCS } " ] ] ; then
2017-06-05 16:51:32 -04:00
echo
2017-06-26 16:46:31 -04:00
echo "Regenerating docs..."
if ! hack/generate-docs.sh; then
echo
echo "hack/generate-docs.sh FAILED to complete."
exit 1
fi
2017-06-05 16:51:32 -04:00
fi
2016-07-29 16:03:20 -04:00
if [ [ -n " ${ DRY_RUN } " ] ] ; then
echo "!!! Skipping git push and PR creation because you set DRY_RUN."
echo "To return to the branch you were in when you invoked this script:"
echo
echo " git checkout ${ STARTINGBRANCH } "
echo
echo "To delete this branch:"
echo
echo " git branch -D ${ NEWBRANCHUNIQ } "
exit 0
fi
2015-07-13 19:15:05 -04:00
2019-04-20 03:49:34 -04:00
if git remote -v | grep ^" ${ FORK_REMOTE } " | grep " ${ MAIN_REPO_ORG } / ${ MAIN_REPO_NAME } .git " ; then
2018-10-09 09:14:17 -04:00
echo " !!! You have ${ FORK_REMOTE } configured as your ${ MAIN_REPO_ORG } / ${ MAIN_REPO_NAME } .git "
2015-07-05 10:59:01 -04:00
echo "This isn't normal. Leaving you with push instructions:"
echo
2015-07-13 19:15:05 -04:00
echo "+++ First manually push the branch this script created:"
echo
2015-07-05 10:59:01 -04:00
echo " git push REMOTE ${ NEWBRANCHUNIQ } : ${ NEWBRANCH } "
echo
2016-10-17 15:03:50 -04:00
echo " where REMOTE is your personal fork (maybe ${ UPSTREAM_REMOTE } ? Consider swapping those.). "
echo "OR consider setting UPSTREAM_REMOTE and FORK_REMOTE to different values."
2015-07-13 19:15:05 -04:00
echo
make-a-pr
2015-07-05 10:59:01 -04:00
cleanbranch = ""
exit 0
fi
echo
2016-10-17 15:03:50 -04:00
echo " +++ I'm about to do the following to push to GitHub (and I'm assuming ${ FORK_REMOTE } is your personal fork): "
2015-07-05 10:59:01 -04:00
echo
2016-10-17 15:03:50 -04:00
echo " git push ${ FORK_REMOTE } ${ NEWBRANCHUNIQ } : ${ NEWBRANCH } "
2015-07-05 10:59:01 -04:00
echo
2022-06-17 00:36:46 -04:00
read -p "+++ Proceed (anything other than 'y' aborts the cherry-pick)? [y/n] " -r
2015-07-05 10:59:01 -04:00
if ! [ [ " ${ REPLY } " = ~ ^[ yY] $ ] ] ; then
echo "Aborting." >& 2
exit 1
fi
2016-10-17 15:03:50 -04:00
git push " ${ FORK_REMOTE } " -f " ${ NEWBRANCHUNIQ } : ${ NEWBRANCH } "
2015-07-13 19:15:05 -04:00
make-a-pr