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

Side by Side Diff: git_cl.py

Issue 2409223002: git cl try: make code less Rietveld-specific. (Closed)
Patch Set: Review. Created 4 years, 2 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 | « no previous file | tests/git_cl_test.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 # 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 __future__ import print_function 10 from __future__ import print_function
(...skipping 300 matching lines...) Expand 10 before | Expand all | Expand 10 after
311 if response.status < 500 or try_count >= 2: 311 if response.status < 500 or try_count >= 2:
312 raise httplib2.HttpLib2Error(content) 312 raise httplib2.HttpLib2Error(content)
313 313
314 # status >= 500 means transient failures. 314 # status >= 500 means transient failures.
315 logging.debug('Transient errors when %s. Will retry.', operation_name) 315 logging.debug('Transient errors when %s. Will retry.', operation_name)
316 time.sleep(0.5 + 1.5*try_count) 316 time.sleep(0.5 + 1.5*try_count)
317 try_count += 1 317 try_count += 1
318 assert False, 'unreachable' 318 assert False, 'unreachable'
319 319
320 320
321 def trigger_try_jobs(auth_config, changelist, options, masters, category): 321 def _trigger_try_jobs(auth_config, changelist, masters, options,
322 rietveld_url = settings.GetDefaultServerUrl() 322 category='git_cl_try', patchset=None):
323 rietveld_host = urlparse.urlparse(rietveld_url).hostname 323 assert changelist.GetIssue(), 'CL must be uploaded first'
324 authenticator = auth.get_authenticator_for_host(rietveld_host, auth_config) 324 codereview_url = changelist.GetCodereviewServer()
325 assert codereview_url, 'CL must be uploaded first'
326 patchset = patchset or changelist.GetMostRecentPatchset()
327 assert patchset, 'CL must be uploaded first'
328
329 codereview_host = urlparse.urlparse(codereview_url).hostname
330 authenticator = auth.get_authenticator_for_host(codereview_host, auth_config)
325 http = authenticator.authorize(httplib2.Http()) 331 http = authenticator.authorize(httplib2.Http())
326 http.force_exception_to_status_code = True 332 http.force_exception_to_status_code = True
327 issue_props = changelist.GetIssueProperties() 333
328 issue = changelist.GetIssue() 334 # TODO(tandrii): consider caching Gerrit CL details just like
329 patchset = changelist.GetMostRecentPatchset() 335 # _RietveldChangelistImpl does, then caching values in these two variables
330 properties = _get_properties_from_options(options) 336 # won't be necessary.
337 owner_email = changelist.GetIssueOwner()
338 project = changelist.GetIssueProject()
331 339
332 buildbucket_put_url = ( 340 buildbucket_put_url = (
333 'https://{hostname}/_ah/api/buildbucket/v1/builds/batch'.format( 341 'https://{hostname}/_ah/api/buildbucket/v1/builds/batch'.format(
334 hostname=options.buildbucket_host)) 342 hostname=options.buildbucket_host))
335 buildset = 'patch/rietveld/{hostname}/{issue}/{patch}'.format( 343 buildset = 'patch/{codereview}/{hostname}/{issue}/{patch}'.format(
336 hostname=rietveld_host, 344 codereview='gerrit' if changelist.IsGerrit() else 'rietveld',
337 issue=issue, 345 hostname=codereview_host,
346 issue=changelist.GetIssue(),
338 patch=patchset) 347 patch=patchset)
348 extra_properties = _get_properties_from_options(options)
339 349
340 batch_req_body = {'builds': []} 350 batch_req_body = {'builds': []}
341 print_text = [] 351 print_text = []
342 print_text.append('Tried jobs on:') 352 print_text.append('Tried jobs on:')
343 for master, builders_and_tests in sorted(masters.iteritems()): 353 for master, builders_and_tests in sorted(masters.iteritems()):
344 print_text.append('Master: %s' % master) 354 print_text.append('Master: %s' % master)
345 bucket = _prefix_master(master) 355 bucket = _prefix_master(master)
346 for builder, tests in sorted(builders_and_tests.iteritems()): 356 for builder, tests in sorted(builders_and_tests.iteritems()):
347 print_text.append(' %s: %s' % (builder, tests)) 357 print_text.append(' %s: %s' % (builder, tests))
348 parameters = { 358 parameters = {
349 'builder_name': builder, 359 'builder_name': builder,
350 'changes': [{ 360 'changes': [{
351 'author': {'email': issue_props['owner_email']}, 361 'author': {'email': owner_email},
352 'revision': options.revision, 362 'revision': options.revision,
353 }], 363 }],
354 'properties': { 364 'properties': {
355 'category': category, 365 'category': category,
356 'issue': issue, 366 'issue': changelist.GetIssue(),
357 'master': master, 367 'master': master,
358 'patch_project': issue_props['project'], 368 'patch_project': project,
359 'patch_storage': 'rietveld', 369 'patch_storage': 'rietveld',
360 'patchset': patchset, 370 'patchset': patchset,
361 'reason': options.name, 371 'reason': options.name,
362 'rietveld': rietveld_url, 372 'rietveld': codereview_url,
363 }, 373 },
364 } 374 }
365 if 'presubmit' in builder.lower(): 375 if 'presubmit' in builder.lower():
366 parameters['properties']['dry_run'] = 'true' 376 parameters['properties']['dry_run'] = 'true'
367 if tests: 377 if tests:
368 parameters['properties']['testfilter'] = tests 378 parameters['properties']['testfilter'] = tests
369 if properties: 379 if extra_properties:
370 parameters['properties'].update(properties) 380 parameters['properties'].update(extra_properties)
371 if options.clobber: 381 if options.clobber:
372 parameters['properties']['clobber'] = True 382 parameters['properties']['clobber'] = True
373 batch_req_body['builds'].append( 383 batch_req_body['builds'].append(
374 { 384 {
375 'bucket': bucket, 385 'bucket': bucket,
376 'parameters_json': json.dumps(parameters), 386 'parameters_json': json.dumps(parameters),
377 'client_operation_id': str(uuid.uuid4()), 387 'client_operation_id': str(uuid.uuid4()),
378 'tags': ['builder:%s' % builder, 388 'tags': ['builder:%s' % builder,
379 'buildset:%s' % buildset, 389 'buildset:%s' % buildset,
380 'master:%s' % master, 390 'master:%s' % master,
(...skipping 1160 matching lines...) Expand 10 before | Expand all | Expand 10 after
1541 1551
1542 def SetCQState(self, new_state): 1552 def SetCQState(self, new_state):
1543 """Update the CQ state for latest patchset. 1553 """Update the CQ state for latest patchset.
1544 1554
1545 Issue must have been already uploaded and known. 1555 Issue must have been already uploaded and known.
1546 """ 1556 """
1547 assert new_state in _CQState.ALL_STATES 1557 assert new_state in _CQState.ALL_STATES
1548 assert self.GetIssue() 1558 assert self.GetIssue()
1549 return self._codereview_impl.SetCQState(new_state) 1559 return self._codereview_impl.SetCQState(new_state)
1550 1560
1551 def CannotTriggerTryJobReason(self):
1552 """Returns reason (str) if unable trigger tryjobs on this CL or None."""
1553 return self._codereview_impl.CannotTriggerTryJobReason()
1554
1555 # Forward methods to codereview specific implementation. 1561 # Forward methods to codereview specific implementation.
1556 1562
1557 def CloseIssue(self): 1563 def CloseIssue(self):
1558 return self._codereview_impl.CloseIssue() 1564 return self._codereview_impl.CloseIssue()
1559 1565
1560 def GetStatus(self): 1566 def GetStatus(self):
1561 return self._codereview_impl.GetStatus() 1567 return self._codereview_impl.GetStatus()
1562 1568
1563 def GetCodereviewServer(self): 1569 def GetCodereviewServer(self):
1564 return self._codereview_impl.GetCodereviewServer() 1570 return self._codereview_impl.GetCodereviewServer()
1565 1571
1572 def GetIssueOwner(self):
1573 """Get owner from codereview, which may differ from this checkout."""
1574 return self._codereview_impl.GetIssueOwner()
1575
1576 def GetIssueProject(self):
1577 """Get project from codereview, which may differ from what this
1578 checkout's codereview.settings or gerrit project URL say.
1579 """
1580 return self._codereview_impl.GetIssueProject()
1581
1566 def GetApprovingReviewers(self): 1582 def GetApprovingReviewers(self):
1567 return self._codereview_impl.GetApprovingReviewers() 1583 return self._codereview_impl.GetApprovingReviewers()
1568 1584
1569 def GetMostRecentPatchset(self): 1585 def GetMostRecentPatchset(self):
1570 return self._codereview_impl.GetMostRecentPatchset() 1586 return self._codereview_impl.GetMostRecentPatchset()
1571 1587
1588 def CannotTriggerTryJobReason(self):
1589 """Returns reason (str) if unable trigger tryjobs on this CL or None."""
1590 return self._codereview_impl.CannotTriggerTryJobReason()
1591
1572 def __getattr__(self, attr): 1592 def __getattr__(self, attr):
1573 # This is because lots of untested code accesses Rietveld-specific stuff 1593 # This is because lots of untested code accesses Rietveld-specific stuff
1574 # directly, and it's hard to fix for sure. So, just let it work, and fix 1594 # directly, and it's hard to fix for sure. So, just let it work, and fix
1575 # on a case by case basis. 1595 # on a case by case basis.
1576 # Note that child method defines __getattr__ as well, and forwards it here, 1596 # Note that child method defines __getattr__ as well, and forwards it here,
1577 # because _RietveldChangelistImpl is not cleaned up yet, and given 1597 # because _RietveldChangelistImpl is not cleaned up yet, and given
1578 # deprecation of Rietveld, it should probably be just removed. 1598 # deprecation of Rietveld, it should probably be just removed.
1579 # Until that time, avoid infinite recursion by bypassing __getattr__ 1599 # Until that time, avoid infinite recursion by bypassing __getattr__
1580 # of implementation class. 1600 # of implementation class.
1581 return self._codereview_impl.__getattribute__(attr) 1601 return self._codereview_impl.__getattribute__(attr)
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after
1691 """Update the CQ state for latest patchset. 1711 """Update the CQ state for latest patchset.
1692 1712
1693 Issue must have been already uploaded and known. 1713 Issue must have been already uploaded and known.
1694 """ 1714 """
1695 raise NotImplementedError() 1715 raise NotImplementedError()
1696 1716
1697 def CannotTriggerTryJobReason(self): 1717 def CannotTriggerTryJobReason(self):
1698 """Returns reason (str) if unable trigger tryjobs on this CL or None.""" 1718 """Returns reason (str) if unable trigger tryjobs on this CL or None."""
1699 raise NotImplementedError() 1719 raise NotImplementedError()
1700 1720
1721 def GetIssueOwner(self):
1722 raise NotImplementedError()
1723
1724 def GetIssueProject(self):
1725 raise NotImplementedError()
1726
1701 1727
1702 class _RietveldChangelistImpl(_ChangelistCodereviewBase): 1728 class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1703 def __init__(self, changelist, auth_config=None, rietveld_server=None): 1729 def __init__(self, changelist, auth_config=None, rietveld_server=None):
1704 super(_RietveldChangelistImpl, self).__init__(changelist) 1730 super(_RietveldChangelistImpl, self).__init__(changelist)
1705 assert settings, 'must be initialized in _ChangelistCodereviewBase' 1731 assert settings, 'must be initialized in _ChangelistCodereviewBase'
1706 if not rietveld_server: 1732 if not rietveld_server:
1707 settings.GetDefaultServerUrl() 1733 settings.GetDefaultServerUrl()
1708 1734
1709 self._rietveld_server = rietveld_server 1735 self._rietveld_server = rietveld_server
1710 self._auth_config = auth_config 1736 self._auth_config = auth_config
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
1776 return 'Rietveld doesn\'t know about your issue %s' % self.GetIssue() 1802 return 'Rietveld doesn\'t know about your issue %s' % self.GetIssue()
1777 if props.get('closed'): 1803 if props.get('closed'):
1778 return 'CL %s is closed' % self.GetIssue() 1804 return 'CL %s is closed' % self.GetIssue()
1779 if props.get('private'): 1805 if props.get('private'):
1780 return 'CL %s is private' % self.GetIssue() 1806 return 'CL %s is private' % self.GetIssue()
1781 return None 1807 return None
1782 1808
1783 def GetApprovingReviewers(self): 1809 def GetApprovingReviewers(self):
1784 return get_approving_reviewers(self.GetIssueProperties()) 1810 return get_approving_reviewers(self.GetIssueProperties())
1785 1811
1812 def GetIssueOwner(self):
1813 return (self.GetIssueProperties() or {}).get('owner_email')
1814
1815 def GetIssueProject(self):
1816 return (self.GetIssueProperties() or {}).get('project')
1817
1786 def AddComment(self, message): 1818 def AddComment(self, message):
1787 return self.RpcServer().add_comment(self.GetIssue(), message) 1819 return self.RpcServer().add_comment(self.GetIssue(), message)
1788 1820
1789 def GetStatus(self): 1821 def GetStatus(self):
1790 """Apply a rough heuristic to give a simple summary of an issue's review 1822 """Apply a rough heuristic to give a simple summary of an issue's review
1791 or CQ status, assuming adherence to a common workflow. 1823 or CQ status, assuming adherence to a common workflow.
1792 1824
1793 Returns None if no issue for this branch, or one of the following keywords: 1825 Returns None if no issue for this branch, or one of the following keywords:
1794 * 'error' - error from review tool (including deleted issues) 1826 * 'error' - error from review tool (including deleted issues)
1795 * 'unsent' - not sent for review 1827 * 'unsent' - not sent for review
(...skipping 935 matching lines...) Expand 10 before | Expand all | Expand 10 after
2731 _CQState.DRY_RUN: 1, 2763 _CQState.DRY_RUN: 1,
2732 _CQState.COMMIT : 2, 2764 _CQState.COMMIT : 2,
2733 } 2765 }
2734 gerrit_util.SetReview(self._GetGerritHost(), self.GetIssue(), 2766 gerrit_util.SetReview(self._GetGerritHost(), self.GetIssue(),
2735 labels={'Commit-Queue': vote_map[new_state]}) 2767 labels={'Commit-Queue': vote_map[new_state]})
2736 2768
2737 def CannotTriggerTryJobReason(self): 2769 def CannotTriggerTryJobReason(self):
2738 # TODO(tandrii): implement for Gerrit. 2770 # TODO(tandrii): implement for Gerrit.
2739 raise NotImplementedError() 2771 raise NotImplementedError()
2740 2772
2773 def GetIssueOwner(self):
2774 # TODO(tandrii): implement for Gerrit.
2775 raise NotImplementedError()
2776
2777 def GetIssueProject(self):
2778 # TODO(tandrii): implement for Gerrit.
2779 raise NotImplementedError()
2780
2741 2781
2742 _CODEREVIEW_IMPLEMENTATIONS = { 2782 _CODEREVIEW_IMPLEMENTATIONS = {
2743 'rietveld': _RietveldChangelistImpl, 2783 'rietveld': _RietveldChangelistImpl,
2744 'gerrit': _GerritChangelistImpl, 2784 'gerrit': _GerritChangelistImpl,
2745 } 2785 }
2746 2786
2747 2787
2748 def _add_codereview_issue_select_options(parser, extra=""): 2788 def _add_codereview_issue_select_options(parser, extra=""):
2749 _add_codereview_select_options(parser) 2789 _add_codereview_select_options(parser)
2750 2790
(...skipping 1959 matching lines...) Expand 10 before | Expand all | Expand 10 after
4710 4750
4711 cl = Changelist(auth_config=auth_config) 4751 cl = Changelist(auth_config=auth_config)
4712 if not cl.GetIssue(): 4752 if not cl.GetIssue():
4713 parser.error('Need to upload first') 4753 parser.error('Need to upload first')
4714 4754
4715 if cl.IsGerrit(): 4755 if cl.IsGerrit():
4716 parser.error( 4756 parser.error(
4717 'Not yet supported for Gerrit (http://crbug.com/599931).\n' 4757 'Not yet supported for Gerrit (http://crbug.com/599931).\n'
4718 'If your project has Commit Queue, dry run is a workaround:\n' 4758 'If your project has Commit Queue, dry run is a workaround:\n'
4719 ' git cl set-commit --dry-run') 4759 ' git cl set-commit --dry-run')
4720 # Code below assumes Rietveld issue.
4721 # TODO(tandrii): actually implement for Gerrit http://crbug.com/599931.
4722 4760
4723 error_message = cl.CannotTriggerTryJobReason() 4761 error_message = cl.CannotTriggerTryJobReason()
4724 if error_message: 4762 if error_message:
4725 parser.error('Can\'t trigger try jobs: %s') 4763 parser.error('Can\'t trigger try jobs: %s')
4726 4764
4727 if not options.name: 4765 if not options.name:
4728 options.name = cl.GetBranch() 4766 options.name = cl.GetBranch()
4729 4767
4730 if options.bot and not options.master: 4768 if options.bot and not options.master:
4731 options.master, err_msg = GetBuilderMaster(options.bot) 4769 options.master, err_msg = GetBuilderMaster(options.bot)
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
4806 'Consider specifying which bots to trigger manually ' 4844 'Consider specifying which bots to trigger manually '
4807 'or asking your project owners for permissions ' 4845 'or asking your project owners for permissions '
4808 'or contacting Chrome Infrastructure team at ' 4846 'or contacting Chrome Infrastructure team at '
4809 'https://www.chromium.org/infra\n\n') 4847 'https://www.chromium.org/infra\n\n')
4810 # Still raise exception so that stack trace is printed. 4848 # Still raise exception so that stack trace is printed.
4811 raise 4849 raise
4812 4850
4813 for builders in masters.itervalues(): 4851 for builders in masters.itervalues():
4814 if any('triggered' in b for b in builders): 4852 if any('triggered' in b for b in builders):
4815 print('ERROR You are trying to send a job to a triggered bot. This type ' 4853 print('ERROR You are trying to send a job to a triggered bot. This type '
4816 'of bot requires an\ninitial job from a parent (usually a builder).' 4854 'of bot requires an initial job from a parent (usually a builder). '
4817 ' Instead send your job to the parent.\n' 4855 'Instead send your job to the parent.\n'
4818 'Bot list: %s' % builders, file=sys.stderr) 4856 'Bot list: %s' % builders, file=sys.stderr)
4819 return 1 4857 return 1
4820 4858
4821 patchset = cl.GetMostRecentPatchset() 4859 patchset = cl.GetMostRecentPatchset()
4822 if patchset and patchset != cl.GetPatchset(): 4860 if patchset != cl.GetPatchset():
4823 print( 4861 print('Warning: Codereview server has newer patchsets (%s) than most '
4824 '\nWARNING Mismatch between local config and server. Did a previous ' 4862 'recent upload from local checkout (%s). Did a previous upload '
4825 'upload fail?\ngit-cl try always uses latest patchset from rietveld. ' 4863 'fail?\n'
4826 'Continuing using\npatchset %s.\n' % patchset) 4864 'By default, git cl try uses the latest patchset from '
4865 'codereview, continuing to use patchset %s.\n' %
4866 (patchset, cl.GetPatchset(), patchset))
4827 try: 4867 try:
4828 trigger_try_jobs(auth_config, cl, options, masters, 'git_cl_try') 4868 _trigger_try_jobs(auth_config, cl, masters, options, 'git_cl_try',
4869 patchset)
4829 except BuildbucketResponseException as ex: 4870 except BuildbucketResponseException as ex:
4830 print('ERROR: %s' % ex) 4871 print('ERROR: %s' % ex)
4831 return 1 4872 return 1
4832 except Exception as e:
4833 stacktrace = (''.join(traceback.format_stack()) + traceback.format_exc())
4834 print('ERROR: Exception when trying to trigger try jobs: %s\n%s' %
4835 (e, stacktrace))
4836 return 1
4837 return 0 4873 return 0
4838 4874
4839 4875
4840 def CMDtry_results(parser, args): 4876 def CMDtry_results(parser, args):
4841 """Prints info about try jobs associated with current CL.""" 4877 """Prints info about try jobs associated with current CL."""
4842 group = optparse.OptionGroup(parser, 'Try job results options') 4878 group = optparse.OptionGroup(parser, 'Try job results options')
4843 group.add_option( 4879 group.add_option(
4844 '-p', '--patchset', type=int, help='patchset number if not current.') 4880 '-p', '--patchset', type=int, help='patchset number if not current.')
4845 group.add_option( 4881 group.add_option(
4846 '--print-master', action='store_true', help='print master name as well.') 4882 '--print-master', action='store_true', help='print master name as well.')
(...skipping 22 matching lines...) Expand all
4869 if not patchset: 4905 if not patchset:
4870 parser.error('Codereview doesn\'t know about issue %s. ' 4906 parser.error('Codereview doesn\'t know about issue %s. '
4871 'No access to issue or wrong issue number?\n' 4907 'No access to issue or wrong issue number?\n'
4872 'Either upload first, or pass --patchset explicitely' % 4908 'Either upload first, or pass --patchset explicitely' %
4873 cl.GetIssue()) 4909 cl.GetIssue())
4874 4910
4875 if patchset != cl.GetPatchset(): 4911 if patchset != cl.GetPatchset():
4876 print('Warning: Codereview server has newer patchsets (%s) than most ' 4912 print('Warning: Codereview server has newer patchsets (%s) than most '
4877 'recent upload from local checkout (%s). Did a previous upload ' 4913 'recent upload from local checkout (%s). Did a previous upload '
4878 'fail?\n' 4914 'fail?\n'
4879 'By default, git cl try uses latest patchset from codereview, ' 4915 'By default, git cl try-results uses the latest patchset from '
4880 'continuing to use patchset %s.\n' % 4916 'codereview, continuing to use patchset %s.\n' %
4881 (patchset, cl.GetPatchset(), patchset)) 4917 (patchset, cl.GetPatchset(), patchset))
4882 try: 4918 try:
4883 jobs = fetch_try_jobs(auth_config, cl, options.buildbucket_host, patchset) 4919 jobs = fetch_try_jobs(auth_config, cl, options.buildbucket_host, patchset)
4884 except BuildbucketResponseException as ex: 4920 except BuildbucketResponseException as ex:
4885 print('Buildbucket error: %s' % ex) 4921 print('Buildbucket error: %s' % ex)
4886 return 1 4922 return 1
4887 if options.json: 4923 if options.json:
4888 write_try_results_json(options.json, jobs) 4924 write_try_results_json(options.json, jobs)
4889 else: 4925 else:
4890 print_try_jobs(options, jobs) 4926 print_try_jobs(options, jobs)
(...skipping 438 matching lines...) Expand 10 before | Expand all | Expand 10 after
5329 if __name__ == '__main__': 5365 if __name__ == '__main__':
5330 # These affect sys.stdout so do it outside of main() to simplify mocks in 5366 # These affect sys.stdout so do it outside of main() to simplify mocks in
5331 # unit testing. 5367 # unit testing.
5332 fix_encoding.fix_encoding() 5368 fix_encoding.fix_encoding()
5333 setup_color.init() 5369 setup_color.init()
5334 try: 5370 try:
5335 sys.exit(main(sys.argv[1:])) 5371 sys.exit(main(sys.argv[1:]))
5336 except KeyboardInterrupt: 5372 except KeyboardInterrupt:
5337 sys.stderr.write('interrupted\n') 5373 sys.stderr.write('interrupted\n')
5338 sys.exit(1) 5374 sys.exit(1)
OLDNEW
« no previous file with comments | « no previous file | tests/git_cl_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698