github.com/ironcore-dev/gardener-extension-provider-ironcore@v0.3.2-0.20240314231816-8336447fb9a0/hack/cherry-pick-pull.sh (about)

     1  #!/usr/bin/env bash
     2  
     3  # Copyright 2015 The Kubernetes Authors.
     4  #
     5  # Licensed under the Apache License, Version 2.0 (the "License");
     6  # you may not use this file except in compliance with the License.
     7  # You may obtain a copy of the License at
     8  #
     9  #     http://www.apache.org/licenses/LICENSE-2.0
    10  #
    11  # Unless required by applicable law or agreed to in writing, software
    12  # distributed under the License is distributed on an "AS IS" BASIS,
    13  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  # See the License for the specific language governing permissions and
    15  # limitations under the License.
    16  
    17  # This file was copied from the kubernetes/kubernetes project
    18  # https://github.com/kubernetes/kubernetes/blob/v1.20.0/hack/cherry_pick_pull.sh
    19  #
    20  # Modifications Copyright 2021 SAP SE or an SAP affiliate company. All rights reserved.
    21  
    22  # Usage Instructions: https://github.com/gardener/gardener/blob/master/docs/development/process.md#cherry-picks
    23  
    24  # Checkout a PR from GitHub. (Yes, this is sitting in a Git tree. How
    25  # meta.) Assumes you care about pulls from remote "upstream" and
    26  # checks them out to a branch named:
    27  #  automated-cherry-pick-of-<pr>-<target branch>-<timestamp>
    28  
    29  set -o errexit
    30  set -o nounset
    31  set -o pipefail
    32  
    33  REPO_ROOT="$(git rev-parse --show-toplevel)"
    34  declare -r REPO_ROOT
    35  cd "${REPO_ROOT}"
    36  
    37  STARTINGBRANCH=$(git symbolic-ref --short HEAD)
    38  declare -r STARTINGBRANCH
    39  declare -r REBASEMAGIC="${REPO_ROOT}/.git/rebase-apply"
    40  DRY_RUN=${DRY_RUN:-""}
    41  UPSTREAM_REMOTE=${UPSTREAM_REMOTE:-upstream}
    42  FORK_REMOTE=${FORK_REMOTE:-origin}
    43  MAIN_REPO_ORG=${MAIN_REPO_ORG:-$(git remote get-url "$UPSTREAM_REMOTE" | awk '{gsub(/http[s]:\/\/|git@/,"")}1' | awk -F'[@:./]' 'NR==1{print $3}')}
    44  MAIN_REPO_NAME=${MAIN_REPO_NAME:-$(git remote get-url "$UPSTREAM_REMOTE" | awk '{gsub(/http[s]:\/\/|git@/,"")}1' | awk -F'[@:./]' 'NR==1{print $4}')}
    45  DEPRECATED_RELEASE_NOTE_CATEGORY="|noteworthy|improvement|action"
    46  RELEASE_NOTE_CATEGORY="(breaking|feature|bugfix|doc|other${DEPRECATED_RELEASE_NOTE_CATEGORY})"
    47  RELEASE_NOTE_TARGET_GROUP="(user|operator|developer|dependency)"
    48  
    49  if [[ -z ${GITHUB_USER:-} ]]; then
    50    echo "Please export GITHUB_USER=<your-user> (or GH organization, if that's where your fork lives)"
    51    exit 1
    52  fi
    53  
    54  if ! which hub > /dev/null; then
    55    echo "Can't find 'hub' tool in PATH, please install from https://github.com/github/hub"
    56    exit 1
    57  fi
    58  
    59  if [[ "$#" -lt 2 ]]; then
    60    echo "${0} <upstream remote>/<remote branch> <pr-number>...: cherry pick one or more <pr> onto <remote branch> and leave instructions for proposing pull request"
    61    echo
    62    echo "  Checks out <upstream remote>/<remote branch> and handles the cherry-pick of <pr> (possibly multiple) for you."
    63    echo "  Examples:"
    64    echo "    $0 ${UPSTREAM_REMOTE}/release-3.14 12345        # Cherry-picks PR 12345 onto ${UPSTREAM_REMOTE}/release-3.14 and proposes that as a PR."
    65    echo "    $0 ${UPSTREAM_REMOTE}/release-3.14 12345 56789  # Cherry-picks PR 12345, then 56789 and proposes the combination as a single PR."
    66    echo
    67    echo "  Set the DRY_RUN environment var to skip git push and creating PR."
    68    echo "  This is useful for creating patches to a release branch without making a PR."
    69    echo "  When DRY_RUN is set the script will leave you in a branch containing the commits you cherry-picked."
    70    echo
    71    echo "  Set UPSTREAM_REMOTE (default: upstream) and FORK_REMOTE (default: origin)"
    72    echo "  to override the default remote names to what you have locally."
    73    exit 2
    74  fi
    75  
    76  if git_status=$(git status --porcelain --untracked=no 2>/dev/null) && [[ -n "${git_status}" ]]; then
    77    echo "!!! Dirty tree. Clean up and try again."
    78    exit 1
    79  fi
    80  
    81  if [[ -e "${REBASEMAGIC}" ]]; then
    82    echo "!!! 'git rebase' or 'git am' in progress. Clean up and try again."
    83    exit 1
    84  fi
    85  
    86  declare -r BRANCH="$1"
    87  shift 1
    88  declare -r PULLS=( "$@" )
    89  
    90  function join { local IFS="$1"; shift; echo "$*"; }
    91  PULLDASH=$(join - "${PULLS[@]/#/#}") # Generates something like "#12345-#56789"
    92  declare -r PULLDASH
    93  PULLSUBJ=$(join " " "${PULLS[@]/#/#}") # Generates something like "#12345 #56789"
    94  declare -r PULLSUBJ
    95  
    96  echo "+++ Updating remotes..."
    97  git remote update "${UPSTREAM_REMOTE}" "${FORK_REMOTE}"
    98  
    99  if ! git log -n1 --format=%H "${BRANCH}" >/dev/null 2>&1; then
   100    echo "!!! '${BRANCH}' not found. The second argument should be something like ${UPSTREAM_REMOTE}/release-0.21."
   101    echo "    (In particular, it needs to be a valid, existing remote branch that I can 'git checkout'.)"
   102    exit 1
   103  fi
   104  
   105  NEWBRANCHREQ="automated-cherry-pick-of-${PULLDASH}" # "Required" portion for tools.
   106  declare -r NEWBRANCHREQ
   107  NEWBRANCH="$(echo "${NEWBRANCHREQ}-${BRANCH}" | sed 's/\//-/g')"
   108  declare -r NEWBRANCH
   109  NEWBRANCHUNIQ="${NEWBRANCH}-$(date +%s)"
   110  declare -r NEWBRANCHUNIQ
   111  echo "+++ Creating local branch ${NEWBRANCHUNIQ}"
   112  
   113  cleanbranch=""
   114  prtext=""
   115  gitamcleanup=false
   116  function return_to_kansas {
   117    if [[ "${gitamcleanup}" == "true" ]]; then
   118      echo
   119      echo "+++ Aborting in-progress git am."
   120      git am --abort >/dev/null 2>&1 || true
   121    fi
   122  
   123    # return to the starting branch and delete the PR text file
   124    if [[ -z "${DRY_RUN}" ]]; then
   125      echo
   126      echo "+++ Returning you to the ${STARTINGBRANCH} branch and cleaning up."
   127      git checkout -f "${STARTINGBRANCH}" >/dev/null 2>&1 || true
   128      if [[ -n "${cleanbranch}" ]]; then
   129        git branch -D "${cleanbranch}" >/dev/null 2>&1 || true
   130      fi
   131      if [[ -n "${prtext}" ]]; then
   132        rm "${prtext}"
   133      fi
   134    fi
   135  }
   136  trap return_to_kansas EXIT
   137  
   138  SUBJECTS=()
   139  RELEASE_NOTES=()
   140  LABELS=()
   141  function make-a-pr() {
   142    local rel
   143    rel="$(basename "${BRANCH}")"
   144    echo
   145    echo "+++ Creating a pull request on GitHub at ${GITHUB_USER}:${NEWBRANCH}"
   146  
   147    # This looks like an unnecessary use of a tmpfile, but it avoids
   148    # https://github.com/github/hub/issues/976 Otherwise stdin is stolen
   149    # when we shove the heredoc at hub directly, tickling the ioctl
   150    # crash.
   151    prtext="$(mktemp -t prtext.XXXX)" # cleaned in return_to_kansas
   152    local numandtitle
   153    numandtitle=$(printf '%s\n' "${SUBJECTS[@]}")
   154    relnotes=$(printf "${RELEASE_NOTES[@]}")
   155    labels=$(printf "${LABELS[@]}")
   156    cat >"${prtext}" <<EOF
   157  [${rel}] Automated cherry pick of ${numandtitle}
   158  
   159  ${labels}
   160  
   161  Cherry pick of ${PULLSUBJ} on ${rel}.
   162  
   163  ${numandtitle}
   164  
   165  **Release Notes:**
   166  ${relnotes}
   167  EOF
   168  
   169    hub pull-request -F "${prtext}" -h "${GITHUB_USER}:${NEWBRANCH}" -b "${MAIN_REPO_ORG}:${rel}"
   170  }
   171  
   172  git checkout -b "${NEWBRANCHUNIQ}" "${BRANCH}"
   173  cleanbranch="${NEWBRANCHUNIQ}"
   174  
   175  gitamcleanup=true
   176  for pull in "${PULLS[@]}"; do
   177    echo "+++ Downloading patch to /tmp/${pull}.patch (in case you need to do this again)"
   178  
   179    curl -o "/tmp/${pull}.patch" -sSL "https://github.com/${MAIN_REPO_ORG}/${MAIN_REPO_NAME}/pull/${pull}.patch"
   180    echo
   181    echo "+++ About to attempt cherry pick of PR. To reattempt:"
   182    echo "  $ git am -3 /tmp/${pull}.patch"
   183    echo
   184    git am -3 "/tmp/${pull}.patch" || {
   185      conflicts=false
   186      while unmerged=$(git status --porcelain | grep ^U) && [[ -n ${unmerged} ]] \
   187        || [[ -e "${REBASEMAGIC}" ]]; do
   188        conflicts=true # <-- We should have detected conflicts once
   189        echo
   190        echo "+++ Conflicts detected:"
   191        echo
   192        (git status --porcelain | grep ^U) || echo "!!! None. Did you git am --continue?"
   193        echo
   194        echo "+++ Please resolve the conflicts in another window (and remember to 'git add / git am --continue')"
   195        read -p "+++ Proceed (anything but 'y' aborts the cherry-pick)? [y/n] " -r
   196        echo
   197        if ! [[ "${REPLY}" =~ ^[yY]$ ]]; then
   198          echo "Aborting." >&2
   199          exit 1
   200        fi
   201      done
   202  
   203      if [[ "${conflicts}" != "true" ]]; then
   204        echo "!!! git am failed, likely because of an in-progress 'git am' or 'git rebase'"
   205        exit 1
   206      fi
   207    }
   208  
   209    # set the subject
   210    pr_info=$(curl "https://api.github.com/repos/${MAIN_REPO_ORG}/${MAIN_REPO_NAME}/pulls/${pull}" -sS)
   211    subject=$(echo ${pr_info} | jq -cr '.title')
   212    SUBJECTS+=("#${pull}: ${subject}")
   213    labels=$(echo ${pr_info} | jq '.labels[].name' -cr | grep -P '^(area|kind)' | sed -e 's|/| |' -e 's|^|/|g')
   214    LABELS+=("${labels}")
   215  
   216    # remove the patch file from /tmp
   217    rm -f "/tmp/${pull}.patch"
   218  
   219    # get the release notes
   220    notes=$(echo ${pr_info} | jq '.body' | grep -Po "\`\`\` *${RELEASE_NOTE_CATEGORY} ${RELEASE_NOTE_TARGET_GROUP}.*?\`\`\`" || true)
   221    RELEASE_NOTES+=("${notes}")
   222  done
   223  gitamcleanup=false
   224  
   225  if [[ -n "${DRY_RUN}" ]]; then
   226    echo "!!! Skipping git push and PR creation because you set DRY_RUN."
   227    echo "To return to the branch you were in when you invoked this script:"
   228    echo
   229    echo "  git checkout ${STARTINGBRANCH}"
   230    echo
   231    echo "To delete this branch:"
   232    echo
   233    echo "  git branch -D ${NEWBRANCHUNIQ}"
   234    exit 0
   235  fi
   236  
   237  if git remote -v | grep ^"${FORK_REMOTE}" | grep "${MAIN_REPO_ORG}/${MAIN_REPO_NAME}.git"; then
   238    echo "!!! You have ${FORK_REMOTE} configured as your ${MAIN_REPO_ORG}/${MAIN_REPO_NAME}.git"
   239    echo "This isn't normal. Leaving you with push instructions:"
   240    echo
   241    echo "+++ First manually push the branch this script created:"
   242    echo
   243    echo "  git push REMOTE ${NEWBRANCHUNIQ}:${NEWBRANCH}"
   244    echo
   245    echo "where REMOTE is your personal fork (maybe ${UPSTREAM_REMOTE}? Consider swapping those.)."
   246    echo "OR consider setting UPSTREAM_REMOTE and FORK_REMOTE to different values."
   247    echo
   248    make-a-pr
   249    cleanbranch=""
   250    exit 0
   251  fi
   252  
   253  echo
   254  echo "+++ I'm about to do the following to push to GitHub (and I'm assuming ${FORK_REMOTE} is your personal fork):"
   255  echo
   256  echo "  git push ${FORK_REMOTE} ${NEWBRANCHUNIQ}:${NEWBRANCH}"
   257  echo
   258  read -p "+++ Proceed (anything but 'y' aborts the cherry-pick)? [y/n] " -r
   259  if ! [[ "${REPLY}" =~ ^[yY]$ ]]; then
   260    echo "Aborting." >&2
   261    exit 1
   262  fi
   263  
   264  git push "${FORK_REMOTE}" -f "${NEWBRANCHUNIQ}:${NEWBRANCH}"
   265  make-a-pr