• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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