1#!/usr/bin/env python 2# Copyright 2014 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 6"""Generate a CL to roll a DEPS entry to the specified revision number and post 7it to Rietveld so that the CL will land automatically if it passes the 8commit-queue's checks. 9""" 10 11import logging 12import optparse 13import os 14import re 15import sys 16 17import find_depot_tools 18import scm 19import subprocess2 20 21 22def die_with_error(msg): 23 print >> sys.stderr, msg 24 sys.exit(1) 25 26 27def process_deps(path, project, new_rev, is_dry_run): 28 """Update project_revision to |new_issue|. 29 30 A bit hacky, could it be made better? 31 """ 32 content = open(path).read() 33 # Hack for Blink to get the AutoRollBot running again. 34 if project == "blink": 35 project = "webkit" 36 old_line = r'(\s+)"%s_revision": "([0-9a-f]{2,40})",' % project 37 new_line = r'\1"%s_revision": "%s",' % (project, new_rev) 38 new_content = re.sub(old_line, new_line, content, 1) 39 old_rev = re.search(old_line, content).group(2) 40 if not old_rev or new_content == content: 41 die_with_error('Failed to update the DEPS file') 42 43 if not is_dry_run: 44 open(path, 'w').write(new_content) 45 return old_rev 46 47 48class PrintSubprocess(object): 49 """Wrapper for subprocess2 which prints out every command.""" 50 def __getattr__(self, attr): 51 def _run_subprocess2(cmd, *args, **kwargs): 52 print cmd 53 sys.stdout.flush() 54 return getattr(subprocess2, attr)(cmd, *args, **kwargs) 55 return _run_subprocess2 56 57prnt_subprocess = PrintSubprocess() 58 59 60def main(): 61 tool_dir = os.path.dirname(os.path.abspath(__file__)) 62 parser = optparse.OptionParser(usage='%prog [options] <project> <new rev>', 63 description=sys.modules[__name__].__doc__) 64 parser.add_option('-v', '--verbose', action='count', default=0) 65 parser.add_option('--dry-run', action='store_true') 66 parser.add_option('-f', '--force', action='store_true', 67 help='Make destructive changes to the local checkout if ' 68 'necessary.') 69 parser.add_option('--commit', action='store_true', default=True, 70 help='(default) Put change in commit queue on upload.') 71 parser.add_option('--no-commit', action='store_false', dest='commit', 72 help='Don\'t put change in commit queue on upload.') 73 parser.add_option('-r', '--reviewers', default='', 74 help='Add given users as either reviewers or TBR as' 75 ' appropriate.') 76 parser.add_option('--upstream', default='origin/master', 77 help='(default "%default") Use given start point for change' 78 ' to upload. For instance, if you use the old git workflow,' 79 ' you might set it to "origin/trunk".') 80 parser.add_option('--cc', help='CC email addresses for issue.') 81 parser.add_option('-m', '--message', help='Custom commit message.') 82 83 options, args = parser.parse_args() 84 logging.basicConfig( 85 level= 86 [logging.WARNING, logging.INFO, logging.DEBUG][ 87 min(2, options.verbose)]) 88 if len(args) != 2: 89 parser.print_help() 90 exit(0) 91 92 root_dir = os.path.dirname(tool_dir) 93 os.chdir(root_dir) 94 95 project = args[0] 96 new_rev = args[1] 97 98 # Silence the editor. 99 os.environ['EDITOR'] = 'true' 100 101 if options.force and not options.dry_run: 102 prnt_subprocess.check_call(['git', 'clean', '-d', '-f']) 103 prnt_subprocess.call(['git', 'rebase', '--abort']) 104 105 old_branch = scm.GIT.GetBranch(root_dir) 106 new_branch = '%s_roll' % project 107 108 if options.upstream == new_branch: 109 parser.error('Cannot set %s as its own upstream.' % new_branch) 110 111 if old_branch == new_branch: 112 if options.force: 113 if not options.dry_run: 114 prnt_subprocess.check_call(['git', 'checkout', options.upstream, '-f']) 115 prnt_subprocess.call(['git', 'branch', '-D', old_branch]) 116 else: 117 parser.error('Please delete the branch %s and move to a different branch' 118 % new_branch) 119 120 if not options.dry_run: 121 prnt_subprocess.check_call(['git', 'fetch', 'origin']) 122 prnt_subprocess.call(['git', 'svn', 'fetch']) 123 branch_cmd = ['git', 'checkout', '-b', new_branch, options.upstream] 124 if options.force: 125 branch_cmd.append('-f') 126 prnt_subprocess.check_output(branch_cmd) 127 128 try: 129 old_rev = process_deps(os.path.join(root_dir, 'DEPS'), project, new_rev, 130 options.dry_run) 131 print '%s roll %s:%s' % (project.title(), old_rev, new_rev) 132 133 review_field = 'TBR' if options.commit else 'R' 134 commit_msg = options.message or '%s roll %s:%s\n' % (project.title(), 135 old_rev, new_rev) 136 commit_msg += '\n%s=%s\n' % (review_field, options.reviewers) 137 138 if options.dry_run: 139 print 'Commit message: ' + commit_msg 140 return 0 141 142 prnt_subprocess.check_output(['git', 'commit', '-m', commit_msg, 'DEPS']) 143 prnt_subprocess.check_call(['git', 'diff', '--no-ext-diff', 144 options.upstream]) 145 upload_cmd = ['git', 'cl', 'upload', '--bypass-hooks'] 146 if options.commit: 147 upload_cmd.append('--use-commit-queue') 148 if options.reviewers: 149 upload_cmd.append('--send-mail') 150 if options.cc: 151 upload_cmd.extend(['--cc', options.cc]) 152 prnt_subprocess.check_call(upload_cmd) 153 finally: 154 if not options.dry_run: 155 prnt_subprocess.check_output(['git', 'checkout', old_branch]) 156 prnt_subprocess.check_output(['git', 'branch', '-D', new_branch]) 157 return 0 158 159 160if __name__ == '__main__': 161 sys.exit(main()) 162