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: |
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 as 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 as e: |
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 as e: |
188 print( | 173 print( |
189 'Failed to fetch the patch for issue %d, patchset %d.\n' | 174 'Failed to fetch the patch for issue %d, patchset %d.\n' |
190 'Try visiting %s/%d') % ( | 175 'Try visiting %s/%d') % ( |
191 options.issue, options.patchset, | 176 options.issue, options.patchset, |
192 options.server, options.issue) | 177 options.server, options.issue) |
193 return 1 | 178 return 1 |
194 if options.whitelist: | 179 if options.whitelist: |
195 patchset.patches = [patch for patch in patchset.patches | 180 patchset.patches = [patch for patch in patchset.patches |
196 if patch.filename in options.whitelist] | 181 if patch.filename in options.whitelist] |
197 if options.blacklist: | 182 if options.blacklist: |
(...skipping 17 matching lines...) Expand all Loading... |
215 # modified. | 200 # modified. |
216 if options.root_dir == 'src' and getpass.getuser() == 'chrome-bot': | 201 if options.root_dir == 'src' and getpass.getuser() == 'chrome-bot': |
217 # See sourcedirIsPatched() in: | 202 # See sourcedirIsPatched() in: |
218 # http://src.chromium.org/viewvc/chrome/trunk/tools/build/scripts/slave/ | 203 # http://src.chromium.org/viewvc/chrome/trunk/tools/build/scripts/slave/ |
219 # chromium_commands.py?view=markup | 204 # chromium_commands.py?view=markup |
220 open('.buildbot-patched', 'w').close() | 205 open('.buildbot-patched', 'w').close() |
221 | 206 |
222 print('\nApplying the patch.') | 207 print('\nApplying the patch.') |
223 try: | 208 try: |
224 scm_obj.apply_patch(patchset, verbose=True) | 209 scm_obj.apply_patch(patchset, verbose=True) |
225 except checkout.PatchApplicationFailed, e: | 210 except checkout.PatchApplicationFailed as e: |
226 print(str(e)) | 211 print(str(e)) |
227 print('CWD=%s' % os.getcwd()) | 212 print('CWD=%s' % os.getcwd()) |
228 print('Checkout path=%s' % scm_obj.project_path) | 213 print('Checkout path=%s' % scm_obj.project_path) |
229 return 1 | 214 return 1 |
230 | 215 |
231 if ('DEPS' in map(os.path.basename, patchset.filenames) | 216 if ('DEPS' in map(os.path.basename, patchset.filenames) |
232 and not options.ignore_deps): | 217 and not options.ignore_deps): |
233 gclient_root = gclient_utils.FindGclientRoot(full_dir) | 218 gclient_root = gclient_utils.FindGclientRoot(full_dir) |
234 if gclient_root and scm_type: | 219 if gclient_root and scm_type: |
235 print( | 220 print( |
(...skipping 24 matching lines...) Expand all 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 |