| 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 |
| (...skipping 23 matching lines...) Expand all Loading... |
| 34 | 34 |
| 35 def write(self, data): | 35 def write(self, data): |
| 36 self.stream.write(data) | 36 self.stream.write(data) |
| 37 self.stream.flush() | 37 self.stream.flush() |
| 38 | 38 |
| 39 def __getattr__(self, attr): | 39 def __getattr__(self, attr): |
| 40 return getattr(self.stream, attr) | 40 return getattr(self.stream, attr) |
| 41 | 41 |
| 42 | 42 |
| 43 def main(): | 43 def main(): |
| 44 # TODO(pgervais): This function is way too long. Split. |
| 44 sys.stdout = Unbuffered(sys.stdout) | 45 sys.stdout = Unbuffered(sys.stdout) |
| 45 parser = optparse.OptionParser(description=sys.modules[__name__].__doc__) | 46 parser = optparse.OptionParser(description=sys.modules[__name__].__doc__) |
| 46 parser.add_option( | 47 parser.add_option( |
| 47 '-v', '--verbose', action='count', default=0, | 48 '-v', '--verbose', action='count', default=0, |
| 48 help='Prints debugging infos') | 49 help='Prints debugging infos') |
| 49 parser.add_option( | 50 parser.add_option( |
| 50 '-e', '--email', | 51 '-e', '--email', |
| 51 help='Email address to access rietveld. If not specified, anonymous ' | 52 help='Email address to access rietveld. If not specified, anonymous ' |
| 52 'access will be used.') | 53 'access will be used.') |
| 53 parser.add_option( | 54 parser.add_option( |
| 54 '-w', '--password', default=None, | 55 '-E', '--email-file', |
| 55 help='Password for email addressed. Use - to read password from stdin.') | 56 help='File containing the email address to access rietveld. ' |
| 57 'If not specified, anonymous access will be used.') |
| 58 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', |
| 64 help='Path to file containing a private key in p12 format for OAuth2 ' |
| 65 'authentication. Use -w to provide the decrypting password, if any.') |
| 56 parser.add_option( | 66 parser.add_option( |
| 57 '-i', '--issue', type='int', help='Rietveld issue number') | 67 '-i', '--issue', type='int', help='Rietveld issue number') |
| 58 parser.add_option( | 68 parser.add_option( |
| 59 '-p', '--patchset', type='int', help='Rietveld issue\'s patchset number') | 69 '-p', '--patchset', type='int', help='Rietveld issue\'s patchset number') |
| 60 parser.add_option( | 70 parser.add_option( |
| 61 '-r', | 71 '-r', |
| 62 '--root_dir', | 72 '--root_dir', |
| 63 default=os.getcwd(), | 73 default=os.getcwd(), |
| 64 help='Root directory to apply the patch') | 74 help='Root directory to apply the patch') |
| 65 parser.add_option( | 75 parser.add_option( |
| 66 '-s', | 76 '-s', |
| 67 '--server', | 77 '--server', |
| 68 default='http://codereview.chromium.org', | 78 default='http://codereview.chromium.org', |
| 69 help='Rietveld server') | 79 help='Rietveld server') |
| 70 parser.add_option('--no-auth', action='store_true', | 80 parser.add_option('--no-auth', action='store_true', |
| 71 help='Do not attempt authenticated requests.') | 81 help='Do not attempt authenticated requests.') |
| 72 parser.add_option('--revision-mapping', default='{}', | 82 parser.add_option('--revision-mapping', default='{}', |
| 73 help='When running gclient, annotate the got_revisions ' | 83 help='When running gclient, annotate the got_revisions ' |
| 74 'using the revision-mapping.') | 84 'using the revision-mapping.') |
| 75 parser.add_option('-f', '--force', action='store_true', | 85 parser.add_option('-f', '--force', action='store_true', |
| 76 help='Really run apply_issue, even if .update.flag ' | 86 help='Really run apply_issue, even if .update.flag ' |
| 77 'is detected.') | 87 'is detected.') |
| 78 parser.add_option('-b', '--base_ref', help='Base git ref to patch on top of, ' | 88 parser.add_option('-b', '--base_ref', help='Base git ref to patch on top of, ' |
| 79 'used for verification.') | 89 'used for verification.') |
| 80 options, args = parser.parse_args() | 90 options, args = parser.parse_args() |
| 91 |
| 92 if options.password and options.private_key_file: |
| 93 parser.error('-k and -w options are incompatible') |
| 94 if options.email and options.email_file: |
| 95 parser.error('-e and -E options are incompatible') |
| 96 |
| 81 if (os.path.isfile(os.path.join(os.getcwd(), 'update.flag')) | 97 if (os.path.isfile(os.path.join(os.getcwd(), 'update.flag')) |
| 82 and not options.force): | 98 and not options.force): |
| 83 print 'update.flag file found: bot_update has run and checkout is already ' | 99 print 'update.flag file found: bot_update has run and checkout is already ' |
| 84 print 'in a consistent state. No actions will be performed in this step.' | 100 print 'in a consistent state. No actions will be performed in this step.' |
| 85 return 0 | 101 return 0 |
| 86 logging.basicConfig( | 102 logging.basicConfig( |
| 87 format='%(levelname)5s %(module)11s(%(lineno)4d): %(message)s', | 103 format='%(levelname)5s %(module)11s(%(lineno)4d): %(message)s', |
| 88 level=[logging.WARNING, logging.INFO, logging.DEBUG][ | 104 level=[logging.WARNING, logging.INFO, logging.DEBUG][ |
| 89 min(2, options.verbose)]) | 105 min(2, options.verbose)]) |
| 90 if args: | 106 if args: |
| 91 parser.error('Extra argument(s) "%s" not understood' % ' '.join(args)) | 107 parser.error('Extra argument(s) "%s" not understood' % ' '.join(args)) |
| 92 if not options.issue: | 108 if not options.issue: |
| 93 parser.error('Require --issue') | 109 parser.error('Require --issue') |
| 94 options.server = options.server.rstrip('/') | 110 options.server = options.server.rstrip('/') |
| 95 if not options.server: | 111 if not options.server: |
| 96 parser.error('Require a valid server') | 112 parser.error('Require a valid server') |
| 97 | 113 |
| 98 options.revision_mapping = json.loads(options.revision_mapping) | 114 options.revision_mapping = json.loads(options.revision_mapping) |
| 99 | 115 |
| 100 if options.password == '-': | 116 if options.password == '-': |
| 101 print('Reading password') | 117 print('Reading password') |
| 102 options.password = sys.stdin.readline().strip() | 118 options.password = sys.stdin.readline().strip() |
| 103 | 119 |
| 120 # read email if needed |
| 121 if options.email_file: |
| 122 if not os.path.exists(options.email_file): |
| 123 parser.error('file does not exist: %s' % options.email_file) |
| 124 with open(options.email_file, 'rb') as f: |
| 125 options.email = f.read().strip() |
| 126 |
| 104 print('Connecting to %s' % options.server) | 127 print('Connecting to %s' % options.server) |
| 105 # Always try un-authenticated first. | 128 # Always try un-authenticated first, except for OAuth2 |
| 106 # TODO(maruel): Use OAuth2 properly so we don't hit rate-limiting on login | 129 if options.private_key_file: |
| 107 # attempts. | 130 # OAuth2 authentication |
| 108 # Bad except clauses order (HTTPError is an ancestor class of | 131 obj = rietveld.JwtOAuth2Rietveld(options.server, |
| 109 # ClientLoginError) | 132 options.email, |
| 110 # pylint: disable=E0701 | 133 options.private_key_file, |
| 111 obj = rietveld.Rietveld(options.server, '', None) | 134 private_key_password=options.password) |
| 112 properties = None | |
| 113 try: | |
| 114 properties = obj.get_issue_properties(options.issue, False) | 135 properties = obj.get_issue_properties(options.issue, False) |
| 115 except urllib2.HTTPError, e: | 136 else: |
| 116 if e.getcode() != 302: | 137 obj = rietveld.Rietveld(options.server, '', None) |
| 117 raise | 138 properties = None |
| 118 elif options.no_auth: | 139 # Bad except clauses order (HTTPError is an ancestor class of |
| 119 exit('FAIL: Login detected -- is issue private?') | 140 # ClientLoginError) |
| 120 # TODO(maruel): A few 'Invalid username or password.' are printed first, we | 141 # pylint: disable=E0701 |
| 121 # should get rid of those. | 142 try: |
| 122 except rietveld.upload.ClientLoginError, e: | 143 properties = obj.get_issue_properties(options.issue, False) |
| 123 # Fine, we'll do proper authentication. | 144 except urllib2.HTTPError as e: |
| 124 pass | 145 if e.getcode() != 302: |
| 125 if properties is None: | 146 raise |
| 126 if options.email is not None: | 147 if options.no_auth: |
| 127 obj = rietveld.Rietveld(options.server, options.email, options.password) | 148 exit('FAIL: Login detected -- is issue private?') |
| 128 try: | 149 # TODO(maruel): A few 'Invalid username or password.' are printed first, |
| 129 properties = obj.get_issue_properties(options.issue, False) | 150 # we should get rid of those. |
| 130 except rietveld.upload.ClientLoginError, e: | 151 except rietveld.upload.ClientLoginError, e: |
| 131 if sys.stdout.closed: | 152 # Fine, we'll do proper authentication. |
| 153 pass |
| 154 if properties is None: |
| 155 if options.email is not None: |
| 156 obj = rietveld.Rietveld(options.server, options.email, options.password) |
| 157 try: |
| 158 properties = obj.get_issue_properties(options.issue, False) |
| 159 except rietveld.upload.ClientLoginError, e: |
| 160 if sys.stdout.closed: |
| 161 print('Accessing the issue requires proper credentials.') |
| 162 return 1 |
| 163 else: |
| 164 print('Accessing the issue requires login.') |
| 165 obj = rietveld.Rietveld(options.server, None, None) |
| 166 try: |
| 167 properties = obj.get_issue_properties(options.issue, False) |
| 168 except rietveld.upload.ClientLoginError, e: |
| 132 print('Accessing the issue requires proper credentials.') | 169 print('Accessing the issue requires proper credentials.') |
| 133 return 1 | 170 return 1 |
| 134 else: | |
| 135 print('Accessing the issue requires login.') | |
| 136 obj = rietveld.Rietveld(options.server, None, None) | |
| 137 try: | |
| 138 properties = obj.get_issue_properties(options.issue, False) | |
| 139 except rietveld.upload.ClientLoginError, e: | |
| 140 print('Accessing the issue requires proper credentials.') | |
| 141 return 1 | |
| 142 | 171 |
| 143 if not options.patchset: | 172 if not options.patchset: |
| 144 options.patchset = properties['patchsets'][-1] | 173 options.patchset = properties['patchsets'][-1] |
| 145 print('No patchset specified. Using patchset %d' % options.patchset) | 174 print('No patchset specified. Using patchset %d' % options.patchset) |
| 146 | 175 |
| 147 print('Downloading the patch.') | 176 print('Downloading the patch.') |
| 148 try: | 177 try: |
| 149 patchset = obj.get_patch(options.issue, options.patchset) | 178 patchset = obj.get_patch(options.issue, options.patchset) |
| 150 except urllib2.HTTPError, e: | 179 except urllib2.HTTPError, e: |
| 151 print( | 180 print( |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 216 f, options.revision_mapping) | 245 f, options.revision_mapping) |
| 217 annotated_gclient.emit_buildprops(revisions) | 246 annotated_gclient.emit_buildprops(revisions) |
| 218 | 247 |
| 219 return retcode | 248 return retcode |
| 220 return 0 | 249 return 0 |
| 221 | 250 |
| 222 | 251 |
| 223 if __name__ == "__main__": | 252 if __name__ == "__main__": |
| 224 fix_encoding.fix_encoding() | 253 fix_encoding.fix_encoding() |
| 225 sys.exit(main()) | 254 sys.exit(main()) |
| OLD | NEW |