diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml new file mode 100644 index 0000000000..98203f8cbf --- /dev/null +++ b/.github/workflows/backport.yml @@ -0,0 +1,27 @@ +name: Backport +on: + pull_request: + types: [closed] + branches: + - master + +jobs: + backport: + if: ${{ github.event.pull_request.merged == true && contains(join(github.event.pull_request.labels.*.name, ' '), 'process/cherry-pick/') }} + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: write + steps: + - name: checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: cherry-pick + env: + GH_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + ./hack/backport-pr.sh ${{ github.event.pull_request.number }} + diff --git a/hack/backport-pr.sh b/hack/backport-pr.sh new file mode 100755 index 0000000000..a14d3b8b45 --- /dev/null +++ b/hack/backport-pr.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash + +set -u -o pipefail + +log_error() { + if [ -n "${GITHUB_ACTIONS:-}" ]; then + echo "::error::${@}" + return + fi + echo "$@" >&2 +} + +PR="${1}" + +# Some round about stuff to run either locally or in github actions +: "${GITHUB_SERVER_URL:=https://github.com}" +if [ -v 2 ]; then + GITHUB_REPO="${2}" +fi +: "${GITHUB_REPO:=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}}" + +content="$(gh pr view -R "${GITHUB_REPO}" --json title --json labels --json commits "${PR}")" +if [ $? -ne 0 ]; then + log_error "Error getting PR #${PR} to backport" + exit 1 +fi + +labels="$(jq -r '.labels[].name' <<<"${content}" | grep cherry-pick/)" +commits="$(jq -r '.commits[].oid' 2>&1 <<<"${content}")" +title="$(jq -r '.title' <<<"${content}")" + +if [ $? -ne 0 ] || [ -z "${commits}" ]; then + log_error "Error getting commits for PR #${PR} to backport: ${commits}" + exit 1 +fi + +dir="$(mktemp -d)" +remote="cherry_pick_origin_for_${PR}" +cleanup() { + rm -rf "${dir}" + git remote remove "${remote}" +} + +trap cleanup EXIT + +git remote add "${remote}" "${GITHUB_REPO}.git" + +backport() { + ( + branch="${1}" + + new_branch="cherry_pick_pr${PR}_to_${branch}" + trap "cd -; git worktree remove -f ${dir}; git branch -D ${new_branch}" EXIT + git fetch "${remote}" $branch:$branch >/dev/null || return 2 + git worktree add -b "${new_branch}" "${dir}" $branch >/dev/null || return 3 + + cd "${dir}" + for i in ${commits}; do + git cherry-pick -x -s $i >/dev/null || return 3 + done + + git push -f "${remote}" "${new_branch}" >/dev/null || return 4 + gh pr create -R "${GITHUB_REPO}" --base "${branch}" --head "${new_branch}" \ + --title "[${branch}] ${title}" \ + --body "Backport PR #${PR} to $branch" + ) +} + +for label in $labels; do + branch="${label#process/cherry-pick/}" + out="$(backport "${branch}" 2>&1)" + if [ $? -eq 0 ]; then + gh pr comment -R "${GITHUB_REPO}" "${PR}" -b "Backport this change to $branch: ${out}" + continue + fi + log_error "Error cherry-picking PR #${PR} to $branch: ${out}" + gh pr comment -R "${GITHUB_REPO}" "${PR}" -b "Failed to backport this change to $branch: ${out}" + continue +done