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 |