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 | 8 |
| 9 import getpass | 9 import getpass |
| 10 import json | 10 import json |
| 11 import logging | 11 import logging |
| 12 import optparse | 12 import optparse |
| 13 import os | 13 import os |
| 14 import subprocess | 14 import subprocess |
| 15 import sys | 15 import sys |
| 16 import urllib2 | 16 import urllib2 |
| 17 | 17 |
| 18 import breakpad # pylint: disable=W0611 | 18 import breakpad # pylint: disable=W0611 |
| 19 | 19 |
| 20 import annotated_gclient | 20 import annotated_gclient |
| 21 import auth | |
| 21 import checkout | 22 import checkout |
| 22 import fix_encoding | 23 import fix_encoding |
| 23 import gclient_utils | 24 import gclient_utils |
| 24 import rietveld | 25 import rietveld |
| 25 import scm | 26 import scm |
| 26 | 27 |
| 27 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | 28 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) |
| 28 | 29 |
| 29 | 30 |
| 30 class Unbuffered(object): | 31 class Unbuffered(object): |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 49 help='Prints debugging infos') | 50 help='Prints debugging infos') |
| 50 parser.add_option( | 51 parser.add_option( |
| 51 '-e', '--email', | 52 '-e', '--email', |
| 52 help='Email address to access rietveld. If not specified, anonymous ' | 53 help='Email address to access rietveld. If not specified, anonymous ' |
| 53 'access will be used.') | 54 'access will be used.') |
| 54 parser.add_option( | 55 parser.add_option( |
| 55 '-E', '--email-file', | 56 '-E', '--email-file', |
| 56 help='File containing the email address to access rietveld. ' | 57 help='File containing the email address to access rietveld. ' |
| 57 'If not specified, anonymous access will be used.') | 58 'If not specified, anonymous access will be used.') |
| 58 parser.add_option( | 59 parser.add_option( |
| 59 '-w', '--password', | |
| 60 help='Password for email addressed. Use - to read password from stdin. ' | |
| 61 'if -k is provided, this is the private key file password.') | |
| 62 parser.add_option( | |
| 63 '-k', '--private-key-file', | 60 '-k', '--private-key-file', |
| 64 help='Path to file containing a private key in p12 format for OAuth2 ' | 61 help='Path to file containing a private key in p12 format for OAuth2 ' |
| 65 'authentication. Use -w to provide the decrypting password, if any.') | 62 'authentication with "notasecret" password (as generated by Google ' |
| 63 'Cloud Console).') | |
| 66 parser.add_option( | 64 parser.add_option( |
| 67 '-i', '--issue', type='int', help='Rietveld issue number') | 65 '-i', '--issue', type='int', help='Rietveld issue number') |
| 68 parser.add_option( | 66 parser.add_option( |
| 69 '-p', '--patchset', type='int', help='Rietveld issue\'s patchset number') | 67 '-p', '--patchset', type='int', help='Rietveld issue\'s patchset number') |
| 70 parser.add_option( | 68 parser.add_option( |
| 71 '-r', | 69 '-r', |
| 72 '--root_dir', | 70 '--root_dir', |
| 73 default=os.getcwd(), | 71 default=os.getcwd(), |
| 74 help='Root directory to apply the patch') | 72 help='Root directory to apply the patch') |
| 75 parser.add_option( | 73 parser.add_option( |
| 76 '-s', | 74 '-s', |
| 77 '--server', | 75 '--server', |
| 78 default='http://codereview.chromium.org', | 76 default='http://codereview.chromium.org', |
| 79 help='Rietveld server') | 77 help='Rietveld server') |
| 80 parser.add_option('--no-auth', action='store_true', | 78 parser.add_option('--no-auth', action='store_true', |
| 81 help='Do not attempt authenticated requests.') | 79 help='Do not attempt authenticated requests.') |
| 82 parser.add_option('--revision-mapping', default='{}', | 80 parser.add_option('--revision-mapping', default='{}', |
| 83 help='When running gclient, annotate the got_revisions ' | 81 help='When running gclient, annotate the got_revisions ' |
| 84 'using the revision-mapping.') | 82 'using the revision-mapping.') |
| 85 parser.add_option('-f', '--force', action='store_true', | 83 parser.add_option('-f', '--force', action='store_true', |
| 86 help='Really run apply_issue, even if .update.flag ' | 84 help='Really run apply_issue, even if .update.flag ' |
| 87 'is detected.') | 85 'is detected.') |
| 88 parser.add_option('-b', '--base_ref', help='DEPRECATED do not use.') | 86 parser.add_option('-b', '--base_ref', help='DEPRECATED do not use.') |
| 89 parser.add_option('--whitelist', action='append', default=[], | 87 parser.add_option('--whitelist', action='append', default=[], |
| 90 help='Patch only specified file(s).') | 88 help='Patch only specified file(s).') |
| 91 parser.add_option('--blacklist', action='append', default=[], | 89 parser.add_option('--blacklist', action='append', default=[], |
| 92 help='Don\'t patch specified file(s).') | 90 help='Don\'t patch specified file(s).') |
| 93 parser.add_option('-d', '--ignore_deps', action='store_true', | 91 parser.add_option('-d', '--ignore_deps', action='store_true', |
| 94 help='Don\'t run gclient sync on DEPS changes.') | 92 help='Don\'t run gclient sync on DEPS changes.') |
| 93 | |
| 94 auth.add_auth_options(parser) | |
| 95 options, args = parser.parse_args() | 95 options, args = parser.parse_args() |
| 96 auth_config = auth.extract_auth_config_from_options(options) | |
| 96 | 97 |
| 97 if options.whitelist and options.blacklist: | 98 if options.whitelist and options.blacklist: |
| 98 parser.error('Cannot specify both --whitelist and --blacklist') | 99 parser.error('Cannot specify both --whitelist and --blacklist') |
| 99 | 100 |
| 100 if options.password and options.private_key_file: | |
| 101 parser.error('-k and -w options are incompatible') | |
| 102 if options.email and options.email_file: | 101 if options.email and options.email_file: |
| 103 parser.error('-e and -E options are incompatible') | 102 parser.error('-e and -E options are incompatible') |
| 104 | 103 |
| 105 if (os.path.isfile(os.path.join(os.getcwd(), 'update.flag')) | 104 if (os.path.isfile(os.path.join(os.getcwd(), 'update.flag')) |
| 106 and not options.force): | 105 and not options.force): |
| 107 print 'update.flag file found: bot_update has run and checkout is already ' | 106 print 'update.flag file found: bot_update has run and checkout is already ' |
| 108 print 'in a consistent state. No actions will be performed in this step.' | 107 print 'in a consistent state. No actions will be performed in this step.' |
| 109 return 0 | 108 return 0 |
| 110 logging.basicConfig( | 109 logging.basicConfig( |
| 111 format='%(levelname)5s %(module)11s(%(lineno)4d): %(message)s', | 110 format='%(levelname)5s %(module)11s(%(lineno)4d): %(message)s', |
| 112 level=[logging.WARNING, logging.INFO, logging.DEBUG][ | 111 level=[logging.WARNING, logging.INFO, logging.DEBUG][ |
| 113 min(2, options.verbose)]) | 112 min(2, options.verbose)]) |
| 114 if args: | 113 if args: |
| 115 parser.error('Extra argument(s) "%s" not understood' % ' '.join(args)) | 114 parser.error('Extra argument(s) "%s" not understood' % ' '.join(args)) |
| 116 if not options.issue: | 115 if not options.issue: |
| 117 parser.error('Require --issue') | 116 parser.error('Require --issue') |
| 118 options.server = options.server.rstrip('/') | 117 options.server = options.server.rstrip('/') |
| 119 if not options.server: | 118 if not options.server: |
| 120 parser.error('Require a valid server') | 119 parser.error('Require a valid server') |
| 121 | 120 |
| 122 options.revision_mapping = json.loads(options.revision_mapping) | 121 options.revision_mapping = json.loads(options.revision_mapping) |
| 123 | 122 |
| 124 if options.password == '-': | |
| 125 print('Reading password') | |
| 126 options.password = sys.stdin.readline().strip() | |
| 127 | |
| 128 # read email if needed | 123 # read email if needed |
| 129 if options.email_file: | 124 if options.email_file: |
| 130 if not os.path.exists(options.email_file): | 125 if not os.path.exists(options.email_file): |
| 131 parser.error('file does not exist: %s' % options.email_file) | 126 parser.error('file does not exist: %s' % options.email_file) |
| 132 with open(options.email_file, 'rb') as f: | 127 with open(options.email_file, 'rb') as f: |
| 133 options.email = f.read().strip() | 128 options.email = f.read().strip() |
| 134 | 129 |
| 135 print('Connecting to %s' % options.server) | 130 print('Connecting to %s' % options.server) |
| 136 # Always try un-authenticated first, except for OAuth2 | 131 # Always try un-authenticated first, except for OAuth2 |
| 137 if options.private_key_file: | 132 if options.private_key_file: |
| 138 # OAuth2 authentication | 133 # OAuth2 authentication |
| 139 obj = rietveld.JwtOAuth2Rietveld(options.server, | 134 obj = rietveld.JwtOAuth2Rietveld(options.server, |
| 140 options.email, | 135 options.email, |
| 141 options.private_key_file, | 136 options.private_key_file) |
| 142 private_key_password=options.password) | |
| 143 properties = obj.get_issue_properties(options.issue, False) | 137 properties = obj.get_issue_properties(options.issue, False) |
| 144 else: | 138 else: |
| 145 obj = rietveld.Rietveld(options.server, '', None) | 139 # Passing None as auth_config disables authentication. |
| 140 obj = rietveld.Rietveld(options.server, None) | |
| 146 properties = None | 141 properties = None |
| 147 # Bad except clauses order (HTTPError is an ancestor class of | 142 # Bad except clauses order (HTTPError is an ancestor class of |
| 148 # ClientLoginError) | 143 # ClientLoginError) |
| 149 # pylint: disable=E0701 | 144 # pylint: disable=E0701 |
| 150 try: | 145 try: |
| 151 properties = obj.get_issue_properties(options.issue, False) | 146 properties = obj.get_issue_properties(options.issue, False) |
| 152 except urllib2.HTTPError as e: | 147 except urllib2.HTTPError as e: |
| 153 if e.getcode() != 302: | 148 if e.getcode() != 302: |
| 154 raise | 149 raise |
| 155 if options.no_auth: | 150 if options.no_auth: |
|
Vadim Sh.
2015/04/09 01:04:38
I do not fully understand this logic... I suspect
| |
| 156 exit('FAIL: Login detected -- is issue private?') | 151 exit('FAIL: Login detected -- is issue private?') |
| 157 # TODO(maruel): A few 'Invalid username or password.' are printed first, | 152 # TODO(maruel): A few 'Invalid username or password.' are printed first, |
| 158 # we should get rid of those. | 153 # we should get rid of those. |
| 159 except rietveld.upload.ClientLoginError, e: | 154 except rietveld.upload.ClientLoginError, e: |
| 160 # Fine, we'll do proper authentication. | 155 # Fine, we'll do proper authentication. |
| 161 pass | 156 pass |
| 162 if properties is None: | 157 if properties is None: |
| 163 if options.email is not None: | 158 obj = rietveld.Rietveld(options.server, auth_config, options.email) |
| 164 obj = rietveld.Rietveld(options.server, options.email, options.password) | 159 try: |
| 165 try: | 160 properties = obj.get_issue_properties(options.issue, False) |
| 166 properties = obj.get_issue_properties(options.issue, False) | 161 except rietveld.upload.ClientLoginError, e: |
|
M-A Ruel
2015/04/09 01:20:59
except rietveld.upload.ClientLoginError as e:
Vadim Sh.
2015/04/09 01:34:29
Done.
| |
| 167 except rietveld.upload.ClientLoginError, e: | 162 print('Accessing the issue requires proper credentials.') |
| 168 if sys.stdout.closed: | 163 return 1 |
| 169 print('Accessing the issue requires proper credentials.') | |
| 170 return 1 | |
| 171 else: | |
| 172 print('Accessing the issue requires login.') | |
| 173 obj = rietveld.Rietveld(options.server, None, None) | |
| 174 try: | |
| 175 properties = obj.get_issue_properties(options.issue, False) | |
| 176 except rietveld.upload.ClientLoginError, e: | |
| 177 print('Accessing the issue requires proper credentials.') | |
| 178 return 1 | |
| 179 | 164 |
| 180 if not options.patchset: | 165 if not options.patchset: |
| 181 options.patchset = properties['patchsets'][-1] | 166 options.patchset = properties['patchsets'][-1] |
| 182 print('No patchset specified. Using patchset %d' % options.patchset) | 167 print('No patchset specified. Using patchset %d' % options.patchset) |
| 183 | 168 |
| 184 print('Downloading the patch.') | 169 print('Downloading the patch.') |
| 185 try: | 170 try: |
| 186 patchset = obj.get_patch(options.issue, options.patchset) | 171 patchset = obj.get_patch(options.issue, options.patchset) |
| 187 except urllib2.HTTPError, e: | 172 except urllib2.HTTPError, e: |
| 188 print( | 173 print( |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 260 return 0 | 245 return 0 |
| 261 | 246 |
| 262 | 247 |
| 263 if __name__ == "__main__": | 248 if __name__ == "__main__": |
| 264 fix_encoding.fix_encoding() | 249 fix_encoding.fix_encoding() |
| 265 try: | 250 try: |
| 266 sys.exit(main()) | 251 sys.exit(main()) |
| 267 except KeyboardInterrupt: | 252 except KeyboardInterrupt: |
| 268 sys.stderr.write('interrupted\n') | 253 sys.stderr.write('interrupted\n') |
| 269 sys.exit(1) | 254 sys.exit(1) |
| OLD | NEW |