From 239558b6243f4070d9f5096c82f019a34012e93c Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Thu, 3 Jun 2021 17:50:43 -0400 Subject: [PATCH] Automatic cherry-pick script (#339) * Automatic cherry-pick script * switch from alamb to apache * autopep8 * flake8 * add rat * tweaks * Add some docs to the README --- dev/release/README.md | 28 +++++++ dev/release/cherry-pick-pr.py | 147 ++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100755 dev/release/cherry-pick-pr.py diff --git a/dev/release/README.md b/dev/release/README.md index 9581a6314669..62f00638d031 100644 --- a/dev/release/README.md +++ b/dev/release/README.md @@ -234,3 +234,31 @@ following commands (cd parquet && cargo publish) (cd parquet_derive && cargo publish) ``` + +# Backporting + +As of now, the plan for backporting to `active_release` is to do so semi-manually. + +Step 1: Pick the commit to cherry-pick + +Step 2: Create cherry-pick PR to active_release + +Step 3a: If CI passes, merge cherry-pick PR + +Step 3b: If CI doesn't pass or some other changes are needed, the PR should be reviewed / approved as normal prior to merge + + + +For example, to backport `b2de5446cc1e45a0559fb39039d0545df1ac0d26` to active_release use the folliwing + +```shell +git clone git@github.com:apache/arrow-rs.git /tmp/arrow-rs + +ARROW_GITHUB_API_TOKEN=$ARROW_GITHUB_API_TOKEN CHECKOUT_ROOT=/tmp/arrow-rs CHERRY_PICK_SHA=b2de5446cc1e45a0559fb39039d0545df1ac0d26 python3 dev/release/cherry-pick-pr.py +``` + +## Rationale for creating PRs: +1. PRs are a natural place to run the CI tests to make sure there are no logical conflicts +2. PRs offer a place for the original author / committers to comment and say it should/should not be backported. +3. PRs offer a way to make cleanups / fixups and approve (if needed) for non cherry pick PRs +4. There is an additional control / review when the candidate release is created diff --git a/dev/release/cherry-pick-pr.py b/dev/release/cherry-pick-pr.py new file mode 100755 index 000000000000..65095b695c33 --- /dev/null +++ b/dev/release/cherry-pick-pr.py @@ -0,0 +1,147 @@ +#!/usr/bin/python3 +############################################################################## +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +############################################################################## + +# This script is designed to create a cherry pick PR to a target branch +# +# Usage: python3 cherry_pick_pr.py +# +# To test locally: +# +# git clone git@github.com:apache/arrow-rs.git /tmp/arrow-rs +# +# pip3 install PyGithub +# ARROW_GITHUB_API_TOKEN=<..> +# CHECKOUT_ROOT= +# CHERRY_PICK_SHA= python3 cherry-pick-pr.py +# +import os +import sys +import six +import subprocess + +from pathlib import Path + +TARGET_BRANCH = 'active_release' +TARGET_REPO = 'apache/arrow-rs' + +p = Path(__file__) + +# Use github workspace if specified +repo_root = os.environ.get("CHECKOUT_ROOT") +if repo_root is None: + print("arrow-rs checkout must be supplied in CHECKOUT_ROOT environment") + sys.exit(1) + +print("Using checkout in {}".format(repo_root)) + +token = os.environ.get('ARROW_GITHUB_API_TOKEN', None) +if token is None: + print("GITHUB token must be supplied in ARROW_GITHUB_API_TOKEN environmet") + sys.exit(1) + +new_sha = os.environ.get('CHERRY_PICK_SHA', None) +if new_sha is None: + print("SHA to cherry pick must be supplied in CHERRY_PICK_SHA environment") + sys.exit(1) + + +# from merge_pr.py from arrow repo +def run_cmd(cmd): + if isinstance(cmd, six.string_types): + cmd = cmd.split(' ') + try: + output = subprocess.check_output(cmd) + except subprocess.CalledProcessError as e: + # this avoids hiding the stdout / stderr of failed processes + print('Command failed: %s' % cmd) + print('With output:') + print('--------------') + print(e.output) + print('--------------') + raise e + + if isinstance(output, six.binary_type): + output = output.decode('utf-8') + + return output + + +os.chdir(repo_root) +new_sha_short = run_cmd("git rev-parse --short {}".format(new_sha)).strip() +new_branch = 'cherry_pick_{}'.format(new_sha_short) + + +def make_cherry_pick(): + if os.environ.get('GITHUB_SHA', None) is not None: + print("Running on github runner, setting email/username") + run_cmd(['git', 'config', 'user.email', 'dev@arrow.apache.com']) + run_cmd(['git', 'config', 'user.name', 'Arrow-RS Automation']) + + # + # Create a new branch from active_release + # and cherry pick to there. + # + + print("Creating cherry pick from {} to {}".format( + new_sha_short, new_branch + )) + + # The following tortured dance is required due to how the github + # actions/checkout works (it doesn't pull other branches and pulls + # only one commit back) + + # pull 10 commits back so we can get the proper cherry pick + # (probably only need 2 but 10 must be better, right?) + run_cmd(['git', 'fetch', '--depth', '10', 'origin', 'master']) + run_cmd(['git', 'fetch', 'origin', 'active_release']) + run_cmd(['git', 'checkout', '-b', new_branch]) + run_cmd(['git', 'reset', '--hard', 'origin/active_release']) + run_cmd(['git', 'cherry-pick', new_sha]) + run_cmd(['git', 'push', '-u', 'origin', new_branch]) + + +def make_cherry_pick_pr(): + from github import Github + g = Github(token) + repo = g.get_repo(TARGET_REPO) + + # Default titles + new_title = 'Cherry pick {} to active_release'.format(new_sha) + new_commit_message = 'Automatic cherry-pick of {}\n'.format(new_sha) + + # try and get info from github api + commit = repo.get_commit(new_sha) + for orig_pull in commit.get_pulls(): + new_commit_message += '* Originally appeared in {}: {}\n'.format( + orig_pull.html_url, orig_pull.title) + new_title = 'Cherry pick {} to active_release'.format(orig_pull.title) + + pr = repo.create_pull(title=new_title, + body=new_commit_message, + base='refs/heads/active_release', + head='refs/heads/{}'.format(new_branch), + maintainer_can_modify=True + ) + + print('Created PR {}'.format(pr.html_url)) + + +make_cherry_pick() +make_cherry_pick_pr()