#!/usr/bin/env python3 # Copyright 2021 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # based on an almost identical script by: jyrki@google.com (Jyrki Alakuijala) """Updates the commit message used in the auto-roller. Merges several small commit logs into a single more useful commit message. Usage: update_commit_message.py --old-revision= """ import argparse import logging import os import platform import re import shutil import subprocess import sys import tempfile GCLIENT_LINE = r'([^:]+): ([^@]+)@(.*)' CHANGE_TEMPLATE = '* %s: %s.git/+log/%s..%s' EXIT_SUCCESS = 0 EXIT_FAILURE = 1 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) GCLIENT = """\ solutions = [{ 'name': '.', 'url': 'https://chromium.googlesource.com/vulkan-deps.git', 'deps_file': 'DEPS', 'managed': False, }] """ INSERT_NEEDLE = 'If this roll has caused a breakage' def run(cmd, args): exe = ('%s.bat' % cmd) if platform.system() == 'Windows' else cmd cmd_args = [exe] + list(args) return subprocess.check_output(cmd_args).decode('ascii').strip() def git(*args): return run('git', args) def gclient(*args): return run('gclient', args) def parse_revinfo(output): expr = re.compile(GCLIENT_LINE) config = dict() urls = dict() for line in output.split('\n'): match = expr.match(line.strip()) if match: dep = match.group(1) urls[dep] = match.group(2) config[dep] = match.group(3) return config, urls def _local_commit_amend(commit_msg, dry_run): logging.info('Amending changes to local commit.') old_commit_msg = git('log', '-1', '--pretty=%B') logging.debug('Existing commit message:\n%s\n', old_commit_msg) insert_index = old_commit_msg.rfind(INSERT_NEEDLE) if insert_index == -1: logging.exception('"%s" not found in commit message.' % INSERT_NEEDLE) new_commit_msg = old_commit_msg[:insert_index] + commit_msg + '\n\n' + old_commit_msg[insert_index:] logging.debug('New commit message:\n%s\n', new_commit_msg) if not dry_run: with tempfile.NamedTemporaryFile(delete=False, mode="w") as ntf: ntf.write(new_commit_msg) ntf.close() git('commit', '--amend', '--no-edit', '--file=%s' % ntf.name) os.unlink(ntf.name) def main(raw_args): parser = argparse.ArgumentParser() parser.add_argument('--old-revision', help='Old git revision in the roll.', required=True) parser.add_argument( '--dry-run', help='Test out functionality without making changes.', action='store_true', default=False) parser.add_argument( '-v', '--verbose', help='Verbose debug logging.', action='store_true', default=False) args = parser.parse_args(raw_args) if args.verbose: logging.basicConfig(level=logging.DEBUG) else: logging.basicConfig(level=logging.INFO) cwd = os.getcwd() os.chdir(SCRIPT_DIR) old_deps_content = git('show', '%s:DEPS' % args.old_revision) with tempfile.TemporaryDirectory() as tempdir: os.chdir(tempdir) # Add the gclientfile. with open(os.path.join(tempdir, '.gclient'), 'w') as gcfile: gcfile.write(GCLIENT) gcfile.close() # Get the current config. shutil.copyfile(os.path.join(SCRIPT_DIR, 'DEPS'), os.path.join(tempdir, 'DEPS')) gclient_head_output = gclient('revinfo') # Get the prior config. with open('DEPS', 'w') as deps: deps.write(old_deps_content) deps.close() gclient_old_output = gclient('revinfo') os.chdir(SCRIPT_DIR) head_config, urls = parse_revinfo(gclient_head_output) old_config, _ = parse_revinfo(gclient_old_output) changed_deps = [] for dep, new_sha1 in head_config.items(): if dep in old_config: old_sha1 = old_config[dep] if new_sha1 != old_sha1: dep_short = dep.replace('\\', '/').split('/')[0] repo = urls[dep] logging.debug('Found change: %s to %s' % (dep, new_sha1)) changed_deps.append(CHANGE_TEMPLATE % (dep_short, repo, old_sha1[:10], new_sha1[:10])) if not changed_deps: print('No changed dependencies, early exit.') return EXIT_SUCCESS commit_msg = 'Changed dependencies:\n%s' % '\n'.join(sorted(changed_deps)) os.chdir(cwd) _local_commit_amend(commit_msg, args.dry_run) return EXIT_SUCCESS if __name__ == '__main__': sys.exit(main(sys.argv[1:]))