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
diff --git a/dev/release/README.md b/dev/release/README.md
index 9581a63..62f0063 100644
--- a/dev/release/README.md
+++ b/dev/release/README.md
@@ -234,3 +234,31 @@
(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 0000000..65095b6
--- /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=<path>
+# CHERRY_PICK_SHA=<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()