Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(513)

Side by Side Diff: presubmit_canned_checks.py

Issue 6730020: Clean up the parsing of approvals for OWNERS checks. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: update w/ review feedback from maruel Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | tests/presubmit_unittest.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2010 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Generic presubmit checks that can be reused by other presubmit checks.""" 5 """Generic presubmit checks that can be reused by other presubmit checks."""
6 6
7 ### Description checks 7 ### Description checks
8 8
9 def CheckChangeHasTestField(input_api, output_api): 9 def CheckChangeHasTestField(input_api, output_api):
10 """Requires that the changelist have a TEST= field.""" 10 """Requires that the changelist have a TEST= field."""
(...skipping 608 matching lines...) Expand 10 before | Expand all | Expand 10 after
619 out.append('%s has %d build(s) pending' % 619 out.append('%s has %d build(s) pending' %
620 (builder_name, pending_builds_len)) 620 (builder_name, pending_builds_len))
621 if out: 621 if out:
622 return [output_api.PresubmitPromptWarning( 622 return [output_api.PresubmitPromptWarning(
623 'Build(s) pending. It is suggested to wait that no more than %d ' 623 'Build(s) pending. It is suggested to wait that no more than %d '
624 'builds are pending.' % max_pendings, 624 'builds are pending.' % max_pendings,
625 long_text='\n'.join(out))] 625 long_text='\n'.join(out))]
626 return [] 626 return []
627 627
628 628
629 def CheckOwners(input_api, output_api, email_regexp=None, 629 def CheckOwners(input_api, output_api, source_file_filter=None):
630 source_file_filter=None): 630 if not input_api.is_committing:
631 return []
632 if input_api.tbr:
633 return [output_api.PresubmitNotifyResult('--tbr was specified, '
634 'skipping OWNERS check')]
635 if not input_api.change.issue:
636 return [output_api.PresubmitError('Change not uploaded for review')]
637
631 affected_files = set([f.LocalPath() for f in 638 affected_files = set([f.LocalPath() for f in
632 input_api.change.AffectedFiles(source_file_filter)]) 639 input_api.change.AffectedFiles(source_file_filter)])
640
633 owners_db = input_api.owners_db 641 owners_db = input_api.owners_db
634 if email_regexp: 642 owner, approvers = _RietveldOwnerAndApprovers(input_api,
635 owners_db.email_regexp = input_api.re.compile(email_regexp) 643 owners_db.email_regexp)
644 approvers_plus_owner = approvers.union(set([owner]))
636 645
637 if input_api.is_committing and input_api.tbr: 646 missing_files = owners_db.files_not_covered_by(affected_files,
638 return [output_api.PresubmitNotifyResult( 647 approvers_plus_owner)
639 '--tbr was specified, skipping OWNERS check')] 648 if missing_files:
640 elif input_api.is_committing: 649 return [output_api.PresubmitError('Missing LGTM from an OWNER for: %s' %
641 approvers = _Approvers(input_api, owners_db.email_regexp) 650 ','.join(missing_files))]
642 missing_files = owners_db.files_not_covered_by(affected_files, approvers)
643 if missing_files:
644 return [output_api.PresubmitError('Missing LGTM from an OWNER for: %s' %
645 ','.join(missing_files))]
646 return []
647 elif input_api.change.tags.get('R'):
648 return []
649 651
650 suggested_reviewers = owners_db.reviewers_for(affected_files) 652 if not approvers:
651 return [output_api.PresubmitAddReviewers(suggested_reviewers)] 653 return [output_api.PresubmitError('Missing LGTM from someone other than %s'
654 % owner)]
655 return []
652 656
653 657
654 def _Approvers(input_api, email_regexp): 658 def _RietveldOwnerAndApprovers(input_api, email_regexp):
655 if not input_api.change.issue: 659 """Return the owner and approvers of a change, if any."""
656 return []
657
658 # TODO(dpranke): Should figure out if input_api.host_url is supposed to 660 # TODO(dpranke): Should figure out if input_api.host_url is supposed to
659 # be a host or a scheme+host and normalize it there. 661 # be a host or a scheme+host and normalize it there.
660 host = input_api.host_url 662 host = input_api.host_url
661 if not host.startswith('http://') and not host.startswith('https://'): 663 if not host.startswith('http://') and not host.startswith('https://'):
662 host = 'http://' + host 664 host = 'http://' + host
663 url = '%s/api/%s?messages=true' % (host, input_api.change.issue) 665 url = '%s/api/%s?messages=true' % (host, input_api.change.issue)
664 666
665 f = input_api.urllib2.urlopen(url) 667 f = input_api.urllib2.urlopen(url)
666 issue_props = input_api.json.load(f) 668 issue_props = input_api.json.load(f)
667 owner = input_api.re.escape(issue_props['owner']) 669 messages = issue_props.get('messages', [])
670 owner = issue_props['owner']
671 owner_regexp = input_api.re.compile(input_api.re.escape(owner))
672 approvers = _GetApprovers(messages, email_regexp, owner_regexp)
673
674 return (owner, set(approvers))
675
676
677 def _IsApprovingComment(text):
678 """Implements the logic for parsing a change comment for approval."""
679
680 # Any comment that contains a line containing an 'lgtm' is an approval.
681 #
682 # TODO(dpranke): this differs from the logic used inside Google in a few
683 # ways. Inside Google,
684 #
685 # 1) the approving phrase must appear at the beginning of the first non
686 # quoted-line in the comment.'
687 # 2) "LG", "Looks Good", and "Looks Good to Me" are also acceptable.
688 # 3) Subsequent comments from the reviewer can rescind approval, unless
689 # the phrase "LGTM++" was used.
690 # We should consider implementing some or all of this here.
691 for l in text.splitlines():
692 l = l.strip().lower()
693 if 'lgtm' in l:
M-A Ruel 2011/03/24 20:27:19 nit: Should we strip lines starting with '>' ?
694 return True
695 return False
696
697
698 def _GetApprovers(messages, email_regexp, owner_regexp):
699 """Returns the set of approvers for a change given the owner and messages.
700
701 Messages should be a list of dicts containing 'sender' and 'text' keys."""
668 702
669 # TODO(dpranke): This mimics the logic in 703 # TODO(dpranke): This mimics the logic in
670 # /tools/commit-queue/verifiers/reviewer_lgtm.py 704 # /tools/commit-queue/verifiers/reviewer_lgtm.py
671 # We should share the code and/or remove the check there where it is 705 # We should share the code and/or remove the check there where it is
672 # redundant (since the commit queue also enforces the presubmit checks). 706 # redundant (since the commit queue also enforces the presubmit checks).
673 def match_reviewer(r): 707 def match_reviewer(r):
674 return email_regexp.match(r) and not input_api.re.match(owner, r) 708 return email_regexp.match(r) and not owner_regexp.match(r)
675 709
676 approvers = [] 710 approvers = []
677 for m in issue_props.get('messages', []): 711 for m in messages:
678 if 'lgtm' in m['text'].lower() and match_reviewer(m['sender']): 712 sender = m['sender']
679 approvers.append(m['sender']) 713 if _IsApprovingComment(m['text']) and match_reviewer(sender):
714 approvers.append(sender)
680 return set(approvers) 715 return set(approvers)
681
OLDNEW
« no previous file with comments | « no previous file | tests/presubmit_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698