OLD | NEW |
---|---|
1 # coding=utf8 | 1 # coding=utf8 |
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 """Commit queue manager class. | 5 """Commit queue manager class. |
6 | 6 |
7 Security implications: | 7 Security implications: |
8 | 8 |
9 The following hypothesis are made: | 9 The following hypothesis are made: |
10 - Commit queue: | 10 - Commit queue: |
(...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
190 | 190 |
191 raise | 191 raise |
192 | 192 |
193 # If there is an issue in processed_issues that is not in new_issues, | 193 # If there is an issue in processed_issues that is not in new_issues, |
194 # discard it. | 194 # discard it. |
195 for pending in self.queue.iterate(): | 195 for pending in self.queue.iterate(): |
196 # Note that pending.issue is a int but self.queue.pending_commits keys | 196 # Note that pending.issue is a int but self.queue.pending_commits keys |
197 # are str due to json support. | 197 # are str due to json support. |
198 if pending.issue not in new_issues: | 198 if pending.issue not in new_issues: |
199 logging.info('Flushing issue %d' % pending.issue) | 199 logging.info('Flushing issue %d' % pending.issue) |
200 self.context.status.send( | |
201 pending, | |
202 { 'verification': 'abort', | |
203 'payload': { | |
204 'output': 'CQ bit was unchecked on CL. Ignoring.' }}) | |
205 pending.get_state = lambda: base.IGNORED | 200 pending.get_state = lambda: base.IGNORED |
206 self._discard_pending(pending, None) | 201 self._discard_pending(pending, 'CQ bit was unchecked on CL. Ignoring.') |
207 | 202 |
208 # Find new issues. | 203 # Find new issues. |
209 for issue_id in new_issues: | 204 for issue_id in new_issues: |
210 if str(issue_id) not in self.queue.pending_commits: | 205 if str(issue_id) not in self.queue.pending_commits: |
211 try: | 206 try: |
212 issue_data = self.context.rietveld.get_issue_properties( | 207 issue_data = self.context.rietveld.get_issue_properties( |
213 issue_id, True) | 208 issue_id, True) |
214 except urllib2.HTTPError as e: | 209 except urllib2.HTTPError as e: |
215 if e.code in (500, 502, 503): | 210 if e.code in (500, 502, 503): |
216 # Temporary AppEngine hiccup. Just log it and continue. | 211 # Temporary AppEngine hiccup. Just log it and continue. |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
264 # Take in account the case where a verifier was removed. | 259 # Take in account the case where a verifier was removed. |
265 done = set(pending.verifications.keys()) | 260 done = set(pending.verifications.keys()) |
266 missing = expected - done | 261 missing = expected - done |
267 if (not missing or pending.get_state() != base.PROCESSING): | 262 if (not missing or pending.get_state() != base.PROCESSING): |
268 continue | 263 continue |
269 logging.info( | 264 logging.info( |
270 'Processing issue %s (%s, %d)' % ( | 265 'Processing issue %s (%s, %d)' % ( |
271 pending.issue, missing, pending.get_state())) | 266 pending.issue, missing, pending.get_state())) |
272 self._verify_pending(pending) | 267 self._verify_pending(pending) |
273 except base.DiscardPending as e: | 268 except base.DiscardPending as e: |
274 self._discard_pending(e.pending, e.status) | 269 self._discard_pending(e.pending, e.status or |
Paweł Hajdan Jr.
2014/01/28 01:13:46
nit: Indentation in these "or" expressions might b
| |
270 'process_new_pending_commit: ' + self.FAILED_NO_MESSAGE) | |
275 | 271 |
276 def update_status(self): | 272 def update_status(self): |
277 """Updates the status for each pending commit verifier.""" | 273 """Updates the status for each pending commit verifier.""" |
278 why_nots = dict((p.issue, p.why_not()) for p in self.queue.iterate()) | 274 why_nots = dict((p.issue, p.why_not()) for p in self.queue.iterate()) |
279 | 275 |
280 for verifier in self.all_verifiers: | 276 for verifier in self.all_verifiers: |
281 try: | 277 try: |
282 verifier.update_status(self.queue.iterate()) | 278 verifier.update_status(self.queue.iterate()) |
283 except base.DiscardPending as e: | 279 except base.DiscardPending as e: |
284 # It's not efficient since it takes a full loop for each pending | 280 # It's not efficient since it takes a full loop for each pending |
285 # commit to discard. | 281 # commit to discard. |
286 self._discard_pending(e.pending, e.status) | 282 self._discard_pending(e.pending, e.status or |
283 'update_status: ' + self.FAILED_NO_MESSAGE) | |
287 | 284 |
288 for pending in self.queue.iterate(): | 285 for pending in self.queue.iterate(): |
289 why_not = pending.why_not() | 286 why_not = pending.why_not() |
290 if why_nots[pending.issue] != why_not: | 287 if why_nots[pending.issue] != why_not: |
291 self.context.status.send( | 288 self.context.status.send( |
292 pending, | 289 pending, |
293 {'verification': 'why not', | 290 {'verification': 'why not', |
294 'payload': {'message': why_not}}) | 291 'payload': {'message': why_not}}) |
295 | 292 |
296 | 293 |
297 def scan_results(self): | 294 def scan_results(self): |
298 """Scans pending commits that can be committed or discarded.""" | 295 """Scans pending commits that can be committed or discarded.""" |
299 for pending in self.queue.iterate(): | 296 for pending in self.queue.iterate(): |
300 state = pending.get_state() | 297 state = pending.get_state() |
301 if state == base.FAILED: | 298 if state == base.FAILED: |
302 self._discard_pending( | 299 self._discard_pending( |
303 pending, pending.error_message() or self.FAILED_NO_MESSAGE) | 300 pending, pending.error_message() or |
301 'scan_results(FAILED): ' + self.FAILED_NO_MESSAGE) | |
304 elif state == base.SUCCEEDED: | 302 elif state == base.SUCCEEDED: |
305 if self._throttle(pending): | 303 if self._throttle(pending): |
306 continue | 304 continue |
307 try: | 305 try: |
308 # Runs checks. It's be nice to run the test before the postpone, | 306 # Runs checks. It's be nice to run the test before the postpone, |
309 # especially if the tree is closed for a long moment but at the same | 307 # especially if the tree is closed for a long moment but at the same |
310 # time it would keep fetching the rietveld status constantly. | 308 # time it would keep fetching the rietveld status constantly. |
311 self._last_minute_checks(pending) | 309 self._last_minute_checks(pending) |
312 self.context.status.send( | 310 self.context.status.send( |
313 pending, | 311 pending, |
314 {'verification': 'why not', | 312 {'verification': 'why not', |
315 'payload': {'message': ''}}) | 313 'payload': {'message': ''}}) |
316 | 314 |
317 self._commit_patch(pending) | 315 self._commit_patch(pending) |
318 except base.DiscardPending as e: | 316 except base.DiscardPending as e: |
319 self._discard_pending(e.pending, e.status) | 317 self._discard_pending(e.pending, e.status or |
318 'scan_results(commit): ' + self.FAILED_NO_MESSAGE) | |
320 except Exception as e: | 319 except Exception as e: |
321 self._discard_pending(pending, self.INTERNAL_EXCEPTION) | 320 self._discard_pending(pending, self.INTERNAL_EXCEPTION) |
322 raise | 321 raise |
323 else: | 322 else: |
324 # When state is IGNORED, we need to keep this issue so it's not fetched | 323 # When state is IGNORED, we need to keep this issue so it's not fetched |
325 # another time but we can't discard it since we don't want to remove the | 324 # another time but we can't discard it since we don't want to remove the |
326 # commit bit for another project hosted on the same code review | 325 # commit bit for another project hosted on the same code review |
327 # instance. | 326 # instance. |
328 assert state in (base.PROCESSING, base.IGNORED) | 327 assert state in (base.PROCESSING, base.IGNORED) |
329 | 328 |
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
416 pending, | 415 pending, |
417 'List of reviewers changed. %s did a drive-by without LGTM\'ing!' % | 416 'List of reviewers changed. %s did a drive-by without LGTM\'ing!' % |
418 ','.join(drivers_by)) | 417 ','.join(drivers_by)) |
419 if pending.patchset != pending_data['patchsets'][-1]: | 418 if pending.patchset != pending_data['patchsets'][-1]: |
420 raise base.DiscardPending(pending, | 419 raise base.DiscardPending(pending, |
421 'Commit queue failed due to new patchset.') | 420 'Commit queue failed due to new patchset.') |
422 | 421 |
423 def _discard_pending(self, pending, message): | 422 def _discard_pending(self, pending, message): |
424 """Discards a pending commit. Attach an optional message to the review.""" | 423 """Discards a pending commit. Attach an optional message to the review.""" |
425 logging.debug('_discard_pending(%s, %s)', pending.issue, message) | 424 logging.debug('_discard_pending(%s, %s)', pending.issue, message) |
425 msg = message or self.FAILED_NO_MESSAGE | |
426 try: | 426 try: |
427 try: | 427 try: |
428 if pending.get_state() != base.IGNORED: | 428 if pending.get_state() != base.IGNORED: |
429 self.context.rietveld.set_flag( | 429 self.context.rietveld.set_flag( |
430 pending.issue, pending.patchset, 'commit', False) | 430 pending.issue, pending.patchset, 'commit', False) |
431 except urllib2.HTTPError as e: | 431 except urllib2.HTTPError as e: |
432 logging.error( | 432 logging.error( |
433 'Failed to set the flag to False for %s with message %s' % ( | 433 'Failed to set the flag to False for %s with message %s' % ( |
434 pending.pending_name(), message)) | 434 pending.pending_name(), msg)) |
435 traceback.print_stack() | 435 traceback.print_stack() |
436 logging.error(str(e)) | 436 logging.error(str(e)) |
437 errors.send_stack(e) | 437 errors.send_stack(e) |
438 if message: | 438 try: |
439 try: | 439 self.context.rietveld.add_comment(pending.issue, msg) |
440 self.context.rietveld.add_comment(pending.issue, message) | 440 except urllib2.HTTPError as e: |
441 except urllib2.HTTPError as e: | 441 logging.error( |
442 logging.error( | 442 'Failed to add comment for %s with message %s' % ( |
443 'Failed to add comment for %s with message %s' % ( | 443 pending.pending_name(), msg)) |
444 pending.pending_name(), message)) | 444 traceback.print_stack() |
445 traceback.print_stack() | 445 errors.send_stack(e) |
446 errors.send_stack(e) | 446 self.context.status.send( |
447 self.context.status.send( | 447 pending, |
448 pending, | 448 { 'verification': 'abort', |
449 { 'verification': 'abort', | 449 'payload': { |
450 'payload': { | 450 'output': msg }}) |
451 'output': message }}) | |
452 finally: | 451 finally: |
453 # Most importantly, remove the PendingCommit from the queue. | 452 # Most importantly, remove the PendingCommit from the queue. |
454 self.queue.remove(pending.issue) | 453 self.queue.remove(pending.issue) |
455 | 454 |
456 def _commit_patch(self, pending): | 455 def _commit_patch(self, pending): |
457 """Commits the pending patch to the repository. | 456 """Commits the pending patch to the repository. |
458 | 457 |
459 Do the checkout and applies the patch. | 458 Do the checkout and applies the patch. |
460 """ | 459 """ |
461 try: | 460 try: |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
519 self.queue.remove(pending.issue) | 518 self.queue.remove(pending.issue) |
520 except ( | 519 except ( |
521 checkout.PatchApplicationFailed, patch.UnsupportedPatchFormat) as e: | 520 checkout.PatchApplicationFailed, patch.UnsupportedPatchFormat) as e: |
522 raise base.DiscardPending(pending, str(e)) | 521 raise base.DiscardPending(pending, str(e)) |
523 except subprocess2.CalledProcessError as e: | 522 except subprocess2.CalledProcessError as e: |
524 stdout = getattr(e, 'stdout', None) | 523 stdout = getattr(e, 'stdout', None) |
525 out = 'Failed to apply the patch.' | 524 out = 'Failed to apply the patch.' |
526 if stdout: | 525 if stdout: |
527 out += '\n%s' % stdout | 526 out += '\n%s' % stdout |
528 raise base.DiscardPending(pending, out) | 527 raise base.DiscardPending(pending, out) |
528 # Apply default message here | |
Paweł Hajdan Jr.
2014/01/28 01:13:46
nit: This comment line seems to be out of place -
| |
529 except base.DiscardPending as e: | 529 except base.DiscardPending as e: |
530 self._discard_pending(e.pending, e.status) | 530 self._discard_pending(e.pending, e.status or |
531 '_commit_patch: ' + self.FAILED_NO_MESSAGE) | |
531 | 532 |
532 def _throttle(self, pending): | 533 def _throttle(self, pending): |
533 """Returns True if a commit should be delayed.""" | 534 """Returns True if a commit should be delayed.""" |
534 if pending.postpone(): | 535 if pending.postpone(): |
535 self.context.status.send( | 536 self.context.status.send( |
536 pending, | 537 pending, |
537 {'verification': 'why not', | 538 {'verification': 'why not', |
538 'payload': { | 539 'payload': { |
539 'message': pending.why_not()}}) | 540 'message': pending.why_not()}}) |
540 return True | 541 return True |
(...skipping 19 matching lines...) Expand all Loading... | |
560 """Loads the commit queue state from a JSON file.""" | 561 """Loads the commit queue state from a JSON file.""" |
561 self.queue = model.load_from_json_file(filename) | 562 self.queue = model.load_from_json_file(filename) |
562 | 563 |
563 def save(self, filename): | 564 def save(self, filename): |
564 """Save the commit queue state in a simple JSON file.""" | 565 """Save the commit queue state in a simple JSON file.""" |
565 model.save_to_json_file(filename, self.queue) | 566 model.save_to_json_file(filename, self.queue) |
566 | 567 |
567 def close(self): | 568 def close(self): |
568 """Close all the active pending manager items.""" | 569 """Close all the active pending manager items.""" |
569 self.context.status.close() | 570 self.context.status.close() |
OLD | NEW |