Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Applies an issue from Rietveld. | 6 """Applies an issue from Rietveld. |
| 7 """ | 7 """ |
| 8 | |
| 9 import getpass | 8 import getpass |
| 10 import json | 9 import json |
| 11 import logging | 10 import logging |
| 12 import optparse | 11 import optparse |
| 13 import os | 12 import os |
| 14 import subprocess | 13 import subprocess |
| 15 import sys | 14 import sys |
| 16 import urllib2 | 15 import urllib2 |
| 17 | 16 |
| 18 import breakpad # pylint: disable=W0611 | 17 import breakpad # pylint: disable=W0611 |
| 19 | 18 |
| 20 import annotated_gclient | 19 import annotated_gclient |
| 21 import auth | 20 import auth |
| 22 import checkout | 21 import checkout |
| 23 import fix_encoding | 22 import fix_encoding |
| 24 import gclient_utils | 23 import gclient_utils |
| 25 import rietveld | 24 import rietveld |
| 26 import scm | 25 import scm |
| 27 | 26 |
| 28 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | 27 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
| 29 | 28 |
| 30 | 29 |
| 30 RETURN_CODE_ARG_PARSER = 2 # default in python. | |
| 31 RETURN_CODE_INFRA_FAILURE = 3 # considered as infra failure. | |
| 32 RETURN_CODE_OTHERWISE = 1 # any other failure, likely patch apply one. | |
|
pgervais
2015/10/05 16:13:51
Nit: RETURN_CODE_FAILURE_OTHER ?
'RETURN_CODE_OTH
tandrii(chromium)
2015/10/05 17:55:43
Done.
| |
| 33 | |
| 34 | |
| 31 class Unbuffered(object): | 35 class Unbuffered(object): |
| 32 """Disable buffering on a file object.""" | 36 """Disable buffering on a file object.""" |
| 33 def __init__(self, stream): | 37 def __init__(self, stream): |
| 34 self.stream = stream | 38 self.stream = stream |
| 35 | 39 |
| 36 def write(self, data): | 40 def write(self, data): |
| 37 self.stream.write(data) | 41 self.stream.write(data) |
| 38 self.stream.flush() | 42 self.stream.flush() |
| 39 | 43 |
| 40 def __getattr__(self, attr): | 44 def __getattr__(self, attr): |
| 41 return getattr(self.stream, attr) | 45 return getattr(self.stream, attr) |
| 42 | 46 |
| 43 | 47 |
| 44 def main(): | 48 def _get_arg_parser(): |
| 45 # TODO(pgervais): This function is way too long. Split. | |
| 46 sys.stdout = Unbuffered(sys.stdout) | |
| 47 parser = optparse.OptionParser(description=sys.modules[__name__].__doc__) | 49 parser = optparse.OptionParser(description=sys.modules[__name__].__doc__) |
| 48 parser.add_option( | 50 parser.add_option( |
| 49 '-v', '--verbose', action='count', default=0, | 51 '-v', '--verbose', action='count', default=0, |
| 50 help='Prints debugging infos') | 52 help='Prints debugging infos') |
| 51 parser.add_option( | 53 parser.add_option( |
| 52 '-e', '--email', | 54 '-e', '--email', |
| 53 help='Email address to access rietveld. If not specified, anonymous ' | 55 help='Email address to access rietveld. If not specified, anonymous ' |
| 54 'access will be used.') | 56 'access will be used.') |
| 55 parser.add_option( | 57 parser.add_option( |
| 56 '-E', '--email-file', | 58 '-E', '--email-file', |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 85 'is detected.') | 87 'is detected.') |
| 86 parser.add_option('-b', '--base_ref', help='DEPRECATED do not use.') | 88 parser.add_option('-b', '--base_ref', help='DEPRECATED do not use.') |
| 87 parser.add_option('--whitelist', action='append', default=[], | 89 parser.add_option('--whitelist', action='append', default=[], |
| 88 help='Patch only specified file(s).') | 90 help='Patch only specified file(s).') |
| 89 parser.add_option('--blacklist', action='append', default=[], | 91 parser.add_option('--blacklist', action='append', default=[], |
| 90 help='Don\'t patch specified file(s).') | 92 help='Don\'t patch specified file(s).') |
| 91 parser.add_option('-d', '--ignore_deps', action='store_true', | 93 parser.add_option('-d', '--ignore_deps', action='store_true', |
| 92 help='Don\'t run gclient sync on DEPS changes.') | 94 help='Don\'t run gclient sync on DEPS changes.') |
| 93 | 95 |
| 94 auth.add_auth_options(parser) | 96 auth.add_auth_options(parser) |
| 97 return parser | |
| 98 | |
| 99 | |
| 100 def main(): | |
| 101 # TODO(pgervais,tandrii): split this func, it's still too long. | |
| 102 sys.stdout = Unbuffered(sys.stdout) | |
| 103 parser = _get_arg_parser() | |
| 95 options, args = parser.parse_args() | 104 options, args = parser.parse_args() |
| 96 auth_config = auth.extract_auth_config_from_options(options) | 105 auth_config = auth.extract_auth_config_from_options(options) |
| 97 | 106 |
| 98 if options.whitelist and options.blacklist: | 107 if options.whitelist and options.blacklist: |
| 99 parser.error('Cannot specify both --whitelist and --blacklist') | 108 parser.error('Cannot specify both --whitelist and --blacklist') |
| 100 | 109 |
| 101 if options.email and options.email_file: | 110 if options.email and options.email_file: |
| 102 parser.error('-e and -E options are incompatible') | 111 parser.error('-e and -E options are incompatible') |
| 103 | 112 |
| 104 if (os.path.isfile(os.path.join(os.getcwd(), 'update.flag')) | 113 if (os.path.isfile(os.path.join(os.getcwd(), 'update.flag')) |
| 105 and not options.force): | 114 and not options.force): |
| 106 print 'update.flag file found: bot_update has run and checkout is already ' | 115 print 'update.flag file found: bot_update has run and checkout is already ' |
| 107 print 'in a consistent state. No actions will be performed in this step.' | 116 print 'in a consistent state. No actions will be performed in this step.' |
| 108 return 0 | 117 return 0 |
| 118 | |
| 109 logging.basicConfig( | 119 logging.basicConfig( |
| 110 format='%(levelname)5s %(module)11s(%(lineno)4d): %(message)s', | 120 format='%(levelname)5s %(module)11s(%(lineno)4d): %(message)s', |
| 111 level=[logging.WARNING, logging.INFO, logging.DEBUG][ | 121 level=[logging.WARNING, logging.INFO, logging.DEBUG][ |
| 112 min(2, options.verbose)]) | 122 min(2, options.verbose)]) |
| 113 if args: | 123 if args: |
| 114 parser.error('Extra argument(s) "%s" not understood' % ' '.join(args)) | 124 parser.error('Extra argument(s) "%s" not understood' % ' '.join(args)) |
| 115 if not options.issue: | 125 if not options.issue: |
| 116 parser.error('Require --issue') | 126 parser.error('Require --issue') |
| 117 options.server = options.server.rstrip('/') | 127 options.server = options.server.rstrip('/') |
| 118 if not options.server: | 128 if not options.server: |
| 119 parser.error('Require a valid server') | 129 parser.error('Require a valid server') |
| 120 | 130 |
| 121 options.revision_mapping = json.loads(options.revision_mapping) | 131 options.revision_mapping = json.loads(options.revision_mapping) |
| 122 | 132 |
| 123 # read email if needed | 133 # read email if needed |
| 124 if options.email_file: | 134 if options.email_file: |
| 125 if not os.path.exists(options.email_file): | 135 if not os.path.exists(options.email_file): |
| 126 parser.error('file does not exist: %s' % options.email_file) | 136 parser.error('file does not exist: %s' % options.email_file) |
| 127 with open(options.email_file, 'rb') as f: | 137 with open(options.email_file, 'rb') as f: |
| 128 options.email = f.read().strip() | 138 options.email = f.read().strip() |
| 129 | 139 |
| 130 print('Connecting to %s' % options.server) | 140 print('Connecting to %s' % options.server) |
| 131 # Always try un-authenticated first, except for OAuth2 | 141 # Always try un-authenticated first, except for OAuth2 |
| 132 if options.private_key_file: | 142 if options.private_key_file: |
| 133 # OAuth2 authentication | 143 # OAuth2 authentication |
| 134 obj = rietveld.JwtOAuth2Rietveld(options.server, | 144 rietveld_obj = rietveld.JwtOAuth2Rietveld(options.server, |
| 135 options.email, | 145 options.email, |
| 136 options.private_key_file) | 146 options.private_key_file) |
| 137 properties = obj.get_issue_properties(options.issue, False) | 147 try: |
| 148 properties = rietveld_obj.get_issue_properties(options.issue, False) | |
| 149 except urllib2.URLError: | |
| 150 logging.exception('failed to fetch issue properties') | |
| 151 sys.exit(RETURN_CODE_INFRA_FAILURE) | |
| 138 else: | 152 else: |
| 139 # Passing None as auth_config disables authentication. | 153 # Passing None as auth_config disables authentication. |
| 140 obj = rietveld.Rietveld(options.server, None) | 154 rietveld_obj = rietveld.Rietveld(options.server, None) |
| 141 properties = None | 155 properties = None |
| 142 # Bad except clauses order (HTTPError is an ancestor class of | 156 # Bad except clauses order (HTTPError is an ancestor class of |
| 143 # ClientLoginError) | 157 # ClientLoginError) |
| 144 # pylint: disable=E0701 | 158 # pylint: disable=E0701 |
| 145 try: | 159 try: |
| 146 properties = obj.get_issue_properties(options.issue, False) | 160 properties = rietveld_obj.get_issue_properties(options.issue, False) |
| 147 except urllib2.HTTPError as e: | 161 except urllib2.HTTPError as e: |
| 148 if e.getcode() != 302: | 162 if e.getcode() != 302: |
| 149 raise | 163 raise |
| 150 if options.no_auth: | 164 if options.no_auth: |
| 151 exit('FAIL: Login detected -- is issue private?') | 165 exit('FAIL: Login detected -- is issue private?') |
| 152 # TODO(maruel): A few 'Invalid username or password.' are printed first, | 166 # TODO(maruel): A few 'Invalid username or password.' are printed first, |
| 153 # we should get rid of those. | 167 # we should get rid of those. |
| 168 except urllib2.URLError: | |
| 169 logging.exception('failed to fetch issue properties') | |
| 170 return RETURN_CODE_INFRA_FAILURE | |
| 154 except rietveld.upload.ClientLoginError as e: | 171 except rietveld.upload.ClientLoginError as e: |
| 155 # Fine, we'll do proper authentication. | 172 # Fine, we'll do proper authentication. |
| 156 pass | 173 pass |
| 157 if properties is None: | 174 if properties is None: |
| 158 obj = rietveld.Rietveld(options.server, auth_config, options.email) | 175 rietveld_obj = rietveld.Rietveld(options.server, auth_config, |
| 176 options.email) | |
| 159 try: | 177 try: |
| 160 properties = obj.get_issue_properties(options.issue, False) | 178 properties = rietveld_obj.get_issue_properties(options.issue, False) |
| 161 except rietveld.upload.ClientLoginError as e: | 179 except rietveld.upload.ClientLoginError as e: |
| 162 print('Accessing the issue requires proper credentials.') | 180 print('Accessing the issue requires proper credentials.') |
| 163 return 1 | 181 return RETURN_CODE_OTHERWISE |
| 182 except urllib2.URLError: | |
| 183 logging.exception('failed to fetch issue properties') | |
| 184 return RETURN_CODE_INFRA_FAILURE | |
| 164 | 185 |
| 165 if not options.patchset: | 186 if not options.patchset: |
| 166 options.patchset = properties['patchsets'][-1] | 187 options.patchset = properties['patchsets'][-1] |
| 167 print('No patchset specified. Using patchset %d' % options.patchset) | 188 print('No patchset specified. Using patchset %d' % options.patchset) |
| 168 | 189 |
| 169 issues_patchsets_to_apply = [(options.issue, options.patchset)] | 190 issues_patchsets_to_apply = [(options.issue, options.patchset)] |
| 170 depends_on_info = obj.get_depends_on_patchset(options.issue, options.patchset) | 191 try: |
| 192 depends_on_info = rietveld_obj.get_depends_on_patchset( | |
| 193 options.issue, options.patchset) | |
| 194 except urllib2.URLError: | |
| 195 logging.exception('failed to fetch depends_on_patchset') | |
| 196 return RETURN_CODE_INFRA_FAILURE | |
| 197 | |
| 171 while depends_on_info: | 198 while depends_on_info: |
| 172 depends_on_issue = int(depends_on_info['issue']) | 199 depends_on_issue = int(depends_on_info['issue']) |
| 173 depends_on_patchset = int(depends_on_info['patchset']) | 200 depends_on_patchset = int(depends_on_info['patchset']) |
| 174 try: | 201 try: |
| 175 depends_on_info = obj.get_depends_on_patchset(depends_on_issue, | 202 depends_on_info = rietveld_obj.get_depends_on_patchset(depends_on_issue, |
| 176 depends_on_patchset) | 203 depends_on_patchset) |
| 177 issues_patchsets_to_apply.insert(0, (depends_on_issue, | 204 issues_patchsets_to_apply.insert(0, (depends_on_issue, |
| 178 depends_on_patchset)) | 205 depends_on_patchset)) |
| 179 except urllib2.HTTPError: | 206 except urllib2.HTTPError: |
| 180 print ('The patchset that was marked as a dependency no longer ' | 207 print ('The patchset that was marked as a dependency no longer ' |
| 181 'exists: %s/%d/#ps%d' % ( | 208 'exists: %s/%d/#ps%d' % ( |
| 182 options.server, depends_on_issue, depends_on_patchset)) | 209 options.server, depends_on_issue, depends_on_patchset)) |
| 183 print 'Therefore it is likely that this patch will not apply cleanly.' | 210 print 'Therefore it is likely that this patch will not apply cleanly.' |
| 184 print | 211 print |
| 185 depends_on_info = None | 212 depends_on_info = None |
| 213 except urllib2.URLError: | |
| 214 logging.exception('failed to fetch dependency issue') | |
| 215 return RETURN_CODE_INFRA_FAILURE | |
| 186 | 216 |
| 187 num_issues_patchsets_to_apply = len(issues_patchsets_to_apply) | 217 num_issues_patchsets_to_apply = len(issues_patchsets_to_apply) |
| 188 if num_issues_patchsets_to_apply > 1: | 218 if num_issues_patchsets_to_apply > 1: |
| 189 print | 219 print |
| 190 print 'apply_issue.py found %d dependent CLs.' % ( | 220 print 'apply_issue.py found %d dependent CLs.' % ( |
| 191 num_issues_patchsets_to_apply - 1) | 221 num_issues_patchsets_to_apply - 1) |
| 192 print 'They will be applied in the following order:' | 222 print 'They will be applied in the following order:' |
| 193 num = 1 | 223 num = 1 |
| 194 for issue_to_apply, patchset_to_apply in issues_patchsets_to_apply: | 224 for issue_to_apply, patchset_to_apply in issues_patchsets_to_apply: |
| 195 print ' #%d %s/%d/#ps%d' % ( | 225 print ' #%d %s/%d/#ps%d' % ( |
| 196 num, options.server, issue_to_apply, patchset_to_apply) | 226 num, options.server, issue_to_apply, patchset_to_apply) |
| 197 num += 1 | 227 num += 1 |
| 198 print | 228 print |
| 199 | 229 |
| 200 for issue_to_apply, patchset_to_apply in issues_patchsets_to_apply: | 230 for issue_to_apply, patchset_to_apply in issues_patchsets_to_apply: |
| 201 issue_url = '%s/%d/#ps%d' % (options.server, issue_to_apply, | 231 issue_url = '%s/%d/#ps%d' % (options.server, issue_to_apply, |
| 202 patchset_to_apply) | 232 patchset_to_apply) |
| 203 print('Downloading patch from %s' % issue_url) | 233 print('Downloading patch from %s' % issue_url) |
| 204 try: | 234 try: |
| 205 patchset = obj.get_patch(issue_to_apply, patchset_to_apply) | 235 patchset = rietveld_obj.get_patch(issue_to_apply, patchset_to_apply) |
| 206 except urllib2.HTTPError: | 236 except urllib2.HTTPError: |
| 207 print( | 237 print( |
| 208 'Failed to fetch the patch for issue %d, patchset %d.\n' | 238 'Failed to fetch the patch for issue %d, patchset %d.\n' |
| 209 'Try visiting %s/%d') % ( | 239 'Try visiting %s/%d') % ( |
| 210 issue_to_apply, patchset_to_apply, | 240 issue_to_apply, patchset_to_apply, |
| 211 options.server, issue_to_apply) | 241 options.server, issue_to_apply) |
| 212 # Special code for bot_update to indicate that cause is network or | 242 # If we got this far, then this is likely missing patchset. |
| 213 # Rietveld. Not 2, because 2 is returned on arg parsing failure. | 243 # Thus, it's not infra failure. |
| 214 return 3 | 244 return RETURN_CODE_OTHERWISE |
| 245 except urllib2.URLError: | |
| 246 logging.exception( | |
| 247 'Failed to fetch the patch for issue %d, patchset %d', | |
| 248 issue_to_apply, patchset_to_apply) | |
| 249 return RETURN_CODE_INFRA_FAILURE | |
| 215 if options.whitelist: | 250 if options.whitelist: |
| 216 patchset.patches = [patch for patch in patchset.patches | 251 patchset.patches = [patch for patch in patchset.patches |
| 217 if patch.filename in options.whitelist] | 252 if patch.filename in options.whitelist] |
| 218 if options.blacklist: | 253 if options.blacklist: |
| 219 patchset.patches = [patch for patch in patchset.patches | 254 patchset.patches = [patch for patch in patchset.patches |
| 220 if patch.filename not in options.blacklist] | 255 if patch.filename not in options.blacklist] |
| 221 for patch in patchset.patches: | 256 for patch in patchset.patches: |
| 222 print(patch) | 257 print(patch) |
| 223 full_dir = os.path.abspath(options.root_dir) | 258 full_dir = os.path.abspath(options.root_dir) |
| 224 scm_type = scm.determine_scm(full_dir) | 259 scm_type = scm.determine_scm(full_dir) |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 240 # chromium_commands.py?view=markup | 275 # chromium_commands.py?view=markup |
| 241 open('.buildbot-patched', 'w').close() | 276 open('.buildbot-patched', 'w').close() |
| 242 | 277 |
| 243 print('\nApplying the patch from %s' % issue_url) | 278 print('\nApplying the patch from %s' % issue_url) |
| 244 try: | 279 try: |
| 245 scm_obj.apply_patch(patchset, verbose=True) | 280 scm_obj.apply_patch(patchset, verbose=True) |
| 246 except checkout.PatchApplicationFailed as e: | 281 except checkout.PatchApplicationFailed as e: |
| 247 print(str(e)) | 282 print(str(e)) |
| 248 print('CWD=%s' % os.getcwd()) | 283 print('CWD=%s' % os.getcwd()) |
| 249 print('Checkout path=%s' % scm_obj.project_path) | 284 print('Checkout path=%s' % scm_obj.project_path) |
| 250 return 1 | 285 return RETURN_CODE_OTHERWISE |
| 251 | 286 |
| 252 if ('DEPS' in map(os.path.basename, patchset.filenames) | 287 if ('DEPS' in map(os.path.basename, patchset.filenames) |
| 253 and not options.ignore_deps): | 288 and not options.ignore_deps): |
| 254 gclient_root = gclient_utils.FindGclientRoot(full_dir) | 289 gclient_root = gclient_utils.FindGclientRoot(full_dir) |
| 255 if gclient_root and scm_type: | 290 if gclient_root and scm_type: |
| 256 print( | 291 print( |
| 257 'A DEPS file was updated inside a gclient checkout, running gclient ' | 292 'A DEPS file was updated inside a gclient checkout, running gclient ' |
| 258 'sync.') | 293 'sync.') |
| 259 gclient_path = os.path.join(BASE_DIR, 'gclient') | 294 gclient_path = os.path.join(BASE_DIR, 'gclient') |
| 260 if sys.platform == 'win32': | 295 if sys.platform == 'win32': |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 278 annotated_gclient.emit_buildprops(revisions) | 313 annotated_gclient.emit_buildprops(revisions) |
| 279 | 314 |
| 280 return retcode | 315 return retcode |
| 281 return 0 | 316 return 0 |
| 282 | 317 |
| 283 | 318 |
| 284 if __name__ == "__main__": | 319 if __name__ == "__main__": |
| 285 fix_encoding.fix_encoding() | 320 fix_encoding.fix_encoding() |
| 286 try: | 321 try: |
| 287 sys.exit(main()) | 322 sys.exit(main()) |
| 288 except urllib2.URLError: | |
| 289 # Weird flakiness of GAE, see http://crbug.com/537417 | |
| 290 logging.exception('failed to fetch something from Rietveld') | |
| 291 sys.exit(3) | |
| 292 except KeyboardInterrupt: | 323 except KeyboardInterrupt: |
| 293 sys.stderr.write('interrupted\n') | 324 sys.stderr.write('interrupted\n') |
| 294 sys.exit(1) | 325 sys.exit(RETURN_CODE_OTHERWISE) |
| OLD | NEW |