OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 self.dry_run, self.gerrit) | 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 Loading... |
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 Loading... |
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) |
OLD | NEW |