|
|
|
@ -197,73 +197,6 @@ class _MailTextResult(_PresubmitResult):
|
|
|
|
|
super(_MailTextResult, self).__init__()
|
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
|
|
class GerritAccessor(object):
|
|
|
|
|
"""Limited Gerrit functionality for canned presubmit checks to work.
|
|
|
|
|
|
|
|
|
|
To avoid excessive Gerrit calls, caches the results.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, host):
|
|
|
|
|
self.host = host
|
|
|
|
|
self.cache = {}
|
|
|
|
|
|
|
|
|
|
def _FetchChangeDetail(self, issue):
|
|
|
|
|
# Separate function to be easily mocked in tests.
|
|
|
|
|
return gerrit_util.GetChangeDetail(
|
|
|
|
|
self.host, str(issue),
|
|
|
|
|
['ALL_REVISIONS', 'DETAILED_LABELS'])
|
|
|
|
|
|
|
|
|
|
def GetChangeInfo(self, issue):
|
|
|
|
|
"""Returns labels and all revisions (patchsets) for this issue.
|
|
|
|
|
|
|
|
|
|
The result is a dictionary according to Gerrit REST Api.
|
|
|
|
|
https://gerrit-review.googlesource.com/Documentation/rest-api.html
|
|
|
|
|
|
|
|
|
|
However, API isn't very clear what's inside, so see tests for example.
|
|
|
|
|
"""
|
|
|
|
|
assert issue
|
|
|
|
|
cache_key = int(issue)
|
|
|
|
|
if cache_key not in self.cache:
|
|
|
|
|
self.cache[cache_key] = self._FetchChangeDetail(issue)
|
|
|
|
|
return self.cache[cache_key]
|
|
|
|
|
|
|
|
|
|
def GetChangeDescription(self, issue, patchset=None):
|
|
|
|
|
"""If patchset is none, fetches current patchset."""
|
|
|
|
|
info = self.GetChangeInfo(issue)
|
|
|
|
|
# info is a reference to cache. We'll modify it here adding description to
|
|
|
|
|
# it to the right patchset, if it is not yet there.
|
|
|
|
|
|
|
|
|
|
# Find revision info for the patchset we want.
|
|
|
|
|
if patchset is not None:
|
|
|
|
|
for rev, rev_info in info['revisions'].iteritems():
|
|
|
|
|
if str(rev_info['_number']) == str(patchset):
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
raise Exception('patchset %s doesn\'t exist in issue %s' % (
|
|
|
|
|
patchset, issue))
|
|
|
|
|
else:
|
|
|
|
|
rev = info['current_revision']
|
|
|
|
|
rev_info = info['revisions'][rev]
|
|
|
|
|
|
|
|
|
|
# Updates revision info, which is part of cached issue info.
|
|
|
|
|
if 'real_description' not in rev_info:
|
|
|
|
|
rev_info['real_description'] = (
|
|
|
|
|
gerrit_util.GetChangeDescriptionFromGitiles(
|
|
|
|
|
rev_info['fetch']['http']['url'], rev))
|
|
|
|
|
return rev_info['real_description']
|
|
|
|
|
|
|
|
|
|
def GetChangeOwner(self, issue):
|
|
|
|
|
return self.GetChangeInfo(issue)['owner']['email']
|
|
|
|
|
|
|
|
|
|
def GetChangeReviewers(self, issue, approving_only=True):
|
|
|
|
|
# Gerrit has 'approved' sub-section, but it only lists 1 approver.
|
|
|
|
|
# So, if we look only for approvers, we have to look at all anyway.
|
|
|
|
|
# Also, assume LGTM means Code-Review label == 2. Other configurations
|
|
|
|
|
# aren't supported.
|
|
|
|
|
return [r['email']
|
|
|
|
|
for r in self.GetChangeInfo(issue)['labels']['Code-Review']['all']
|
|
|
|
|
if not approving_only or '2' == str(r.get('value', 0))]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class OutputApi(object):
|
|
|
|
|
"""An instance of OutputApi gets passed to presubmit scripts so that they
|
|
|
|
@ -332,7 +265,7 @@ class InputApi(object):
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def __init__(self, change, presubmit_path, is_committing,
|
|
|
|
|
rietveld_obj, verbose, gerrit_obj=None, dry_run=None):
|
|
|
|
|
rietveld_obj, verbose, dry_run=None):
|
|
|
|
|
"""Builds an InputApi object.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
@ -340,15 +273,12 @@ class InputApi(object):
|
|
|
|
|
presubmit_path: The path to the presubmit script being processed.
|
|
|
|
|
is_committing: True if the change is about to be committed.
|
|
|
|
|
rietveld_obj: rietveld.Rietveld client object
|
|
|
|
|
gerrit_obj: provides basic Gerrit codereview functionality.
|
|
|
|
|
dry_run: if true, some Checks will be skipped.
|
|
|
|
|
"""
|
|
|
|
|
# Version number of the presubmit_support script.
|
|
|
|
|
self.version = [int(x) for x in __version__.split('.')]
|
|
|
|
|
self.change = change
|
|
|
|
|
self.is_committing = is_committing
|
|
|
|
|
self.rietveld = rietveld_obj
|
|
|
|
|
self.gerrit = gerrit_obj
|
|
|
|
|
self.dry_run = dry_run
|
|
|
|
|
# TBD
|
|
|
|
|
self.host_url = 'http://codereview.chromium.org'
|
|
|
|
@ -1419,19 +1349,16 @@ def DoPostUploadExecuter(change,
|
|
|
|
|
|
|
|
|
|
class PresubmitExecuter(object):
|
|
|
|
|
def __init__(self, change, committing, rietveld_obj, verbose,
|
|
|
|
|
gerrit_obj=None, dry_run=None):
|
|
|
|
|
dry_run=None):
|
|
|
|
|
"""
|
|
|
|
|
Args:
|
|
|
|
|
change: The Change object.
|
|
|
|
|
committing: True if 'gcl commit' is running, False if 'gcl upload' is.
|
|
|
|
|
rietveld_obj: rietveld.Rietveld client object.
|
|
|
|
|
gerrit_obj: provides basic Gerrit codereview functionality.
|
|
|
|
|
dry_run: if true, some Checks will be skipped.
|
|
|
|
|
"""
|
|
|
|
|
self.change = change
|
|
|
|
|
self.committing = committing
|
|
|
|
|
self.rietveld = rietveld_obj
|
|
|
|
|
self.gerrit = gerrit_obj
|
|
|
|
|
self.verbose = verbose
|
|
|
|
|
self.dry_run = dry_run
|
|
|
|
|
|
|
|
|
@ -1454,7 +1381,7 @@ class PresubmitExecuter(object):
|
|
|
|
|
# Load the presubmit script into context.
|
|
|
|
|
input_api = InputApi(self.change, presubmit_path, self.committing,
|
|
|
|
|
self.rietveld, self.verbose,
|
|
|
|
|
self.dry_run, self.gerrit)
|
|
|
|
|
dry_run=self.dry_run)
|
|
|
|
|
context = {}
|
|
|
|
|
try:
|
|
|
|
|
exec script_text in context
|
|
|
|
@ -1497,7 +1424,6 @@ def DoPresubmitChecks(change,
|
|
|
|
|
default_presubmit,
|
|
|
|
|
may_prompt,
|
|
|
|
|
rietveld_obj,
|
|
|
|
|
gerrit_obj=None,
|
|
|
|
|
dry_run=None):
|
|
|
|
|
"""Runs all presubmit checks that apply to the files in the change.
|
|
|
|
|
|
|
|
|
@ -1517,7 +1443,6 @@ def DoPresubmitChecks(change,
|
|
|
|
|
default_presubmit: A default presubmit script to execute in any case.
|
|
|
|
|
may_prompt: Enable (y/n) questions on warning or error.
|
|
|
|
|
rietveld_obj: rietveld.Rietveld object.
|
|
|
|
|
gerrit_obj: provides basic Gerrit codereview functionality.
|
|
|
|
|
dry_run: if true, some Checks will be skipped.
|
|
|
|
|
|
|
|
|
|
Warning:
|
|
|
|
@ -1546,7 +1471,7 @@ def DoPresubmitChecks(change,
|
|
|
|
|
output.write("Warning, no PRESUBMIT.py found.\n")
|
|
|
|
|
results = []
|
|
|
|
|
executer = PresubmitExecuter(change, committing, rietveld_obj, verbose,
|
|
|
|
|
gerrit_obj, dry_run)
|
|
|
|
|
dry_run=dry_run)
|
|
|
|
|
if default_presubmit:
|
|
|
|
|
if verbose:
|
|
|
|
|
output.write("Running default presubmit script.\n")
|
|
|
|
@ -1771,8 +1696,7 @@ def main(argv=None):
|
|
|
|
|
parser.error('For unversioned directory, <files> is not optional.')
|
|
|
|
|
logging.info('Found %d file(s).' % len(files))
|
|
|
|
|
|
|
|
|
|
rietveld_obj, gerrit_obj = None, None
|
|
|
|
|
|
|
|
|
|
rietveld_obj = None
|
|
|
|
|
if options.rietveld_url:
|
|
|
|
|
# The empty password is permitted: '' is not None.
|
|
|
|
|
if options.rietveld_private_key_file:
|
|
|
|
@ -1794,11 +1718,21 @@ def main(argv=None):
|
|
|
|
|
logging.info('Got description: """\n%s\n"""', options.description)
|
|
|
|
|
|
|
|
|
|
if options.gerrit_url and options.gerrit_fetch:
|
|
|
|
|
assert options.issue and options.patchset
|
|
|
|
|
rietveld_obj = None
|
|
|
|
|
gerrit_obj = GerritAccessor(urlparse.urlparse(options.gerrit_url).netloc)
|
|
|
|
|
options.author = gerrit_obj.GetChangeOwner(options.issue)
|
|
|
|
|
options.description = gerrit_obj.GetChangeDescription(options.patchset)
|
|
|
|
|
assert options.issue and options.patchset
|
|
|
|
|
props = gerrit_util.GetChangeDetail(
|
|
|
|
|
urlparse.urlparse(options.gerrit_url).netloc, str(options.issue),
|
|
|
|
|
['ALL_REVISIONS'])
|
|
|
|
|
options.author = props['owner']['email']
|
|
|
|
|
for rev, rev_info in props['revisions'].iteritems():
|
|
|
|
|
if str(rev_info['_number']) == str(options.patchset):
|
|
|
|
|
options.description = gerrit_util.GetChangeDescriptionFromGitiles(
|
|
|
|
|
rev_info['fetch']['http']['url'], rev)
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
print >> sys.stderr, ('Patchset %d was not found in Gerrit issue %d' %
|
|
|
|
|
options.patchset, options.issue)
|
|
|
|
|
return 2
|
|
|
|
|
logging.info('Got author: "%s"', options.author)
|
|
|
|
|
logging.info('Got description: """\n%s\n"""', options.description)
|
|
|
|
|
|
|
|
|
@ -1820,7 +1754,6 @@ def main(argv=None):
|
|
|
|
|
options.default_presubmit,
|
|
|
|
|
options.may_prompt,
|
|
|
|
|
rietveld_obj,
|
|
|
|
|
gerrit_obj,
|
|
|
|
|
options.dry_run)
|
|
|
|
|
return not results.should_continue()
|
|
|
|
|
except NonexistantCannedCheckFilter, e:
|
|
|
|
|