Add script for automating cherry-picks to release branches

Change-Id: Ic29668fc4c35b3d82106833cb457e95216231b13
diff --git a/tools/cherry-pick.py b/tools/cherry-pick.py
new file mode 100755
index 0000000..08fdac3
--- /dev/null
+++ b/tools/cherry-pick.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+import argparse
+import os
+import subprocess
+import sys
+import utils
+
+def parse_options():
+  parser = argparse.ArgumentParser(description='Release r8')
+  parser.add_argument('--branch',
+                      metavar=('<branch>'),
+                      help='Branch to cherry-pick to')
+  parser.add_argument('--clean-checkout', '--clean_checkout',
+                      default=False,
+                      action='store_true',
+                      help='Perform cherry picks in a clean checkout')
+  parser.add_argument('--no-upload', '--no_upload',
+                      default=False,
+                      action='store_true',
+                      help='Do not upload to Gerrit')
+  parser.add_argument('hashes', metavar='<hash>', nargs='+',
+                    help='Hashed to merge')
+
+  return parser.parse_args()
+
+
+def run(args):
+  # Checkout the branch.
+  subprocess.check_output(['git', 'checkout', args.branch])
+
+  if (not args.clean_checkout):
+    for i in range(len(args.hashes) + 1):
+      branch = 'cherry-%d' % (i + 1)
+      print('Deleting branch %s' % branch)
+      subprocess.run(['git', 'branch', branch, '-D'])
+
+  count = 1
+  for hash in args.hashes:
+    branch = 'cherry-%d' % count
+    print('Cherry-picking %s in %s' % (hash, branch))
+    if (count == 1):
+      subprocess.run(['git', 'new-branch', branch, '--upstream',  'origin/%s' % args.branch])
+    else:
+      subprocess.run(['git', 'new-branch', branch, '--upstream-current'])
+
+    subprocess.run(['git', 'cherry-pick', hash])
+
+    question = ('Ready to continue (cwd %s, will not upload to Gerrit)' % os.getcwd()
+      if args.no_upload else
+      'Ready to upload %s (cwd %s)' % (branch, os.getcwd()))
+    answer = input(question + ' [y/N]?')
+    if answer != 'y':
+      print('Aborting new branch for %s' % branch_version)
+      sys.exit(1)
+
+    if (not args.no_upload):
+      subprocess.run(['git', 'cl', 'upload'])
+    count = count + 1
+
+  branch = 'cherry-%d' % count
+  subprocess.run(['git', 'new-branch', branch, '--upstream-current'])
+  editor = os.environ.get('VISUAL')
+  if not editor:
+    editor = os.environ.get('EDITOR')
+  if not editor:
+    editor = 'vi'
+  else:
+    print("Opening src/main/java/com/android/tools/r8/Version.java" +
+      " for version update with %" % editor)
+  subprocess.run([editor, 'src/main/java/com/android/tools/r8/Version.java'])
+  subprocess.run(['git', 'commit', '-a'])
+  if (not args.no_upload):
+    subprocess.run(['git', 'cl', 'upload'])
+  if (args.clean_checkout):
+    answer = input('Done, press enter to delete checkout in %s' % os.getcwd())
+
+def main():
+  args = parse_options()
+
+  if (args.clean_checkout):
+    with utils.TempDir() as temp:
+      print("Performing cherry-picking in %s" % temp)
+      subprocess.check_call(['git', 'clone', utils.REPO_SOURCE, temp])
+      with utils.ChangedWorkingDirectory(temp):
+        run(args)
+  else:
+    # Run in current directory.
+    print("Performing cherry-picking in %s" % os.getcwd())
+    subprocess.check_output(['git', 'fetch', 'origin'])
+    run(args)
+
+if __name__ == '__main__':
+  sys.exit(main())