| #!/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 | 
 |  | 
 | VERSION_FILE = 'src/main/java/com/android/tools/r8/Version.java' | 
 | VERSION_PREFIX = 'String LABEL = "' | 
 |  | 
 |  | 
 | def parse_options(): | 
 |     parser = argparse.ArgumentParser(description='Release r8') | 
 |     parser.add_argument('--branch', | 
 |                         metavar=('<branch>'), | 
 |                         help='Branch to cherry-pick to') | 
 |     parser.add_argument('--current-checkout', | 
 |                         '--current_checkout', | 
 |                         default=False, | 
 |                         action='store_true', | 
 |                         help='Perform cherry picks into the current 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') | 
 |     parser.add_argument('--remote', | 
 |                         default='origin', | 
 |                         help='The remote name (defaults to "origin")') | 
 |     return parser.parse_args() | 
 |  | 
 |  | 
 | def run(args): | 
 |     if args.current_checkout: | 
 |         for i in range(len(args.hashes) + 1): | 
 |             branch = 'cherry-%s-%d' % (args.branch, i + 1) | 
 |             print('Deleting branch %s' % branch) | 
 |             subprocess.run(['git', 'branch', branch, '-D']) | 
 |  | 
 |     bugs = set() | 
 |  | 
 |     count = 1 | 
 |     for hash in args.hashes: | 
 |         branch = 'cherry-%s-%d' % (args.branch, count) | 
 |         print('Cherry-picking %s in %s' % (hash, branch)) | 
 |         if count == 1: | 
 |             subprocess.run([ | 
 |                 'git', 'new-branch', branch, '--upstream', | 
 |                 '%s/%s' % (args.remote, args.branch) | 
 |             ]) | 
 |         else: | 
 |             subprocess.run(['git', 'new-branch', branch, '--upstream-current']) | 
 |  | 
 |         subprocess.run(['git', 'cherry-pick', hash]) | 
 |         confirm_and_upload(branch, args, bugs) | 
 |         count = count + 1 | 
 |  | 
 |     branch = 'cherry-%s-%d' % (args.branch, count) | 
 |     subprocess.run(['git', 'new-branch', branch, '--upstream-current']) | 
 |  | 
 |     old_version = 'unknown' | 
 |     for line in open(VERSION_FILE, 'r'): | 
 |         index = line.find(VERSION_PREFIX) | 
 |         if index > 0: | 
 |             index += len(VERSION_PREFIX) | 
 |             subline = line[index:] | 
 |             old_version = subline[:subline.index('"')] | 
 |             break | 
 |  | 
 |     new_version = 'unknown' | 
 |     if old_version.find('.') > 0 and old_version.find('-') == -1: | 
 |         split_version = old_version.split('.') | 
 |         new_version = '.'.join(split_version[:-1] + | 
 |                                [str(int(split_version[-1]) + 1)]) | 
 |         subprocess.run([ | 
 |             'sed', '-i', | 
 |             's/%s/%s/' % (old_version, new_version), VERSION_FILE | 
 |         ]) | 
 |     else: | 
 |         editor = os.environ.get('VISUAL') | 
 |         if not editor: | 
 |             editor = os.environ.get('EDITOR') | 
 |         if not editor: | 
 |             editor = 'vi' | 
 |         print("Opening %s for version update with %s" % | 
 |               (VERSION_FILE, editor)) | 
 |         subprocess.run([editor, VERSION_FILE]) | 
 |  | 
 |     message = ("Version %s\n\n" % new_version) | 
 |     for bug in sorted(bugs): | 
 |         message += 'Bug: b/%s\n' % bug | 
 |  | 
 |     subprocess.run(['git', 'commit', '-a', '-m', message]) | 
 |     confirm_and_upload(branch, args, None) | 
 |     if not args.current_checkout: | 
 |         while True: | 
 |             try: | 
 |                 answer = input( | 
 |                     "Type 'delete' to finish and delete checkout in %s: " % | 
 |                     os.getcwd()) | 
 |                 if answer == 'delete': | 
 |                     break | 
 |             except KeyboardInterrupt: | 
 |                 pass | 
 |  | 
 |  | 
 | def confirm_and_upload(branch, args, bugs): | 
 |     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())) | 
 |  | 
 |     while True: | 
 |         try: | 
 |             answer = input(question + ' [yes/abort]? ') | 
 |             if answer == 'yes': | 
 |                 break | 
 |             if answer == 'abort': | 
 |                 print('Aborting new branch for %s' % branch) | 
 |                 sys.exit(1) | 
 |         except KeyboardInterrupt: | 
 |             pass | 
 |  | 
 |     # Compute the set of bug refs from the commit message after confirmation. | 
 |     # If done before a conflicting cherry-pick status will potentially include | 
 |     # references that are orthogonal to the pick. | 
 |     if bugs != None: | 
 |         commit_message = subprocess.check_output( | 
 |             ['git', 'log', '--format=%B', '-n', '1', 'HEAD']) | 
 |         commit_lines = [ | 
 |             l.strip() for l in commit_message.decode('UTF-8').split('\n') | 
 |         ] | 
 |         for line in commit_lines: | 
 |             bug = None | 
 |             if line.startswith('Bug: '): | 
 |                 bug = line.replace('Bug: ', '') | 
 |             elif line.startswith('Fixed: '): | 
 |                 bug = line.replace('Fixed: ', '') | 
 |             elif line.startswith('Fixes: '): | 
 |                 bug = line.replace('Fixes: ', '') | 
 |             if bug: | 
 |                 bugs.add(bug.replace('b/', '').strip()) | 
 |  | 
 |     if not args.no_upload: | 
 |         subprocess.run(['git', 'cl', 'upload', '--bypass-hooks']) | 
 |  | 
 |  | 
 | def main(): | 
 |     args = parse_options() | 
 |  | 
 |     if (not args.current_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()) |