| Index: presubmit_canned_checks.py
|
| diff --git a/presubmit_canned_checks.py b/presubmit_canned_checks.py
|
| index 25a3460212422783dde6e84a8752bb32ee5e5cbe..01ae510bb16968e8bcbc6c69452cb8867e40770d 100644
|
| --- a/presubmit_canned_checks.py
|
| +++ b/presubmit_canned_checks.py
|
| @@ -629,35 +629,37 @@ def CheckBuildbotPendingBuilds(input_api, output_api, url, max_pendings,
|
| return []
|
|
|
|
|
| -def CheckOwners(input_api, output_api, email_regexp=None,
|
| - source_file_filter=None):
|
| - affected_files = set([f.LocalPath() for f in
|
| - input_api.change.AffectedFiles(source_file_filter)])
|
| - owners_db = input_api.owners_db
|
| - if email_regexp:
|
| - owners_db.email_regexp = input_api.re.compile(email_regexp)
|
| -
|
| - if input_api.is_committing and input_api.tbr:
|
| - return [output_api.PresubmitNotifyResult(
|
| - '--tbr was specified, skipping OWNERS check')]
|
| - elif input_api.is_committing:
|
| - approvers = _Approvers(input_api, owners_db.email_regexp)
|
| - missing_files = owners_db.files_not_covered_by(affected_files, approvers)
|
| - if missing_files:
|
| - return [output_api.PresubmitError('Missing LGTM from an OWNER for: %s' %
|
| - ','.join(missing_files))]
|
| - return []
|
| - elif input_api.change.tags.get('R'):
|
| +def CheckOwners(input_api, output_api, source_file_filter=None):
|
| + if not input_api.is_committing:
|
| return []
|
| + if input_api.tbr:
|
| + return [output_api.PresubmitNotifyResult('--tbr was specified, '
|
| + 'skipping OWNERS check')]
|
| + if not input_api.change.issue:
|
| + return [output_api.PresubmitError('Change not uploaded for review')]
|
|
|
| - suggested_reviewers = owners_db.reviewers_for(affected_files)
|
| - return [output_api.PresubmitAddReviewers(suggested_reviewers)]
|
| + affected_files = set([f.LocalPath() for f in
|
| + input_api.change.AffectedFiles(source_file_filter)])
|
|
|
| + owners_db = input_api.owners_db
|
| + owner_email, approvers = _RietveldOwnerAndApprovers(input_api,
|
| + owners_db.email_regexp)
|
| + approvers_plus_owner = approvers.union(set([owner_email]))
|
| +
|
| + missing_files = owners_db.files_not_covered_by(affected_files,
|
| + approvers_plus_owner)
|
| + if missing_files:
|
| + return [output_api.PresubmitError('Missing LGTM from an OWNER for: %s' %
|
| + ','.join(missing_files))]
|
| +
|
| + if not approvers:
|
| + return [output_api.PresubmitError('Missing LGTM from someone other than %s'
|
| + % owner_email)]
|
| + return []
|
|
|
| -def _Approvers(input_api, email_regexp):
|
| - if not input_api.change.issue:
|
| - return []
|
|
|
| +def _RietveldOwnerAndApprovers(input_api, email_regexp):
|
| + """Return the owner and approvers of a change, if any."""
|
| # TODO(dpranke): Should figure out if input_api.host_url is supposed to
|
| # be a host or a scheme+host and normalize it there.
|
| host = input_api.host_url
|
| @@ -667,19 +669,56 @@ def _Approvers(input_api, email_regexp):
|
|
|
| f = input_api.urllib2.urlopen(url)
|
| issue_props = input_api.json.load(f)
|
| - owner = input_api.re.escape(issue_props['owner'])
|
| + messages = issue_props.get('messages', [])
|
| + owner_email = issue_props['owner_email']
|
| + owner_regexp = input_api.re.compile(input_api.re.escape(owner_email))
|
| + approvers = _GetApprovers(messages, email_regexp, owner_regexp)
|
| +
|
| + return (owner_email, set(approvers))
|
| +
|
| +
|
| +def _IsApprovingComment(text):
|
| + """Implements the logic for parsing a change comment for approval."""
|
| +
|
| + # Any comment that contains a non-quoted line containing an 'lgtm' is an
|
| + # approval.
|
| + #
|
| + # TODO(dpranke): this differs from the logic used inside Google in a few
|
| + # ways. Inside Google,
|
| + #
|
| + # 1) the approving phrase must appear at the beginning of the first non
|
| + # quoted-line in the comment.'
|
| + # 2) "LG", "Looks Good", and "Looks Good to Me" are also acceptable.
|
| + # 3) Subsequent comments from the reviewer can rescind approval, unless
|
| + # the phrase "LGTM++" was used.
|
| + # We should consider implementing some or all of this here.
|
| + for l in text.splitlines():
|
| + l = l.strip().lower()
|
| + if l.startswith('>'):
|
| + continue
|
| +
|
| + if 'lgtm' in l:
|
| + return True
|
| + return False
|
| +
|
| +
|
| +def _GetApprovers(messages, email_regexp, owner_regexp):
|
| + """Returns the set of approvers for a change given the owner and messages.
|
| +
|
| + Messages should be a list of dicts containing 'sender' and 'text' keys."""
|
|
|
| # TODO(dpranke): This mimics the logic in
|
| # /tools/commit-queue/verifiers/reviewer_lgtm.py
|
| # We should share the code and/or remove the check there where it is
|
| # redundant (since the commit queue also enforces the presubmit checks).
|
| def match_reviewer(r):
|
| - return email_regexp.match(r) and not input_api.re.match(owner, r)
|
| + return email_regexp.match(r) and not owner_regexp.match(r)
|
|
|
| approvers = []
|
| - for m in issue_props.get('messages', []):
|
| - if 'lgtm' in m['text'].lower() and match_reviewer(m['sender']):
|
| - approvers.append(m['sender'])
|
| + for m in messages:
|
| + sender = m['sender']
|
| + if _IsApprovingComment(m['text']) and match_reviewer(sender):
|
| + approvers.append(sender)
|
| return set(approvers)
|
|
|
|
|
| @@ -776,10 +815,8 @@ def PanProjectChecks(input_api, output_api,
|
| text_files = lambda x: input_api.FilterSourceFile(x, black_list=black_list,
|
| white_list=white_list)
|
|
|
| - # TODO(dpranke): enable upload as well
|
| - if input_api.is_committing:
|
| - results.extend(input_api.canned_checks.CheckOwners(
|
| - input_api, output_api, source_file_filter=sources))
|
| + results.extend(input_api.canned_checks.CheckOwners(
|
| + input_api, output_api, source_file_filter=sources))
|
|
|
| results.extend(input_api.canned_checks.CheckLongLines(
|
| input_api, output_api, source_file_filter=sources))
|
|
|