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 # Copyright (C) 2008 Evan Martin <martine@danga.com> | 6 # Copyright (C) 2008 Evan Martin <martine@danga.com> |
7 | 7 |
8 """A git-command for integrating reviews on Rietveld and Gerrit.""" | 8 """A git-command for integrating reviews on Rietveld and Gerrit.""" |
9 | 9 |
10 from distutils.version import LooseVersion | 10 from distutils.version import LooseVersion |
(...skipping 1295 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1306 else: | 1306 else: |
1307 # Assume url. | 1307 # Assume url. |
1308 parsed_issue_arg = self._codereview_impl.ParseIssueURL( | 1308 parsed_issue_arg = self._codereview_impl.ParseIssueURL( |
1309 urlparse.urlparse(issue_arg)) | 1309 urlparse.urlparse(issue_arg)) |
1310 if not parsed_issue_arg or not parsed_issue_arg.valid: | 1310 if not parsed_issue_arg or not parsed_issue_arg.valid: |
1311 DieWithError('Failed to parse issue argument "%s". ' | 1311 DieWithError('Failed to parse issue argument "%s". ' |
1312 'Must be an issue number or a valid URL.' % issue_arg) | 1312 'Must be an issue number or a valid URL.' % issue_arg) |
1313 return self._codereview_impl.CMDPatchWithParsedIssue( | 1313 return self._codereview_impl.CMDPatchWithParsedIssue( |
1314 parsed_issue_arg, reject, nocommit, directory) | 1314 parsed_issue_arg, reject, nocommit, directory) |
1315 | 1315 |
| 1316 def CMDUpload(self, options, git_diff_args, orig_args): |
| 1317 """Uploads a change to codereview.""" |
| 1318 if git_diff_args: |
| 1319 # TODO(ukai): is it ok for gerrit case? |
| 1320 base_branch = git_diff_args[0] |
| 1321 else: |
| 1322 if self.GetBranch() is None: |
| 1323 DieWithError('Can\'t upload from detached HEAD state. Get on a branch!') |
| 1324 |
| 1325 # Default to diffing against common ancestor of upstream branch |
| 1326 base_branch = self.GetCommonAncestorWithUpstream() |
| 1327 git_diff_args = [base_branch, 'HEAD'] |
| 1328 |
| 1329 # Make sure authenticated to codereview before running potentially expensive |
| 1330 # hooks. It is a fast, best efforts check. Codereview still can reject the |
| 1331 # authentication during the actual upload. |
| 1332 self._codereview_impl.EnsureAuthenticated() |
| 1333 |
| 1334 # Apply watchlists on upload. |
| 1335 change = self.GetChange(base_branch, None) |
| 1336 watchlist = watchlists.Watchlists(change.RepositoryRoot()) |
| 1337 files = [f.LocalPath() for f in change.AffectedFiles()] |
| 1338 if not options.bypass_watchlists: |
| 1339 self.SetWatchers(watchlist.GetWatchersForPaths(files)) |
| 1340 |
| 1341 if not options.bypass_hooks: |
| 1342 if options.reviewers or options.tbr_owners: |
| 1343 # Set the reviewer list now so that presubmit checks can access it. |
| 1344 change_description = ChangeDescription(change.FullDescriptionText()) |
| 1345 change_description.update_reviewers(options.reviewers, |
| 1346 options.tbr_owners, |
| 1347 change) |
| 1348 change.SetDescriptionText(change_description.description) |
| 1349 hook_results = self.RunHook(committing=False, |
| 1350 may_prompt=not options.force, |
| 1351 verbose=options.verbose, |
| 1352 change=change) |
| 1353 if not hook_results.should_continue(): |
| 1354 return 1 |
| 1355 if not options.reviewers and hook_results.reviewers: |
| 1356 options.reviewers = hook_results.reviewers.split(',') |
| 1357 |
| 1358 if self.GetIssue(): |
| 1359 latest_patchset = self.GetMostRecentPatchset() |
| 1360 local_patchset = self.GetPatchset() |
| 1361 if (latest_patchset and local_patchset and |
| 1362 local_patchset != latest_patchset): |
| 1363 print ('The last upload made from this repository was patchset #%d but ' |
| 1364 'the most recent patchset on the server is #%d.' |
| 1365 % (local_patchset, latest_patchset)) |
| 1366 print ('Uploading will still work, but if you\'ve uploaded to this ' |
| 1367 'issue from another machine or branch the patch you\'re ' |
| 1368 'uploading now might not include those changes.') |
| 1369 ask_for_data('About to upload; enter to confirm.') |
| 1370 |
| 1371 print_stats(options.similarity, options.find_copies, git_diff_args) |
| 1372 ret = self.CMDUploadChange(options, git_diff_args, change) |
| 1373 if not ret: |
| 1374 git_set_branch_value('last-upload-hash', |
| 1375 RunGit(['rev-parse', 'HEAD']).strip()) |
| 1376 # Run post upload hooks, if specified. |
| 1377 if settings.GetRunPostUploadHook(): |
| 1378 presubmit_support.DoPostUploadExecuter( |
| 1379 change, |
| 1380 self, |
| 1381 settings.GetRoot(), |
| 1382 options.verbose, |
| 1383 sys.stdout) |
| 1384 |
| 1385 # Upload all dependencies if specified. |
| 1386 if options.dependencies: |
| 1387 print |
| 1388 print '--dependencies has been specified.' |
| 1389 print 'All dependent local branches will be re-uploaded.' |
| 1390 print |
| 1391 # Remove the dependencies flag from args so that we do not end up in a |
| 1392 # loop. |
| 1393 orig_args.remove('--dependencies') |
| 1394 ret = upload_branch_deps(self, orig_args) |
| 1395 return ret |
| 1396 |
1316 # Forward methods to codereview specific implementation. | 1397 # Forward methods to codereview specific implementation. |
1317 | 1398 |
1318 def CloseIssue(self): | 1399 def CloseIssue(self): |
1319 return self._codereview_impl.CloseIssue() | 1400 return self._codereview_impl.CloseIssue() |
1320 | 1401 |
1321 def GetStatus(self): | 1402 def GetStatus(self): |
1322 return self._codereview_impl.GetStatus() | 1403 return self._codereview_impl.GetStatus() |
1323 | 1404 |
1324 def GetCodereviewServer(self): | 1405 def GetCodereviewServer(self): |
1325 return self._codereview_impl.GetCodereviewServer() | 1406 return self._codereview_impl.GetCodereviewServer() |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1419 directory: switch to directory before applying the patch. Rietveld only. | 1500 directory: switch to directory before applying the patch. Rietveld only. |
1420 """ | 1501 """ |
1421 raise NotImplementedError() | 1502 raise NotImplementedError() |
1422 | 1503 |
1423 @staticmethod | 1504 @staticmethod |
1424 def ParseIssueURL(parsed_url): | 1505 def ParseIssueURL(parsed_url): |
1425 """Parses url and returns instance of _ParsedIssueNumberArgument or None if | 1506 """Parses url and returns instance of _ParsedIssueNumberArgument or None if |
1426 failed.""" | 1507 failed.""" |
1427 raise NotImplementedError() | 1508 raise NotImplementedError() |
1428 | 1509 |
| 1510 def EnsureAuthenticated(self): |
| 1511 """Best effort check that user is authenticated with codereview server.""" |
| 1512 raise NotImplementedError() |
| 1513 |
1429 def CMDUploadChange(self, options, args, change): | 1514 def CMDUploadChange(self, options, args, change): |
1430 """Uploads a change to codereview.""" | 1515 """Uploads a change to codereview.""" |
1431 raise NotImplementedError() | 1516 raise NotImplementedError() |
1432 | 1517 |
1433 | 1518 |
1434 class _RietveldChangelistImpl(_ChangelistCodereviewBase): | 1519 class _RietveldChangelistImpl(_ChangelistCodereviewBase): |
1435 def __init__(self, changelist, auth_config=None, rietveld_server=None): | 1520 def __init__(self, changelist, auth_config=None, rietveld_server=None): |
1436 super(_RietveldChangelistImpl, self).__init__(changelist) | 1521 super(_RietveldChangelistImpl, self).__init__(changelist) |
1437 assert settings, 'must be initialized in _ChangelistCodereviewBase' | 1522 assert settings, 'must be initialized in _ChangelistCodereviewBase' |
1438 settings.GetDefaultServerUrl() | 1523 settings.GetDefaultServerUrl() |
1439 | 1524 |
1440 self._rietveld_server = rietveld_server | 1525 self._rietveld_server = rietveld_server |
1441 self._auth_config = auth_config | 1526 self._auth_config = auth_config |
1442 self._props = None | 1527 self._props = None |
1443 self._rpc_server = None | 1528 self._rpc_server = None |
1444 | 1529 |
1445 def GetCodereviewServer(self): | 1530 def GetCodereviewServer(self): |
1446 if not self._rietveld_server: | 1531 if not self._rietveld_server: |
1447 # If we're on a branch then get the server potentially associated | 1532 # If we're on a branch then get the server potentially associated |
1448 # with that branch. | 1533 # with that branch. |
1449 if self.GetIssue(): | 1534 if self.GetIssue(): |
1450 rietveld_server_setting = self.GetCodereviewServerSetting() | 1535 rietveld_server_setting = self.GetCodereviewServerSetting() |
1451 if rietveld_server_setting: | 1536 if rietveld_server_setting: |
1452 self._rietveld_server = gclient_utils.UpgradeToHttps(RunGit( | 1537 self._rietveld_server = gclient_utils.UpgradeToHttps(RunGit( |
1453 ['config', rietveld_server_setting], error_ok=True).strip()) | 1538 ['config', rietveld_server_setting], error_ok=True).strip()) |
1454 if not self._rietveld_server: | 1539 if not self._rietveld_server: |
1455 self._rietveld_server = settings.GetDefaultServerUrl() | 1540 self._rietveld_server = settings.GetDefaultServerUrl() |
1456 return self._rietveld_server | 1541 return self._rietveld_server |
1457 | 1542 |
| 1543 def EnsureAuthenticated(self): |
| 1544 """Best effort check that user is authenticated with Rietveld server.""" |
| 1545 if self._auth_config.use_oauth2: |
| 1546 authenticator = auth.get_authenticator_for_host( |
| 1547 self.GetCodereviewServer(), self._auth_config) |
| 1548 if not authenticator.has_cached_credentials(): |
| 1549 raise auth.LoginRequiredError(self.GetCodereviewServer()) |
| 1550 |
1458 def FetchDescription(self): | 1551 def FetchDescription(self): |
1459 issue = self.GetIssue() | 1552 issue = self.GetIssue() |
1460 assert issue | 1553 assert issue |
1461 try: | 1554 try: |
1462 return self.RpcServer().get_description(issue).strip() | 1555 return self.RpcServer().get_description(issue).strip() |
1463 except urllib2.HTTPError as e: | 1556 except urllib2.HTTPError as e: |
1464 if e.code == 404: | 1557 if e.code == 404: |
1465 DieWithError( | 1558 DieWithError( |
1466 ('\nWhile fetching the description for issue %d, received a ' | 1559 ('\nWhile fetching the description for issue %d, received a ' |
1467 '404 (not found)\n' | 1560 '404 (not found)\n' |
(...skipping 322 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1790 auth_config = auth.extract_auth_config_from_options(options) | 1883 auth_config = auth.extract_auth_config_from_options(options) |
1791 branch_cl = Changelist(branchref=local_branch, | 1884 branch_cl = Changelist(branchref=local_branch, |
1792 auth_config=auth_config) | 1885 auth_config=auth_config) |
1793 branch_cl_issue_url = branch_cl.GetIssueURL() | 1886 branch_cl_issue_url = branch_cl.GetIssueURL() |
1794 branch_cl_issue = branch_cl.GetIssue() | 1887 branch_cl_issue = branch_cl.GetIssue() |
1795 branch_cl_patchset = branch_cl.GetPatchset() | 1888 branch_cl_patchset = branch_cl.GetPatchset() |
1796 if branch_cl_issue_url and branch_cl_issue and branch_cl_patchset: | 1889 if branch_cl_issue_url and branch_cl_issue and branch_cl_patchset: |
1797 upload_args.extend( | 1890 upload_args.extend( |
1798 ['--depends_on_patchset', '%s:%s' % ( | 1891 ['--depends_on_patchset', '%s:%s' % ( |
1799 branch_cl_issue, branch_cl_patchset)]) | 1892 branch_cl_issue, branch_cl_patchset)]) |
1800 print ( | 1893 print( |
1801 '\n' | 1894 '\n' |
1802 'The current branch (%s) is tracking a local branch (%s) with ' | 1895 'The current branch (%s) is tracking a local branch (%s) with ' |
1803 'an associated CL.\n' | 1896 'an associated CL.\n' |
1804 'Adding %s/#ps%s as a dependency patchset.\n' | 1897 'Adding %s/#ps%s as a dependency patchset.\n' |
1805 '\n' % (self.GetBranch(), local_branch, branch_cl_issue_url, | 1898 '\n' % (self.GetBranch(), local_branch, branch_cl_issue_url, |
1806 branch_cl_patchset)) | 1899 branch_cl_patchset)) |
1807 | 1900 |
1808 project = settings.GetProject() | 1901 project = settings.GetProject() |
1809 if project: | 1902 if project: |
1810 upload_args.extend(['--project', project]) | 1903 upload_args.extend(['--project', project]) |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1871 parts = urlparse.urlparse(self.GetRemoteUrl()).netloc.split('.') | 1964 parts = urlparse.urlparse(self.GetRemoteUrl()).netloc.split('.') |
1872 parts[0] = parts[0] + '-review' | 1965 parts[0] = parts[0] + '-review' |
1873 self._gerrit_host = '.'.join(parts) | 1966 self._gerrit_host = '.'.join(parts) |
1874 self._gerrit_server = 'https://%s' % self._gerrit_host | 1967 self._gerrit_server = 'https://%s' % self._gerrit_host |
1875 return self._gerrit_server | 1968 return self._gerrit_server |
1876 | 1969 |
1877 @classmethod | 1970 @classmethod |
1878 def IssueSettingPrefix(cls): | 1971 def IssueSettingPrefix(cls): |
1879 return 'gerritissue' | 1972 return 'gerritissue' |
1880 | 1973 |
| 1974 def EnsureAuthenticated(self): |
| 1975 """Best effort check that user is authenticated with Gerrit server.""" |
| 1976 #TODO(tandrii): implement per bug http://crbug.com/583153. |
| 1977 |
1881 def PatchsetSetting(self): | 1978 def PatchsetSetting(self): |
1882 """Return the git setting that stores this change's most recent patchset.""" | 1979 """Return the git setting that stores this change's most recent patchset.""" |
1883 return 'branch.%s.gerritpatchset' % self.GetBranch() | 1980 return 'branch.%s.gerritpatchset' % self.GetBranch() |
1884 | 1981 |
1885 def GetCodereviewServerSetting(self): | 1982 def GetCodereviewServerSetting(self): |
1886 """Returns the git setting that stores this change's Gerrit server.""" | 1983 """Returns the git setting that stores this change's Gerrit server.""" |
1887 branch = self.GetBranch() | 1984 branch = self.GetBranch() |
1888 if branch: | 1985 if branch: |
1889 return 'branch.%s.gerritserver' % branch | 1986 return 'branch.%s.gerritserver' % branch |
1890 return None | 1987 return None |
(...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2072 match = re.match('(/c)?/(\d+)(/(\d+)?/?)?$', part) | 2169 match = re.match('(/c)?/(\d+)(/(\d+)?/?)?$', part) |
2073 if match: | 2170 if match: |
2074 return _ParsedIssueNumberArgument( | 2171 return _ParsedIssueNumberArgument( |
2075 issue=int(match.group(2)), | 2172 issue=int(match.group(2)), |
2076 patchset=int(match.group(4)) if match.group(4) else None, | 2173 patchset=int(match.group(4)) if match.group(4) else None, |
2077 hostname=parsed_url.netloc) | 2174 hostname=parsed_url.netloc) |
2078 return None | 2175 return None |
2079 | 2176 |
2080 def CMDUploadChange(self, options, args, change): | 2177 def CMDUploadChange(self, options, args, change): |
2081 """Upload the current branch to Gerrit.""" | 2178 """Upload the current branch to Gerrit.""" |
| 2179 if options.squash and options.no_squash: |
| 2180 DieWithError('Can only use one of --squash or --no-squash') |
| 2181 options.squash = ((settings.GetSquashGerritUploads() or options.squash) and |
| 2182 not options.no_squash) |
2082 # We assume the remote called "origin" is the one we want. | 2183 # We assume the remote called "origin" is the one we want. |
2083 # It is probably not worthwhile to support different workflows. | 2184 # It is probably not worthwhile to support different workflows. |
2084 gerrit_remote = 'origin' | 2185 gerrit_remote = 'origin' |
2085 | 2186 |
2086 remote, remote_branch = self.GetRemoteBranch() | 2187 remote, remote_branch = self.GetRemoteBranch() |
2087 branch = GetTargetRef(remote, remote_branch, options.target_branch, | 2188 branch = GetTargetRef(remote, remote_branch, options.target_branch, |
2088 pending_prefix='') | 2189 pending_prefix='') |
2089 | 2190 |
2090 if options.title: | 2191 if options.title: |
2091 # TODO(tandrii): it's now supported by Gerrit, implement! | 2192 # TODO(tandrii): it's now supported by Gerrit, implement! |
(...skipping 1191 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3283 if git_common.is_dirty_git_tree('upload'): | 3384 if git_common.is_dirty_git_tree('upload'): |
3284 return 1 | 3385 return 1 |
3285 | 3386 |
3286 options.reviewers = cleanup_list(options.reviewers) | 3387 options.reviewers = cleanup_list(options.reviewers) |
3287 options.cc = cleanup_list(options.cc) | 3388 options.cc = cleanup_list(options.cc) |
3288 | 3389 |
3289 # For sanity of test expectations, do this otherwise lazy-loading *now*. | 3390 # For sanity of test expectations, do this otherwise lazy-loading *now*. |
3290 settings.GetIsGerrit() | 3391 settings.GetIsGerrit() |
3291 | 3392 |
3292 cl = Changelist(auth_config=auth_config) | 3393 cl = Changelist(auth_config=auth_config) |
3293 if args: | 3394 return cl.CMDUpload(options, args, orig_args) |
3294 # TODO(ukai): is it ok for gerrit case? | |
3295 base_branch = args[0] | |
3296 else: | |
3297 if cl.GetBranch() is None: | |
3298 DieWithError('Can\'t upload from detached HEAD state. Get on a branch!') | |
3299 | |
3300 # Default to diffing against common ancestor of upstream branch | |
3301 base_branch = cl.GetCommonAncestorWithUpstream() | |
3302 args = [base_branch, 'HEAD'] | |
3303 | |
3304 # Make sure authenticated to Rietveld before running expensive hooks. It is | |
3305 # a fast, best efforts check. Rietveld still can reject the authentication | |
3306 # during the actual upload. | |
3307 if not cl.IsGerrit() and auth_config.use_oauth2: | |
3308 authenticator = auth.get_authenticator_for_host( | |
3309 cl.GetCodereviewServer(), auth_config) | |
3310 if not authenticator.has_cached_credentials(): | |
3311 raise auth.LoginRequiredError(cl.GetCodereviewServer()) | |
3312 | |
3313 # Apply watchlists on upload. | |
3314 change = cl.GetChange(base_branch, None) | |
3315 watchlist = watchlists.Watchlists(change.RepositoryRoot()) | |
3316 files = [f.LocalPath() for f in change.AffectedFiles()] | |
3317 if not options.bypass_watchlists: | |
3318 cl.SetWatchers(watchlist.GetWatchersForPaths(files)) | |
3319 | |
3320 if not options.bypass_hooks: | |
3321 if options.reviewers or options.tbr_owners: | |
3322 # Set the reviewer list now so that presubmit checks can access it. | |
3323 change_description = ChangeDescription(change.FullDescriptionText()) | |
3324 change_description.update_reviewers(options.reviewers, | |
3325 options.tbr_owners, | |
3326 change) | |
3327 change.SetDescriptionText(change_description.description) | |
3328 hook_results = cl.RunHook(committing=False, | |
3329 may_prompt=not options.force, | |
3330 verbose=options.verbose, | |
3331 change=change) | |
3332 if not hook_results.should_continue(): | |
3333 return 1 | |
3334 if not options.reviewers and hook_results.reviewers: | |
3335 options.reviewers = hook_results.reviewers.split(',') | |
3336 | |
3337 if cl.GetIssue(): | |
3338 latest_patchset = cl.GetMostRecentPatchset() | |
3339 local_patchset = cl.GetPatchset() | |
3340 if latest_patchset and local_patchset and local_patchset != latest_patchset: | |
3341 print ('The last upload made from this repository was patchset #%d but ' | |
3342 'the most recent patchset on the server is #%d.' | |
3343 % (local_patchset, latest_patchset)) | |
3344 print ('Uploading will still work, but if you\'ve uploaded to this issue ' | |
3345 'from another machine or branch the patch you\'re uploading now ' | |
3346 'might not include those changes.') | |
3347 ask_for_data('About to upload; enter to confirm.') | |
3348 | |
3349 print_stats(options.similarity, options.find_copies, args) | |
3350 if cl.IsGerrit(): | |
3351 if options.squash and options.no_squash: | |
3352 DieWithError('Can only use one of --squash or --no-squash') | |
3353 | |
3354 options.squash = ((settings.GetSquashGerritUploads() or options.squash) and | |
3355 not options.no_squash) | |
3356 | |
3357 ret = cl.CMDUploadChange(options, args, change) | |
3358 if not ret: | |
3359 git_set_branch_value('last-upload-hash', | |
3360 RunGit(['rev-parse', 'HEAD']).strip()) | |
3361 # Run post upload hooks, if specified. | |
3362 if settings.GetRunPostUploadHook(): | |
3363 presubmit_support.DoPostUploadExecuter( | |
3364 change, | |
3365 cl, | |
3366 settings.GetRoot(), | |
3367 options.verbose, | |
3368 sys.stdout) | |
3369 | |
3370 # Upload all dependencies if specified. | |
3371 if options.dependencies: | |
3372 print | |
3373 print '--dependencies has been specified.' | |
3374 print 'All dependent local branches will be re-uploaded.' | |
3375 print | |
3376 # Remove the dependencies flag from args so that we do not end up in a | |
3377 # loop. | |
3378 orig_args.remove('--dependencies') | |
3379 upload_branch_deps(cl, orig_args) | |
3380 return ret | |
3381 | 3395 |
3382 | 3396 |
3383 def IsSubmoduleMergeCommit(ref): | 3397 def IsSubmoduleMergeCommit(ref): |
3384 # When submodules are added to the repo, we expect there to be a single | 3398 # When submodules are added to the repo, we expect there to be a single |
3385 # non-git-svn merge commit at remote HEAD with a signature comment. | 3399 # non-git-svn merge commit at remote HEAD with a signature comment. |
3386 pattern = '^SVN changes up to revision [0-9]*$' | 3400 pattern = '^SVN changes up to revision [0-9]*$' |
3387 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref] | 3401 cmd = ['rev-list', '--merges', '--grep=%s' % pattern, '%s^!' % ref] |
3388 return RunGit(cmd) != '' | 3402 return RunGit(cmd) != '' |
3389 | 3403 |
3390 | 3404 |
(...skipping 1246 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4637 if __name__ == '__main__': | 4651 if __name__ == '__main__': |
4638 # These affect sys.stdout so do it outside of main() to simplify mocks in | 4652 # These affect sys.stdout so do it outside of main() to simplify mocks in |
4639 # unit testing. | 4653 # unit testing. |
4640 fix_encoding.fix_encoding() | 4654 fix_encoding.fix_encoding() |
4641 setup_color.init() | 4655 setup_color.init() |
4642 try: | 4656 try: |
4643 sys.exit(main(sys.argv[1:])) | 4657 sys.exit(main(sys.argv[1:])) |
4644 except KeyboardInterrupt: | 4658 except KeyboardInterrupt: |
4645 sys.stderr.write('interrupted\n') | 4659 sys.stderr.write('interrupted\n') |
4646 sys.exit(1) | 4660 sys.exit(1) |
OLD | NEW |