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

Side by Side Diff: presubmit_support.py

Issue 1935563002: Revert of Implement owners check in presubmit for Gerrit. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@P300
Patch Set: Created 4 years, 7 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
« no previous file with comments | « presubmit_canned_checks.py ('k') | 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 #!/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 """Enables directory-specific presubmit checks to run at upload and/or commit. 6 """Enables directory-specific presubmit checks to run at upload and/or commit.
7 """ 7 """
8 8
9 __version__ = '1.8.0' 9 __version__ = '1.8.0'
10 10
(...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after
190 190
191 191
192 # Top level object so multiprocessing can pickle 192 # Top level object so multiprocessing can pickle
193 # Public access through OutputApi object. 193 # Public access through OutputApi object.
194 class _MailTextResult(_PresubmitResult): 194 class _MailTextResult(_PresubmitResult):
195 """A warning that should be included in the review request email.""" 195 """A warning that should be included in the review request email."""
196 def __init__(self, *args, **kwargs): 196 def __init__(self, *args, **kwargs):
197 super(_MailTextResult, self).__init__() 197 super(_MailTextResult, self).__init__()
198 raise NotImplementedError() 198 raise NotImplementedError()
199 199
200 class GerritAccessor(object):
201 """Limited Gerrit functionality for canned presubmit checks to work.
202
203 To avoid excessive Gerrit calls, caches the results.
204 """
205
206 def __init__(self, host):
207 self.host = host
208 self.cache = {}
209
210 def _FetchChangeDetail(self, issue):
211 # Separate function to be easily mocked in tests.
212 return gerrit_util.GetChangeDetail(
213 self.host, str(issue),
214 ['ALL_REVISIONS', 'DETAILED_LABELS'])
215
216 def GetChangeInfo(self, issue):
217 """Returns labels and all revisions (patchsets) for this issue.
218
219 The result is a dictionary according to Gerrit REST Api.
220 https://gerrit-review.googlesource.com/Documentation/rest-api.html
221
222 However, API isn't very clear what's inside, so see tests for example.
223 """
224 assert issue
225 cache_key = int(issue)
226 if cache_key not in self.cache:
227 self.cache[cache_key] = self._FetchChangeDetail(issue)
228 return self.cache[cache_key]
229
230 def GetChangeDescription(self, issue, patchset=None):
231 """If patchset is none, fetches current patchset."""
232 info = self.GetChangeInfo(issue)
233 # info is a reference to cache. We'll modify it here adding description to
234 # it to the right patchset, if it is not yet there.
235
236 # Find revision info for the patchset we want.
237 if patchset is not None:
238 for rev, rev_info in info['revisions'].iteritems():
239 if str(rev_info['_number']) == str(patchset):
240 break
241 else:
242 raise Exception('patchset %s doesn\'t exist in issue %s' % (
243 patchset, issue))
244 else:
245 rev = info['current_revision']
246 rev_info = info['revisions'][rev]
247
248 # Updates revision info, which is part of cached issue info.
249 if 'real_description' not in rev_info:
250 rev_info['real_description'] = (
251 gerrit_util.GetChangeDescriptionFromGitiles(
252 rev_info['fetch']['http']['url'], rev))
253 return rev_info['real_description']
254
255 def GetChangeOwner(self, issue):
256 return self.GetChangeInfo(issue)['owner']['email']
257
258 def GetChangeReviewers(self, issue, approving_only=True):
259 # Gerrit has 'approved' sub-section, but it only lists 1 approver.
260 # So, if we look only for approvers, we have to look at all anyway.
261 # Also, assume LGTM means Code-Review label == 2. Other configurations
262 # aren't supported.
263 return [r['email']
264 for r in self.GetChangeInfo(issue)['labels']['Code-Review']['all']
265 if not approving_only or '2' == str(r.get('value', 0))]
266
267 200
268 class OutputApi(object): 201 class OutputApi(object):
269 """An instance of OutputApi gets passed to presubmit scripts so that they 202 """An instance of OutputApi gets passed to presubmit scripts so that they
270 can output various types of results. 203 can output various types of results.
271 """ 204 """
272 PresubmitResult = _PresubmitResult 205 PresubmitResult = _PresubmitResult
273 PresubmitAddReviewers = _PresubmitAddReviewers 206 PresubmitAddReviewers = _PresubmitAddReviewers
274 PresubmitError = _PresubmitError 207 PresubmitError = _PresubmitError
275 PresubmitPromptWarning = _PresubmitPromptWarning 208 PresubmitPromptWarning = _PresubmitPromptWarning
276 PresubmitNotifyResult = _PresubmitNotifyResult 209 PresubmitNotifyResult = _PresubmitNotifyResult
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
325 r".*\b[A-Z0-9_]{2,}$", 258 r".*\b[A-Z0-9_]{2,}$",
326 # SCM (can happen in dual SCM configuration). (Slightly over aggressive) 259 # SCM (can happen in dual SCM configuration). (Slightly over aggressive)
327 r"(|.*[\\\/])\.git[\\\/].*", 260 r"(|.*[\\\/])\.git[\\\/].*",
328 r"(|.*[\\\/])\.svn[\\\/].*", 261 r"(|.*[\\\/])\.svn[\\\/].*",
329 # There is no point in processing a patch file. 262 # There is no point in processing a patch file.
330 r".+\.diff$", 263 r".+\.diff$",
331 r".+\.patch$", 264 r".+\.patch$",
332 ) 265 )
333 266
334 def __init__(self, change, presubmit_path, is_committing, 267 def __init__(self, change, presubmit_path, is_committing,
335 rietveld_obj, verbose, gerrit_obj=None, dry_run=None): 268 rietveld_obj, verbose, dry_run=None):
336 """Builds an InputApi object. 269 """Builds an InputApi object.
337 270
338 Args: 271 Args:
339 change: A presubmit.Change object. 272 change: A presubmit.Change object.
340 presubmit_path: The path to the presubmit script being processed. 273 presubmit_path: The path to the presubmit script being processed.
341 is_committing: True if the change is about to be committed. 274 is_committing: True if the change is about to be committed.
342 rietveld_obj: rietveld.Rietveld client object 275 rietveld_obj: rietveld.Rietveld client object
343 gerrit_obj: provides basic Gerrit codereview functionality.
344 dry_run: if true, some Checks will be skipped.
345 """ 276 """
346 # Version number of the presubmit_support script. 277 # Version number of the presubmit_support script.
347 self.version = [int(x) for x in __version__.split('.')] 278 self.version = [int(x) for x in __version__.split('.')]
348 self.change = change 279 self.change = change
349 self.is_committing = is_committing 280 self.is_committing = is_committing
350 self.rietveld = rietveld_obj 281 self.rietveld = rietveld_obj
351 self.gerrit = gerrit_obj
352 self.dry_run = dry_run 282 self.dry_run = dry_run
353 # TBD 283 # TBD
354 self.host_url = 'http://codereview.chromium.org' 284 self.host_url = 'http://codereview.chromium.org'
355 if self.rietveld: 285 if self.rietveld:
356 self.host_url = self.rietveld.url 286 self.host_url = self.rietveld.url
357 287
358 # We expose various modules and functions as attributes of the input_api 288 # We expose various modules and functions as attributes of the input_api
359 # so that presubmit scripts don't have to import them. 289 # so that presubmit scripts don't have to import them.
360 self.basename = os.path.basename 290 self.basename = os.path.basename
361 self.cPickle = cPickle 291 self.cPickle = cPickle
(...skipping 1050 matching lines...) Expand 10 before | Expand all | Expand 10 after
1412 output_stream.write('** Post Upload Hook Messages **\n') 1342 output_stream.write('** Post Upload Hook Messages **\n')
1413 for result in results: 1343 for result in results:
1414 result.handle(output_stream) 1344 result.handle(output_stream)
1415 output_stream.write('\n') 1345 output_stream.write('\n')
1416 1346
1417 return results 1347 return results
1418 1348
1419 1349
1420 class PresubmitExecuter(object): 1350 class PresubmitExecuter(object):
1421 def __init__(self, change, committing, rietveld_obj, verbose, 1351 def __init__(self, change, committing, rietveld_obj, verbose,
1422 gerrit_obj=None, dry_run=None): 1352 dry_run=None):
1423 """ 1353 """
1424 Args: 1354 Args:
1425 change: The Change object. 1355 change: The Change object.
1426 committing: True if 'gcl commit' is running, False if 'gcl upload' is. 1356 committing: True if 'gcl commit' is running, False if 'gcl upload' is.
1427 rietveld_obj: rietveld.Rietveld client object. 1357 rietveld_obj: rietveld.Rietveld client object.
1428 gerrit_obj: provides basic Gerrit codereview functionality.
1429 dry_run: if true, some Checks will be skipped.
1430 """ 1358 """
1431 self.change = change 1359 self.change = change
1432 self.committing = committing 1360 self.committing = committing
1433 self.rietveld = rietveld_obj 1361 self.rietveld = rietveld_obj
1434 self.gerrit = gerrit_obj
1435 self.verbose = verbose 1362 self.verbose = verbose
1436 self.dry_run = dry_run 1363 self.dry_run = dry_run
1437 1364
1438 def ExecPresubmitScript(self, script_text, presubmit_path): 1365 def ExecPresubmitScript(self, script_text, presubmit_path):
1439 """Executes a single presubmit script. 1366 """Executes a single presubmit script.
1440 1367
1441 Args: 1368 Args:
1442 script_text: The text of the presubmit script. 1369 script_text: The text of the presubmit script.
1443 presubmit_path: The path to the presubmit file (this will be reported via 1370 presubmit_path: The path to the presubmit file (this will be reported via
1444 input_api.PresubmitLocalPath()). 1371 input_api.PresubmitLocalPath()).
1445 1372
1446 Return: 1373 Return:
1447 A list of result objects, empty if no problems. 1374 A list of result objects, empty if no problems.
1448 """ 1375 """
1449 1376
1450 # Change to the presubmit file's directory to support local imports. 1377 # Change to the presubmit file's directory to support local imports.
1451 main_path = os.getcwd() 1378 main_path = os.getcwd()
1452 os.chdir(os.path.dirname(presubmit_path)) 1379 os.chdir(os.path.dirname(presubmit_path))
1453 1380
1454 # Load the presubmit script into context. 1381 # Load the presubmit script into context.
1455 input_api = InputApi(self.change, presubmit_path, self.committing, 1382 input_api = InputApi(self.change, presubmit_path, self.committing,
1456 self.rietveld, self.verbose, 1383 self.rietveld, self.verbose,
1457 gerrit_obj=self.gerrit, dry_run=self.dry_run) 1384 dry_run=self.dry_run)
1458 context = {} 1385 context = {}
1459 try: 1386 try:
1460 exec script_text in context 1387 exec script_text in context
1461 except Exception, e: 1388 except Exception, e:
1462 raise PresubmitFailure('"%s" had an exception.\n%s' % (presubmit_path, e)) 1389 raise PresubmitFailure('"%s" had an exception.\n%s' % (presubmit_path, e))
1463 1390
1464 # These function names must change if we make substantial changes to 1391 # These function names must change if we make substantial changes to
1465 # the presubmit API that are not backwards compatible. 1392 # the presubmit API that are not backwards compatible.
1466 if self.committing: 1393 if self.committing:
1467 function_name = 'CheckChangeOnCommit' 1394 function_name = 'CheckChangeOnCommit'
(...skipping 22 matching lines...) Expand all
1490 1417
1491 1418
1492 def DoPresubmitChecks(change, 1419 def DoPresubmitChecks(change,
1493 committing, 1420 committing,
1494 verbose, 1421 verbose,
1495 output_stream, 1422 output_stream,
1496 input_stream, 1423 input_stream,
1497 default_presubmit, 1424 default_presubmit,
1498 may_prompt, 1425 may_prompt,
1499 rietveld_obj, 1426 rietveld_obj,
1500 gerrit_obj=None,
1501 dry_run=None): 1427 dry_run=None):
1502 """Runs all presubmit checks that apply to the files in the change. 1428 """Runs all presubmit checks that apply to the files in the change.
1503 1429
1504 This finds all PRESUBMIT.py files in directories enclosing the files in the 1430 This finds all PRESUBMIT.py files in directories enclosing the files in the
1505 change (up to the repository root) and calls the relevant entrypoint function 1431 change (up to the repository root) and calls the relevant entrypoint function
1506 depending on whether the change is being committed or uploaded. 1432 depending on whether the change is being committed or uploaded.
1507 1433
1508 Prints errors, warnings and notifications. Prompts the user for warnings 1434 Prints errors, warnings and notifications. Prompts the user for warnings
1509 when needed. 1435 when needed.
1510 1436
1511 Args: 1437 Args:
1512 change: The Change object. 1438 change: The Change object.
1513 committing: True if 'gcl commit' is running, False if 'gcl upload' is. 1439 committing: True if 'gcl commit' is running, False if 'gcl upload' is.
1514 verbose: Prints debug info. 1440 verbose: Prints debug info.
1515 output_stream: A stream to write output from presubmit tests to. 1441 output_stream: A stream to write output from presubmit tests to.
1516 input_stream: A stream to read input from the user. 1442 input_stream: A stream to read input from the user.
1517 default_presubmit: A default presubmit script to execute in any case. 1443 default_presubmit: A default presubmit script to execute in any case.
1518 may_prompt: Enable (y/n) questions on warning or error. 1444 may_prompt: Enable (y/n) questions on warning or error.
1519 rietveld_obj: rietveld.Rietveld object. 1445 rietveld_obj: rietveld.Rietveld object.
1520 gerrit_obj: provides basic Gerrit codereview functionality.
1521 dry_run: if true, some Checks will be skipped. 1446 dry_run: if true, some Checks will be skipped.
1522 1447
1523 Warning: 1448 Warning:
1524 If may_prompt is true, output_stream SHOULD be sys.stdout and input_stream 1449 If may_prompt is true, output_stream SHOULD be sys.stdout and input_stream
1525 SHOULD be sys.stdin. 1450 SHOULD be sys.stdin.
1526 1451
1527 Return: 1452 Return:
1528 A PresubmitOutput object. Use output.should_continue() to figure out 1453 A PresubmitOutput object. Use output.should_continue() to figure out
1529 if there were errors or warnings and the caller should abort. 1454 if there were errors or warnings and the caller should abort.
1530 """ 1455 """
1531 old_environ = os.environ 1456 old_environ = os.environ
1532 try: 1457 try:
1533 # Make sure python subprocesses won't generate .pyc files. 1458 # Make sure python subprocesses won't generate .pyc files.
1534 os.environ = os.environ.copy() 1459 os.environ = os.environ.copy()
1535 os.environ['PYTHONDONTWRITEBYTECODE'] = '1' 1460 os.environ['PYTHONDONTWRITEBYTECODE'] = '1'
1536 1461
1537 output = PresubmitOutput(input_stream, output_stream) 1462 output = PresubmitOutput(input_stream, output_stream)
1538 if committing: 1463 if committing:
1539 output.write("Running presubmit commit checks ...\n") 1464 output.write("Running presubmit commit checks ...\n")
1540 else: 1465 else:
1541 output.write("Running presubmit upload checks ...\n") 1466 output.write("Running presubmit upload checks ...\n")
1542 start_time = time.time() 1467 start_time = time.time()
1543 presubmit_files = ListRelevantPresubmitFiles( 1468 presubmit_files = ListRelevantPresubmitFiles(
1544 change.AbsoluteLocalPaths(True), change.RepositoryRoot()) 1469 change.AbsoluteLocalPaths(True), change.RepositoryRoot())
1545 if not presubmit_files and verbose: 1470 if not presubmit_files and verbose:
1546 output.write("Warning, no PRESUBMIT.py found.\n") 1471 output.write("Warning, no PRESUBMIT.py found.\n")
1547 results = [] 1472 results = []
1548 executer = PresubmitExecuter(change, committing, rietveld_obj, verbose, 1473 executer = PresubmitExecuter(change, committing, rietveld_obj, verbose,
1549 gerrit_obj, dry_run) 1474 dry_run=dry_run)
1550 if default_presubmit: 1475 if default_presubmit:
1551 if verbose: 1476 if verbose:
1552 output.write("Running default presubmit script.\n") 1477 output.write("Running default presubmit script.\n")
1553 fake_path = os.path.join(change.RepositoryRoot(), 'PRESUBMIT.py') 1478 fake_path = os.path.join(change.RepositoryRoot(), 'PRESUBMIT.py')
1554 results += executer.ExecPresubmitScript(default_presubmit, fake_path) 1479 results += executer.ExecPresubmitScript(default_presubmit, fake_path)
1555 for filename in presubmit_files: 1480 for filename in presubmit_files:
1556 filename = os.path.abspath(filename) 1481 filename = os.path.abspath(filename)
1557 if verbose: 1482 if verbose:
1558 output.write("Running %s\n" % filename) 1483 output.write("Running %s\n" % filename)
1559 # Accept CRLF presubmit script. 1484 # Accept CRLF presubmit script.
(...skipping 204 matching lines...) Expand 10 before | Expand all | Expand 10 after
1764 "can be passed to this program.") 1689 "can be passed to this program.")
1765 if options.rietveld_email_file: 1690 if options.rietveld_email_file:
1766 with open(options.rietveld_email_file, "rb") as f: 1691 with open(options.rietveld_email_file, "rb") as f:
1767 options.rietveld_email = f.read().strip() 1692 options.rietveld_email = f.read().strip()
1768 1693
1769 change_class, files = load_files(options, args) 1694 change_class, files = load_files(options, args)
1770 if not change_class: 1695 if not change_class:
1771 parser.error('For unversioned directory, <files> is not optional.') 1696 parser.error('For unversioned directory, <files> is not optional.')
1772 logging.info('Found %d file(s).' % len(files)) 1697 logging.info('Found %d file(s).' % len(files))
1773 1698
1774 rietveld_obj, gerrit_obj = None, None 1699 rietveld_obj = None
1775
1776 if options.rietveld_url: 1700 if options.rietveld_url:
1777 # The empty password is permitted: '' is not None. 1701 # The empty password is permitted: '' is not None.
1778 if options.rietveld_private_key_file: 1702 if options.rietveld_private_key_file:
1779 rietveld_obj = rietveld.JwtOAuth2Rietveld( 1703 rietveld_obj = rietveld.JwtOAuth2Rietveld(
1780 options.rietveld_url, 1704 options.rietveld_url,
1781 options.rietveld_email, 1705 options.rietveld_email,
1782 options.rietveld_private_key_file) 1706 options.rietveld_private_key_file)
1783 else: 1707 else:
1784 rietveld_obj = rietveld.CachingRietveld( 1708 rietveld_obj = rietveld.CachingRietveld(
1785 options.rietveld_url, 1709 options.rietveld_url,
1786 auth_config, 1710 auth_config,
1787 options.rietveld_email) 1711 options.rietveld_email)
1788 if options.rietveld_fetch: 1712 if options.rietveld_fetch:
1789 assert options.issue 1713 assert options.issue
1790 props = rietveld_obj.get_issue_properties(options.issue, False) 1714 props = rietveld_obj.get_issue_properties(options.issue, False)
1791 options.author = props['owner_email'] 1715 options.author = props['owner_email']
1792 options.description = props['description'] 1716 options.description = props['description']
1793 logging.info('Got author: "%s"', options.author) 1717 logging.info('Got author: "%s"', options.author)
1794 logging.info('Got description: """\n%s\n"""', options.description) 1718 logging.info('Got description: """\n%s\n"""', options.description)
1795 1719
1796 if options.gerrit_url and options.gerrit_fetch: 1720 if options.gerrit_url and options.gerrit_fetch:
1721 rietveld_obj = None
1797 assert options.issue and options.patchset 1722 assert options.issue and options.patchset
1798 rietveld_obj = None 1723 props = gerrit_util.GetChangeDetail(
1799 gerrit_obj = GerritAccessor(urlparse.urlparse(options.gerrit_url).netloc) 1724 urlparse.urlparse(options.gerrit_url).netloc, str(options.issue),
1800 options.author = gerrit_obj.GetChangeOwner(options.issue) 1725 ['ALL_REVISIONS'])
1801 options.description = gerrit_obj.GetChangeDescription(options.patchset) 1726 options.author = props['owner']['email']
1727 for rev, rev_info in props['revisions'].iteritems():
1728 if str(rev_info['_number']) == str(options.patchset):
1729 options.description = gerrit_util.GetChangeDescriptionFromGitiles(
1730 rev_info['fetch']['http']['url'], rev)
1731 break
1732 else:
1733 print >> sys.stderr, ('Patchset %d was not found in Gerrit issue %d' %
1734 options.patchset, options.issue)
1735 return 2
1802 logging.info('Got author: "%s"', options.author) 1736 logging.info('Got author: "%s"', options.author)
1803 logging.info('Got description: """\n%s\n"""', options.description) 1737 logging.info('Got description: """\n%s\n"""', options.description)
1804 1738
1805 try: 1739 try:
1806 with canned_check_filter(options.skip_canned): 1740 with canned_check_filter(options.skip_canned):
1807 results = DoPresubmitChecks( 1741 results = DoPresubmitChecks(
1808 change_class(options.name, 1742 change_class(options.name,
1809 options.description, 1743 options.description,
1810 options.root, 1744 options.root,
1811 files, 1745 files,
1812 options.issue, 1746 options.issue,
1813 options.patchset, 1747 options.patchset,
1814 options.author, 1748 options.author,
1815 upstream=options.upstream), 1749 upstream=options.upstream),
1816 options.commit, 1750 options.commit,
1817 options.verbose, 1751 options.verbose,
1818 sys.stdout, 1752 sys.stdout,
1819 sys.stdin, 1753 sys.stdin,
1820 options.default_presubmit, 1754 options.default_presubmit,
1821 options.may_prompt, 1755 options.may_prompt,
1822 rietveld_obj, 1756 rietveld_obj,
1823 gerrit_obj,
1824 options.dry_run) 1757 options.dry_run)
1825 return not results.should_continue() 1758 return not results.should_continue()
1826 except NonexistantCannedCheckFilter, e: 1759 except NonexistantCannedCheckFilter, e:
1827 print >> sys.stderr, ( 1760 print >> sys.stderr, (
1828 'Attempted to skip nonexistent canned presubmit check: %s' % e.message) 1761 'Attempted to skip nonexistent canned presubmit check: %s' % e.message)
1829 return 2 1762 return 2
1830 except PresubmitFailure, e: 1763 except PresubmitFailure, e:
1831 print >> sys.stderr, e 1764 print >> sys.stderr, e
1832 print >> sys.stderr, 'Maybe your depot_tools is out of date?' 1765 print >> sys.stderr, 'Maybe your depot_tools is out of date?'
1833 print >> sys.stderr, 'If all fails, contact maruel@' 1766 print >> sys.stderr, 'If all fails, contact maruel@'
1834 return 2 1767 return 2
1835 1768
1836 1769
1837 if __name__ == '__main__': 1770 if __name__ == '__main__':
1838 fix_encoding.fix_encoding() 1771 fix_encoding.fix_encoding()
1839 try: 1772 try:
1840 sys.exit(main()) 1773 sys.exit(main())
1841 except KeyboardInterrupt: 1774 except KeyboardInterrupt:
1842 sys.stderr.write('interrupted\n') 1775 sys.stderr.write('interrupted\n')
1843 sys.exit(1) 1776 sys.exit(1)
OLDNEW
« no previous file with comments | « presubmit_canned_checks.py ('k') | tests/presubmit_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698