cherry_pick_pull.sh 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. #!/bin/bash
  2. # Copyright 2015 The Kubernetes Authors.
  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. # Checkout a PR from GitHub. (Yes, this is sitting in a Git tree. How
  16. # meta.) Assumes you care about pulls from remote "upstream" and
  17. # checks thems out to a branch named:
  18. # automated-cherry-pick-of-<pr>-<target branch>-<timestamp>
  19. set -o errexit
  20. set -o nounset
  21. set -o pipefail
  22. declare -r KUBE_ROOT="$(dirname "${BASH_SOURCE}")/.."
  23. cd "${KUBE_ROOT}"
  24. declare -r STARTINGBRANCH=$(git symbolic-ref --short HEAD)
  25. declare -r REBASEMAGIC="${KUBE_ROOT}/.git/rebase-apply"
  26. DRY_RUN=${DRY_RUN:-""}
  27. if [[ -z ${GITHUB_USER:-} ]]; then
  28. echo "Please export GITHUB_USER=<your-user> (or GH organization, if that's where your fork lives)"
  29. exit 1
  30. fi
  31. if ! which hub > /dev/null; then
  32. echo "Can't find 'hub' tool in PATH, please install from https://github.com/github/hub"
  33. exit 1
  34. fi
  35. if [[ "$#" -lt 2 ]]; then
  36. echo "${0} <remote branch> <pr-number>...: cherry pick one or more <pr> onto <remote branch> and leave instructions for proposing pull request"
  37. echo
  38. echo " Checks out <remote branch> and handles the cherry-pick of <pr> (possibly multiple) for you."
  39. echo " Examples:"
  40. echo " $0 upstream/release-3.14 12345 # Cherry-picks PR 12345 onto upstream/release-3.14 and proposes that as a PR."
  41. echo " $0 upstream/release-3.14 12345 56789 # Cherry-picks PR 12345, then 56789 and proposes the combination as a single PR."
  42. echo
  43. echo " Set the DRY_RUN environment var to skip git push and creating PR."
  44. echo " This is useful for creating patches to a release branch without making a PR."
  45. echo " When DRY_RUN is set the script will leave you in a branch containing the commits you cherry-picked."
  46. exit 2
  47. fi
  48. if git_status=$(git status --porcelain --untracked=no 2>/dev/null) && [[ -n "${git_status}" ]]; then
  49. echo "!!! Dirty tree. Clean up and try again."
  50. exit 1
  51. fi
  52. if [[ -e "${REBASEMAGIC}" ]]; then
  53. echo "!!! 'git rebase' or 'git am' in progress. Clean up and try again."
  54. exit 1
  55. fi
  56. declare -r BRANCH="$1"
  57. shift 1
  58. declare -r PULLS=( "$@" )
  59. function join { local IFS="$1"; shift; echo "$*"; }
  60. declare -r PULLDASH=$(join - "${PULLS[@]/#/#}") # Generates something like "#12345-#56789"
  61. declare -r PULLSUBJ=$(join " " "${PULLS[@]/#/#}") # Generates something like "#12345 #56789"
  62. echo "+++ Updating remotes..."
  63. git remote update upstream origin
  64. if ! git log -n1 --format=%H "${BRANCH}" >/dev/null 2>&1; then
  65. echo "!!! '${BRANCH}' not found. The second argument should be something like upstream/release-0.21."
  66. echo " (In particular, it needs to be a valid, existing remote branch that I can 'git checkout'.)"
  67. exit 1
  68. fi
  69. declare -r NEWBRANCHREQ="automated-cherry-pick-of-${PULLDASH}" # "Required" portion for tools.
  70. declare -r NEWBRANCH="$(echo "${NEWBRANCHREQ}-${BRANCH}" | sed 's/\//-/g')"
  71. declare -r NEWBRANCHUNIQ="${NEWBRANCH}-$(date +%s)"
  72. echo "+++ Creating local branch ${NEWBRANCHUNIQ}"
  73. cleanbranch=""
  74. prtext=""
  75. gitamcleanup=false
  76. function return_to_kansas {
  77. if [[ "${gitamcleanup}" == "true" ]]; then
  78. echo
  79. echo "+++ Aborting in-progress git am."
  80. git am --abort >/dev/null 2>&1 || true
  81. fi
  82. # return to the starting branch and delete the PR text file
  83. if [[ -z "${DRY_RUN}" ]]; then
  84. echo
  85. echo "+++ Returning you to the ${STARTINGBRANCH} branch and cleaning up."
  86. git checkout -f "${STARTINGBRANCH}" >/dev/null 2>&1 || true
  87. if [[ -n "${cleanbranch}" ]]; then
  88. git branch -D "${cleanbranch}" >/dev/null 2>&1 || true
  89. fi
  90. if [[ -n "${prtext}" ]]; then
  91. rm "${prtext}"
  92. fi
  93. fi
  94. }
  95. trap return_to_kansas EXIT
  96. function make-a-pr() {
  97. local rel="$(basename "${BRANCH}")"
  98. echo
  99. echo "+++ Creating a pull request on GitHub at ${GITHUB_USER}:${NEWBRANCH}"
  100. # This looks like an unnecessary use of a tmpfile, but it avoids
  101. # https://github.com/github/hub/issues/976 Otherwise stdin is stolen
  102. # when we shove the heredoc at hub directly, tickling the ioctl
  103. # crash.
  104. prtext="$(mktemp -t prtext.XXXX)" # cleaned in return_to_kansas
  105. cat >"${prtext}" <<EOF
  106. Automated cherry pick of ${PULLSUBJ}
  107. Cherry pick of ${PULLSUBJ} on ${rel}.
  108. EOF
  109. hub pull-request -F "${prtext}" -h "${GITHUB_USER}:${NEWBRANCH}" -b "kubernetes:${rel}"
  110. }
  111. git checkout -b "${NEWBRANCHUNIQ}" "${BRANCH}"
  112. cleanbranch="${NEWBRANCHUNIQ}"
  113. gitamcleanup=true
  114. for pull in "${PULLS[@]}"; do
  115. echo "+++ Downloading patch to /tmp/${pull}.patch (in case you need to do this again)"
  116. curl -o "/tmp/${pull}.patch" -sSL "http://pr.k8s.io/${pull}.patch"
  117. echo
  118. echo "+++ About to attempt cherry pick of PR. To reattempt:"
  119. echo " $ git am -3 /tmp/${pull}.patch"
  120. echo
  121. git am -3 "/tmp/${pull}.patch" || {
  122. conflicts=false
  123. while unmerged=$(git status --porcelain | grep ^U) && [[ -n ${unmerged} ]] \
  124. || [[ -e "${REBASEMAGIC}" ]]; do
  125. conflicts=true # <-- We should have detected conflicts once
  126. echo
  127. echo "+++ Conflicts detected:"
  128. echo
  129. (git status --porcelain | grep ^U) || echo "!!! None. Did you git am --continue?"
  130. echo
  131. echo "+++ Please resolve the conflicts in another window (and remember to 'git add / git am --continue')"
  132. read -p "+++ Proceed (anything but 'y' aborts the cherry-pick)? [y/n] " -r
  133. echo
  134. if ! [[ "${REPLY}" =~ ^[yY]$ ]]; then
  135. echo "Aborting." >&2
  136. exit 1
  137. fi
  138. done
  139. if [[ "${conflicts}" != "true" ]]; then
  140. echo "!!! git am failed, likely because of an in-progress 'git am' or 'git rebase'"
  141. exit 1
  142. fi
  143. }
  144. done
  145. gitamcleanup=false
  146. if [[ -n "${DRY_RUN}" ]]; then
  147. echo "!!! Skipping git push and PR creation because you set DRY_RUN."
  148. echo "To return to the branch you were in when you invoked this script:"
  149. echo
  150. echo " git checkout ${STARTINGBRANCH}"
  151. echo
  152. echo "To delete this branch:"
  153. echo
  154. echo " git branch -D ${NEWBRANCHUNIQ}"
  155. exit 0
  156. fi
  157. if git remote -v | grep ^origin | grep kubernetes/kubernetes.git; then
  158. echo "!!! You have 'origin' configured as your kubernetes/kubernetes.git"
  159. echo "This isn't normal. Leaving you with push instructions:"
  160. echo
  161. echo "+++ First manually push the branch this script created:"
  162. echo
  163. echo " git push REMOTE ${NEWBRANCHUNIQ}:${NEWBRANCH}"
  164. echo
  165. echo "where REMOTE is your personal fork (maybe 'upstream'? Consider swapping those.)."
  166. echo
  167. make-a-pr
  168. cleanbranch=""
  169. exit 0
  170. fi
  171. echo
  172. echo "+++ I'm about to do the following to push to GitHub (and I'm assuming origin is your personal fork):"
  173. echo
  174. echo " git push origin ${NEWBRANCHUNIQ}:${NEWBRANCH}"
  175. echo
  176. read -p "+++ Proceed (anything but 'y' aborts the cherry-pick)? [y/n] " -r
  177. if ! [[ "${REPLY}" =~ ^[yY]$ ]]; then
  178. echo "Aborting." >&2
  179. exit 1
  180. fi
  181. git push origin -f "${NEWBRANCHUNIQ}:${NEWBRANCH}"
  182. make-a-pr