1#!/usr/bin/env python3 2# Copyright 2021 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5# based on an almost identical script by: jyrki@google.com (Jyrki Alakuijala) 6"""Updates the commit message used in the auto-roller. 7 8Merges several small commit logs into a single more useful commit message. 9 10Usage: 11 update_commit_message.py --old-revision=<sha1> 12""" 13 14import argparse 15import logging 16import os 17import platform 18import re 19import shutil 20import subprocess 21import sys 22import tempfile 23 24GCLIENT_LINE = r'([^:]+): ([^@]+)@(.*)' 25CHANGE_TEMPLATE = '* %s: %s.git/+log/%s..%s' 26EXIT_SUCCESS = 0 27EXIT_FAILURE = 1 28SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 29GCLIENT = """\ 30solutions = [{ 31 'name': '.', 32 'url': 'https://chromium.googlesource.com/vulkan-deps.git', 33 'deps_file': 'DEPS', 34 'managed': False, 35}] 36""" 37INSERT_NEEDLE = 'If this roll has caused a breakage' 38 39 40def run(cmd, args): 41 exe = ('%s.bat' % cmd) if platform.system() == 'Windows' else cmd 42 cmd_args = [exe] + list(args) 43 return subprocess.check_output(cmd_args).decode('ascii').strip() 44 45 46def git(*args): 47 return run('git', args) 48 49 50def gclient(*args): 51 return run('gclient', args) 52 53 54def parse_revinfo(output): 55 expr = re.compile(GCLIENT_LINE) 56 config = dict() 57 urls = dict() 58 for line in output.split('\n'): 59 match = expr.match(line.strip()) 60 if match: 61 dep = match.group(1) 62 urls[dep] = match.group(2) 63 config[dep] = match.group(3) 64 return config, urls 65 66 67def _local_commit_amend(commit_msg, dry_run): 68 logging.info('Amending changes to local commit.') 69 old_commit_msg = git('log', '-1', '--pretty=%B') 70 logging.debug('Existing commit message:\n%s\n', old_commit_msg) 71 insert_index = old_commit_msg.rfind(INSERT_NEEDLE) 72 if insert_index == -1: 73 logging.exception('"%s" not found in commit message.' % INSERT_NEEDLE) 74 75 new_commit_msg = old_commit_msg[:insert_index] + commit_msg + '\n\n' + old_commit_msg[insert_index:] 76 logging.debug('New commit message:\n%s\n', new_commit_msg) 77 if not dry_run: 78 with tempfile.NamedTemporaryFile(delete=False, mode="w") as ntf: 79 ntf.write(new_commit_msg) 80 ntf.close() 81 git('commit', '--amend', '--no-edit', '--file=%s' % ntf.name) 82 os.unlink(ntf.name) 83 84 85def main(raw_args): 86 parser = argparse.ArgumentParser() 87 parser.add_argument('--old-revision', help='Old git revision in the roll.', required=True) 88 parser.add_argument( 89 '--dry-run', 90 help='Test out functionality without making changes.', 91 action='store_true', 92 default=False) 93 parser.add_argument( 94 '-v', '--verbose', help='Verbose debug logging.', action='store_true', default=False) 95 args = parser.parse_args(raw_args) 96 97 if args.verbose: 98 logging.basicConfig(level=logging.DEBUG) 99 else: 100 logging.basicConfig(level=logging.INFO) 101 102 cwd = os.getcwd() 103 104 os.chdir(SCRIPT_DIR) 105 old_deps_content = git('show', '%s:DEPS' % args.old_revision) 106 107 with tempfile.TemporaryDirectory() as tempdir: 108 os.chdir(tempdir) 109 110 # Add the gclientfile. 111 with open(os.path.join(tempdir, '.gclient'), 'w') as gcfile: 112 gcfile.write(GCLIENT) 113 gcfile.close() 114 115 # Get the current config. 116 shutil.copyfile(os.path.join(SCRIPT_DIR, 'DEPS'), os.path.join(tempdir, 'DEPS')) 117 gclient_head_output = gclient('revinfo') 118 119 # Get the prior config. 120 with open('DEPS', 'w') as deps: 121 deps.write(old_deps_content) 122 deps.close() 123 gclient_old_output = gclient('revinfo') 124 os.chdir(SCRIPT_DIR) 125 126 head_config, urls = parse_revinfo(gclient_head_output) 127 old_config, _ = parse_revinfo(gclient_old_output) 128 129 changed_deps = [] 130 131 for dep, new_sha1 in head_config.items(): 132 if dep in old_config: 133 old_sha1 = old_config[dep] 134 if new_sha1 != old_sha1: 135 dep_short = dep.replace('\\', '/').split('/')[0] 136 repo = urls[dep] 137 logging.debug('Found change: %s to %s' % (dep, new_sha1)) 138 changed_deps.append(CHANGE_TEMPLATE % 139 (dep_short, repo, old_sha1[:10], new_sha1[:10])) 140 141 if not changed_deps: 142 print('No changed dependencies, early exit.') 143 return EXIT_SUCCESS 144 145 commit_msg = 'Changed dependencies:\n%s' % '\n'.join(sorted(changed_deps)) 146 147 os.chdir(cwd) 148 _local_commit_amend(commit_msg, args.dry_run) 149 150 return EXIT_SUCCESS 151 152 153if __name__ == '__main__': 154 sys.exit(main(sys.argv[1:])) 155