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

Side by Side Diff: git_cl.py

Issue 1848393002: Revert of Gerrit git cl: implement git cl patch. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Created 4 years, 8 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 distutils.version import LooseVersion 10 from distutils.version import LooseVersion
(...skipping 816 matching lines...) Expand 10 before | Expand all | Expand 10 after
827 """Returns current branch or None. 827 """Returns current branch or None.
828 828
829 For refs/heads/* branches, returns just last part. For others, full ref. 829 For refs/heads/* branches, returns just last part. For others, full ref.
830 """ 830 """
831 branchref = GetCurrentBranchRef() 831 branchref = GetCurrentBranchRef()
832 if branchref: 832 if branchref:
833 return ShortBranchName(branchref) 833 return ShortBranchName(branchref)
834 return None 834 return None
835 835
836 836
837 class _ParsedIssueNumberArgument(object):
838 def __init__(self, issue=None, patchset=None, hostname=None):
839 self.issue = issue
840 self.patchset = patchset
841 self.hostname = hostname
842
843 @property
844 def valid(self):
845 return self.issue is not None
846
847
848 class _RietveldParsedIssueNumberArgument(_ParsedIssueNumberArgument):
849 def __init__(self, *args, **kwargs):
850 self.patch_url = kwargs.pop('patch_url', None)
851 super(_RietveldParsedIssueNumberArgument, self).__init__(*args, **kwargs)
852
853
854 def ParseIssueNumberArgument(arg):
855 """Parses the issue argument and returns _ParsedIssueNumberArgument."""
856 fail_result = _ParsedIssueNumberArgument()
857
858 if arg.isdigit():
859 return _ParsedIssueNumberArgument(issue=int(arg))
860 if not arg.startswith('http'):
861 return fail_result
862 url = gclient_utils.UpgradeToHttps(arg)
863 try:
864 parsed_url = urlparse.urlparse(url)
865 except ValueError:
866 return fail_result
867 for cls in _CODEREVIEW_IMPLEMENTATIONS.itervalues():
868 tmp = cls.ParseIssueURL(parsed_url)
869 if tmp is not None:
870 return tmp
871 return fail_result
872
873
874 class Changelist(object): 837 class Changelist(object):
875 """Changelist works with one changelist in local branch. 838 """Changelist works with one changelist in local branch.
876 839
877 Supports two codereview backends: Rietveld or Gerrit, selected at object 840 Supports two codereview backends: Rietveld or Gerrit, selected at object
878 creation. 841 creation.
879 842
880 Not safe for concurrent multi-{thread,process} use. 843 Not safe for concurrent multi-{thread,process} use.
881 """ 844 """
882 845
883 def __init__(self, branchref=None, issue=None, codereview=None, **kwargs): 846 def __init__(self, branchref=None, issue=None, codereview=None, **kwargs):
(...skipping 403 matching lines...) Expand 10 before | Expand all | Expand 10 after
1287 try: 1250 try:
1288 return presubmit_support.DoPresubmitChecks(change, committing, 1251 return presubmit_support.DoPresubmitChecks(change, committing,
1289 verbose=verbose, output_stream=sys.stdout, input_stream=sys.stdin, 1252 verbose=verbose, output_stream=sys.stdout, input_stream=sys.stdin,
1290 default_presubmit=None, may_prompt=may_prompt, 1253 default_presubmit=None, may_prompt=may_prompt,
1291 rietveld_obj=self._codereview_impl.GetRieveldObjForPresubmit()) 1254 rietveld_obj=self._codereview_impl.GetRieveldObjForPresubmit())
1292 except presubmit_support.PresubmitFailure, e: 1255 except presubmit_support.PresubmitFailure, e:
1293 DieWithError( 1256 DieWithError(
1294 ('%s\nMaybe your depot_tools is out of date?\n' 1257 ('%s\nMaybe your depot_tools is out of date?\n'
1295 'If all fails, contact maruel@') % e) 1258 'If all fails, contact maruel@') % e)
1296 1259
1297 def CMDPatchIssue(self, issue_arg, reject, nocommit, directory):
1298 """Fetches and applies the issue patch from codereview to local branch."""
1299 if issue_arg.isdigit():
1300 parsed_issue_arg = _RietveldParsedIssueNumberArgument(int(issue_arg))
1301 else:
1302 # Assume url.
1303 parsed_issue_arg = self._codereview_impl.ParseIssueURL(
1304 urlparse.urlparse(issue_arg))
1305 if not parsed_issue_arg or not parsed_issue_arg.valid:
1306 DieWithError('Failed to parse issue argument "%s". '
1307 'Must be an issue number or a valid URL.' % issue_arg)
1308 return self._codereview_impl.CMDPatchWithParsedIssue(
1309 parsed_issue_arg, reject, nocommit, directory)
1310
1311 # Forward methods to codereview specific implementation. 1260 # Forward methods to codereview specific implementation.
1312 1261
1313 def CloseIssue(self): 1262 def CloseIssue(self):
1314 return self._codereview_impl.CloseIssue() 1263 return self._codereview_impl.CloseIssue()
1315 1264
1316 def GetStatus(self): 1265 def GetStatus(self):
1317 return self._codereview_impl.GetStatus() 1266 return self._codereview_impl.GetStatus()
1318 1267
1319 def GetCodereviewServer(self): 1268 def GetCodereviewServer(self):
1320 return self._codereview_impl.GetCodereviewServer() 1269 return self._codereview_impl.GetCodereviewServer()
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after
1390 """Returns a list of reviewers approving the change. 1339 """Returns a list of reviewers approving the change.
1391 1340
1392 Note: not necessarily committers. 1341 Note: not necessarily committers.
1393 """ 1342 """
1394 raise NotImplementedError() 1343 raise NotImplementedError()
1395 1344
1396 def GetMostRecentPatchset(self): 1345 def GetMostRecentPatchset(self):
1397 """Returns the most recent patchset number from the codereview site.""" 1346 """Returns the most recent patchset number from the codereview site."""
1398 raise NotImplementedError() 1347 raise NotImplementedError()
1399 1348
1400 def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit,
1401 directory):
1402 """Fetches and applies the issue.
1403 1349
1404 Arguments:
1405 parsed_issue_arg: instance of _ParsedIssueNumberArgument.
1406 reject: if True, reject the failed patch instead of switching to 3-way
1407 merge. Rietveld only.
1408 nocommit: do not commit the patch, thus leave the tree dirty. Rietveld
1409 only.
1410 directory: switch to directory before applying the patch. Rietveld only.
1411 """
1412 raise NotImplementedError()
1413
1414 @staticmethod
1415 def ParseIssueURL(parsed_url):
1416 """Parses url and returns instance of _ParsedIssueNumberArgument or None if
1417 failed."""
1418 raise NotImplementedError()
1419
1420
1421 class _RietveldChangelistImpl(_ChangelistCodereviewBase): 1350 class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1422 def __init__(self, changelist, auth_config=None, rietveld_server=None): 1351 def __init__(self, changelist, auth_config=None, rietveld_server=None):
1423 super(_RietveldChangelistImpl, self).__init__(changelist) 1352 super(_RietveldChangelistImpl, self).__init__(changelist)
1424 assert settings, 'must be initialized in _ChangelistCodereviewBase' 1353 assert settings, 'must be initialized in _ChangelistCodereviewBase'
1425 settings.GetDefaultServerUrl() 1354 settings.GetDefaultServerUrl()
1426 1355
1427 self._rietveld_server = rietveld_server 1356 self._rietveld_server = rietveld_server
1428 self._auth_config = auth_config 1357 self._auth_config = auth_config
1429 self._props = None 1358 self._props = None
1430 self._rpc_server = None 1359 self._rpc_server = None
(...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after
1582 def GetCodereviewServerSetting(self): 1511 def GetCodereviewServerSetting(self):
1583 """Returns the git setting that stores this change's rietveld server.""" 1512 """Returns the git setting that stores this change's rietveld server."""
1584 branch = self.GetBranch() 1513 branch = self.GetBranch()
1585 if branch: 1514 if branch:
1586 return 'branch.%s.rietveldserver' % branch 1515 return 'branch.%s.rietveldserver' % branch
1587 return None 1516 return None
1588 1517
1589 def GetRieveldObjForPresubmit(self): 1518 def GetRieveldObjForPresubmit(self):
1590 return self.RpcServer() 1519 return self.RpcServer()
1591 1520
1592 def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit,
1593 directory):
1594 # TODO(maruel): Use apply_issue.py
1595
1596 # PatchIssue should never be called with a dirty tree. It is up to the
1597 # caller to check this, but just in case we assert here since the
1598 # consequences of the caller not checking this could be dire.
1599 assert(not git_common.is_dirty_git_tree('apply'))
1600 assert(parsed_issue_arg.valid)
1601 self._changelist.issue = parsed_issue_arg.issue
1602 if parsed_issue_arg.hostname:
1603 self._rietveld_server = 'https://%s' % parsed_issue_arg.hostname
1604
1605 if parsed_issue_arg.patch_url:
1606 assert parsed_issue_arg.patchset
1607 patchset = parsed_issue_arg.patchset
1608 patch_data = urllib2.urlopen(parsed_issue_arg.patch_url).read()
1609 else:
1610 patchset = parsed_issue_arg.patchset or self.GetMostRecentPatchset()
1611 patch_data = self.GetPatchSetDiff(self.GetIssue(), patchset)
1612
1613 # Switch up to the top-level directory, if necessary, in preparation for
1614 # applying the patch.
1615 top = settings.GetRelativeRoot()
1616 if top:
1617 os.chdir(top)
1618
1619 # Git patches have a/ at the beginning of source paths. We strip that out
1620 # with a sed script rather than the -p flag to patch so we can feed either
1621 # Git or svn-style patches into the same apply command.
1622 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7.
1623 try:
1624 patch_data = subprocess2.check_output(
1625 ['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data)
1626 except subprocess2.CalledProcessError:
1627 DieWithError('Git patch mungling failed.')
1628 logging.info(patch_data)
1629
1630 # We use "git apply" to apply the patch instead of "patch" so that we can
1631 # pick up file adds.
1632 # The --index flag means: also insert into the index (so we catch adds).
1633 cmd = ['git', 'apply', '--index', '-p0']
1634 if directory:
1635 cmd.extend(('--directory', directory))
1636 if reject:
1637 cmd.append('--reject')
1638 elif IsGitVersionAtLeast('1.7.12'):
1639 cmd.append('--3way')
1640 try:
1641 subprocess2.check_call(cmd, env=GetNoGitPagerEnv(),
1642 stdin=patch_data, stdout=subprocess2.VOID)
1643 except subprocess2.CalledProcessError:
1644 print 'Failed to apply the patch'
1645 return 1
1646
1647 # If we had an issue, commit the current state and register the issue.
1648 if not nocommit:
1649 RunGit(['commit', '-m', (self.GetDescription() + '\n\n' +
1650 'patch from issue %(i)s at patchset '
1651 '%(p)s (http://crrev.com/%(i)s#ps%(p)s)'
1652 % {'i': self.GetIssue(), 'p': patchset})])
1653 self.SetIssue(self.GetIssue())
1654 self.SetPatchset(patchset)
1655 print "Committed patch locally."
1656 else:
1657 print "Patch applied to index."
1658 return 0
1659
1660 @staticmethod
1661 def ParseIssueURL(parsed_url):
1662 if not parsed_url.scheme or not parsed_url.scheme.startswith('http'):
1663 return None
1664 # Typical url: https://domain/<issue_number>[/[other]]
1665 match = re.match('/(\d+)(/.*)?$', parsed_url.path)
1666 if match:
1667 return _RietveldParsedIssueNumberArgument(
1668 issue=int(match.group(1)),
1669 hostname=parsed_url.netloc)
1670 # Rietveld patch: https://domain/download/issue<number>_<patchset>.diff
1671 match = re.match(r'/download/issue(\d+)_(\d+).diff$', parsed_url.path)
1672 if match:
1673 return _RietveldParsedIssueNumberArgument(
1674 issue=int(match.group(1)),
1675 patchset=int(match.group(2)),
1676 hostname=parsed_url.netloc,
1677 patch_url=gclient_utils.UpgradeToHttps(parsed_url.geturl()))
1678 return None
1679
1680 1521
1681 class _GerritChangelistImpl(_ChangelistCodereviewBase): 1522 class _GerritChangelistImpl(_ChangelistCodereviewBase):
1682 def __init__(self, changelist, auth_config=None): 1523 def __init__(self, changelist, auth_config=None):
1683 # auth_config is Rietveld thing, kept here to preserve interface only. 1524 # auth_config is Rietveld thing, kept here to preserve interface only.
1684 super(_GerritChangelistImpl, self).__init__(changelist) 1525 super(_GerritChangelistImpl, self).__init__(changelist)
1685 self._change_id = None 1526 self._change_id = None
1686 self._gerrit_server = None # e.g. https://chromium-review.googlesource.com 1527 self._gerrit_server = None # e.g. https://chromium-review.googlesource.com
1687 self._gerrit_host = None # e.g. chromium-review.googlesource.com 1528 self._gerrit_host = None # e.g. chromium-review.googlesource.com
1688 1529
1689 def _GetGerritHost(self): 1530 def _GetGerritHost(self):
(...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after
1848 may_prompt=not force, 1689 may_prompt=not force,
1849 verbose=verbose, 1690 verbose=verbose,
1850 change=self.GetChange(self.GetCommonAncestorWithUpstream(), None)) 1691 change=self.GetChange(self.GetCommonAncestorWithUpstream(), None))
1851 if not hook_results.should_continue(): 1692 if not hook_results.should_continue():
1852 return 1 1693 return 1
1853 1694
1854 self.SubmitIssue(wait_for_merge=True) 1695 self.SubmitIssue(wait_for_merge=True)
1855 print('Issue %s has been submitted.' % self.GetIssueURL()) 1696 print('Issue %s has been submitted.' % self.GetIssueURL())
1856 return 0 1697 return 0
1857 1698
1858 def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit,
1859 directory):
1860 assert not reject
1861 assert not nocommit
1862 assert not directory
1863 assert parsed_issue_arg.valid
1864
1865 self._changelist.issue = parsed_issue_arg.issue
1866
1867 if parsed_issue_arg.hostname:
1868 self._gerrit_host = parsed_issue_arg.hostname
1869 self._gerrit_server = 'https://%s' % self._gerrit_host
1870
1871 detail = self._GetChangeDetail(['ALL_REVISIONS'])
1872
1873 if not parsed_issue_arg.patchset:
1874 # Use current revision by default.
1875 revision_info = detail['revisions'][detail['current_revision']]
1876 patchset = int(revision_info['_number'])
1877 else:
1878 patchset = parsed_issue_arg.patchset
1879 for revision_info in detail['revisions'].itervalues():
1880 if int(revision_info['_number']) == parsed_issue_arg.patchset:
1881 break
1882 else:
1883 DieWithError('Couldn\'t find patchset %i in issue %i' %
1884 (parsed_issue_arg.patchset, self.GetIssue()))
1885
1886 fetch_info = revision_info['fetch']['http']
1887 RunGit(['fetch', fetch_info['url'], fetch_info['ref']])
1888 RunGit(['cherry-pick', 'FETCH_HEAD'])
1889 self.SetIssue(self.GetIssue())
1890 self.SetPatchset(patchset)
1891 print('Committed patch for issue %i pathset %i locally' %
1892 (self.GetIssue(), self.GetPatchset()))
1893 return 0
1894
1895 @staticmethod
1896 def ParseIssueURL(parsed_url):
1897 if not parsed_url.scheme or not parsed_url.scheme.startswith('http'):
1898 return None
1899 # Gerrit's new UI is https://domain/c/<issue_number>[/[patchset]]
1900 # But current GWT UI is https://domain/#/c/<issue_number>[/[patchset]]
1901 # Short urls like https://domain/<issue_number> can be used, but don't allow
1902 # specifying the patchset (you'd 404), but we allow that here.
1903 if parsed_url.path == '/':
1904 part = parsed_url.fragment
1905 else:
1906 part = parsed_url.path
1907 match = re.match('(/c)?/(\d+)(/(\d+)?/?)?$', part)
1908 if match:
1909 return _ParsedIssueNumberArgument(
1910 issue=int(match.group(2)),
1911 patchset=int(match.group(4)) if match.group(4) else None,
1912 hostname=parsed_url.netloc)
1913 return None
1914
1915 1699
1916 _CODEREVIEW_IMPLEMENTATIONS = { 1700 _CODEREVIEW_IMPLEMENTATIONS = {
1917 'rietveld': _RietveldChangelistImpl, 1701 'rietveld': _RietveldChangelistImpl,
1918 'gerrit': _GerritChangelistImpl, 1702 'gerrit': _GerritChangelistImpl,
1919 } 1703 }
1920 1704
1921 1705
1922 class ChangeDescription(object): 1706 class ChangeDescription(object):
1923 """Contains a parsed form of the change description.""" 1707 """Contains a parsed form of the change description."""
1924 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$' 1708 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$'
(...skipping 1877 matching lines...) Expand 10 before | Expand all | Expand 10 after
3802 def CMDland(parser, args): 3586 def CMDland(parser, args):
3803 """Commits the current changelist via git.""" 3587 """Commits the current changelist via git."""
3804 if settings.GetIsGitSvn() or git_footers.get_footer_svn_id(): 3588 if settings.GetIsGitSvn() or git_footers.get_footer_svn_id():
3805 print('This appears to be an SVN repository.') 3589 print('This appears to be an SVN repository.')
3806 print('Are you sure you didn\'t mean \'git cl dcommit\'?') 3590 print('Are you sure you didn\'t mean \'git cl dcommit\'?')
3807 print('(Ignore if this is the first commit after migrating from svn->git)') 3591 print('(Ignore if this is the first commit after migrating from svn->git)')
3808 ask_for_data('[Press enter to push or ctrl-C to quit]') 3592 ask_for_data('[Press enter to push or ctrl-C to quit]')
3809 return SendUpstream(parser, args, 'land') 3593 return SendUpstream(parser, args, 'land')
3810 3594
3811 3595
3596 def ParseIssueNum(arg):
3597 """Parses the issue number from args if present otherwise returns None."""
3598 if re.match(r'\d+', arg):
3599 return arg
3600 if arg.startswith('http'):
3601 return re.sub(r'.*/(\d+)/?', r'\1', arg)
3602 return None
3603
3604
3812 @subcommand.usage('<patch url or issue id or issue url>') 3605 @subcommand.usage('<patch url or issue id or issue url>')
3813 def CMDpatch(parser, args): 3606 def CMDpatch(parser, args):
3814 """Patches in a code review.""" 3607 """Patches in a code review."""
3815 parser.add_option('-b', dest='newbranch', 3608 parser.add_option('-b', dest='newbranch',
3816 help='create a new branch off trunk for the patch') 3609 help='create a new branch off trunk for the patch')
3817 parser.add_option('-f', '--force', action='store_true', 3610 parser.add_option('-f', '--force', action='store_true',
3818 help='with -b, clobber any existing branch') 3611 help='with -b, clobber any existing branch')
3819 parser.add_option('-d', '--directory', action='store', metavar='DIR', 3612 parser.add_option('-d', '--directory', action='store', metavar='DIR',
3820 help='Change to the directory DIR immediately, ' 3613 help='Change to the directory DIR immediately, '
3821 'before doing anything else. Rietveld only.') 3614 'before doing anything else.')
3822 parser.add_option('--reject', action='store_true', 3615 parser.add_option('--reject', action='store_true',
3823 help='failed patches spew .rej files rather than ' 3616 help='failed patches spew .rej files rather than '
3824 'attempting a 3-way merge. Rietveld only.') 3617 'attempting a 3-way merge')
3825 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit', 3618 parser.add_option('-n', '--no-commit', action='store_true', dest='nocommit',
3826 help='don\'t commit after patch applies. Rietveld only.') 3619 help="don't commit after patch applies")
3827 3620
3828 3621 group = optparse.OptionGroup(parser,
3829 group = optparse.OptionGroup( 3622 """Options for continuing work on the current issue uploaded
3830 parser, 3623 from a different clone (e.g. different machine). Must be used independently from
3831 'Options for continuing work on the current issue uploaded from a ' 3624 the other options. No issue number should be specified, and the branch must have
3832 'different clone (e.g. different machine). Must be used independently ' 3625 an issue number associated with it""")
3833 'from the other options. No issue number should be specified, and the ' 3626 group.add_option('--reapply', action='store_true',
3834 'branch must have an issue number associated with it') 3627 dest='reapply',
3835 group.add_option('--reapply', action='store_true', dest='reapply', 3628 help="""Reset the branch and reapply the issue.
3836 help='Reset the branch and reapply the issue.\n' 3629 CAUTION: This will undo any local changes in this branch""")
3837 'CAUTION: This will undo any local changes in this '
3838 'branch')
3839 3630
3840 group.add_option('--pull', action='store_true', dest='pull', 3631 group.add_option('--pull', action='store_true', dest='pull',
3841 help='Performs a pull before reapplying.') 3632 help="Performs a pull before reapplying.")
3842 parser.add_option_group(group) 3633 parser.add_option_group(group)
3843 3634
3844 auth.add_auth_options(parser) 3635 auth.add_auth_options(parser)
3845 (options, args) = parser.parse_args(args) 3636 (options, args) = parser.parse_args(args)
3846 auth_config = auth.extract_auth_config_from_options(options) 3637 auth_config = auth.extract_auth_config_from_options(options)
3847 3638
3848 cl = Changelist(auth_config=auth_config)
3849
3850 issue_arg = None 3639 issue_arg = None
3851 if options.reapply : 3640 if options.reapply :
3852 if len(args) > 0: 3641 if len(args) > 0:
3853 parser.error('--reapply implies no additional arguments.') 3642 parser.error("--reapply implies no additional arguments.")
3854 3643
3644 cl = Changelist()
3855 issue_arg = cl.GetIssue() 3645 issue_arg = cl.GetIssue()
3856 upstream = cl.GetUpstreamBranch() 3646 upstream = cl.GetUpstreamBranch()
3857 if upstream == None: 3647 if upstream == None:
3858 parser.error('No upstream branch specified. Cannot reset branch') 3648 parser.error("No upstream branch specified. Cannot reset branch")
3859 3649
3860 RunGit(['reset', '--hard', upstream]) 3650 RunGit(['reset', '--hard', upstream])
3861 if options.pull: 3651 if options.pull:
3862 RunGit(['pull']) 3652 RunGit(['pull'])
3863 else: 3653 else:
3864 if len(args) != 1: 3654 if len(args) != 1:
3865 parser.error('Must specify issue number or url') 3655 parser.error("Must specify issue number")
3866 issue_arg = args[0]
3867 3656
3868 if not issue_arg: 3657 issue_arg = ParseIssueNum(args[0])
3658
3659 # The patch URL works because ParseIssueNum won't do any substitution
3660 # as the re.sub pattern fails to match and just returns it.
3661 if issue_arg == None:
3869 parser.print_help() 3662 parser.print_help()
3870 return 1 3663 return 1
3871 3664
3872 if cl.IsGerrit():
3873 if options.reject:
3874 parser.error('--reject is not supported with Gerrit codereview.')
3875 if options.nocommit:
3876 parser.error('--nocommit is not supported with Gerrit codereview.')
3877 if options.directory:
3878 parser.error('--directory is not supported with Gerrit codereview.')
3879
3880 # We don't want uncommitted changes mixed up with the patch. 3665 # We don't want uncommitted changes mixed up with the patch.
3881 if git_common.is_dirty_git_tree('patch'): 3666 if git_common.is_dirty_git_tree('patch'):
3882 return 1 3667 return 1
3883 3668
3669 # TODO(maruel): Use apply_issue.py
3670 # TODO(ukai): use gerrit-cherry-pick for gerrit repository?
3671
3884 if options.newbranch: 3672 if options.newbranch:
3885 if options.reapply: 3673 if options.reapply:
3886 parser.error("--reapply excludes any option other than --pull") 3674 parser.error("--reapply excludes any option other than --pull")
3887 if options.force: 3675 if options.force:
3888 RunGit(['branch', '-D', options.newbranch], 3676 RunGit(['branch', '-D', options.newbranch],
3889 stderr=subprocess2.PIPE, error_ok=True) 3677 stderr=subprocess2.PIPE, error_ok=True)
3890 RunGit(['checkout', '-b', options.newbranch, 3678 RunGit(['checkout', '-b', options.newbranch,
3891 Changelist().GetUpstreamBranch()]) 3679 Changelist().GetUpstreamBranch()])
3892 3680
3893 return cl.CMDPatchIssue(issue_arg, options.reject, options.nocommit, 3681 return PatchIssue(issue_arg, options.reject, options.nocommit,
3894 options.directory) 3682 options.directory, auth_config)
3683
3684
3685 def PatchIssue(issue_arg, reject, nocommit, directory, auth_config):
3686 # PatchIssue should never be called with a dirty tree. It is up to the
3687 # caller to check this, but just in case we assert here since the
3688 # consequences of the caller not checking this could be dire.
3689 assert(not git_common.is_dirty_git_tree('apply'))
3690
3691 # TODO(tandrii): implement for Gerrit.
3692 if type(issue_arg) is int or issue_arg.isdigit():
3693 # Input is an issue id. Figure out the URL.
3694 issue = int(issue_arg)
3695 cl = Changelist(issue=issue, codereview='rietveld', auth_config=auth_config)
3696 patchset = cl.GetMostRecentPatchset()
3697 patch_data = cl._codereview_impl.GetPatchSetDiff(issue, patchset)
3698 else:
3699 # Assume it's a URL to the patch. Default to https.
3700 issue_url = gclient_utils.UpgradeToHttps(issue_arg)
3701 match = re.match(r'(.*?)/download/issue(\d+)_(\d+).diff', issue_url)
3702 if not match:
3703 DieWithError('Must pass an issue ID or full URL for '
3704 '\'Download raw patch set\'')
3705 issue = int(match.group(2))
3706 cl = Changelist(issue=issue, codereview='rietveld',
3707 rietveld_server=match.group(1), auth_config=auth_config)
3708 patchset = int(match.group(3))
3709 patch_data = urllib2.urlopen(issue_arg).read()
3710
3711 # Switch up to the top-level directory, if necessary, in preparation for
3712 # applying the patch.
3713 top = settings.GetRelativeRoot()
3714 if top:
3715 os.chdir(top)
3716
3717 # Git patches have a/ at the beginning of source paths. We strip that out
3718 # with a sed script rather than the -p flag to patch so we can feed either
3719 # Git or svn-style patches into the same apply command.
3720 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7.
3721 try:
3722 patch_data = subprocess2.check_output(
3723 ['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data)
3724 except subprocess2.CalledProcessError:
3725 DieWithError('Git patch mungling failed.')
3726 logging.info(patch_data)
3727
3728 # We use "git apply" to apply the patch instead of "patch" so that we can
3729 # pick up file adds.
3730 # The --index flag means: also insert into the index (so we catch adds).
3731 cmd = ['git', 'apply', '--index', '-p0']
3732 if directory:
3733 cmd.extend(('--directory', directory))
3734 if reject:
3735 cmd.append('--reject')
3736 elif IsGitVersionAtLeast('1.7.12'):
3737 cmd.append('--3way')
3738 try:
3739 subprocess2.check_call(cmd, env=GetNoGitPagerEnv(),
3740 stdin=patch_data, stdout=subprocess2.VOID)
3741 except subprocess2.CalledProcessError:
3742 print 'Failed to apply the patch'
3743 return 1
3744
3745 # If we had an issue, commit the current state and register the issue.
3746 if not nocommit:
3747 RunGit(['commit', '-m', (cl.GetDescription() + '\n\n' +
3748 'patch from issue %(i)s at patchset '
3749 '%(p)s (http://crrev.com/%(i)s#ps%(p)s)'
3750 % {'i': issue, 'p': patchset})])
3751 cl = Changelist(codereview='rietveld', auth_config=auth_config,
3752 rietveld_server=cl.GetCodereviewServer())
3753 cl.SetIssue(issue)
3754 cl.SetPatchset(patchset)
3755 print "Committed patch locally."
3756 else:
3757 print "Patch applied to index."
3758 return 0
3895 3759
3896 3760
3897 def CMDrebase(parser, args): 3761 def CMDrebase(parser, args):
3898 """Rebases current branch on top of svn repo.""" 3762 """Rebases current branch on top of svn repo."""
3899 # Provide a wrapper for git svn rebase to help avoid accidental 3763 # Provide a wrapper for git svn rebase to help avoid accidental
3900 # git svn dcommit. 3764 # git svn dcommit.
3901 # It's the only command that doesn't use parser at all since we just defer 3765 # It's the only command that doesn't use parser at all since we just defer
3902 # execution to git-svn. 3766 # execution to git-svn.
3903 3767
3904 return RunGitWithCode(['svn', 'rebase'] + args)[1] 3768 return RunGitWithCode(['svn', 'rebase'] + args)[1]
(...skipping 381 matching lines...) Expand 10 before | Expand all | Expand 10 after
4286 4150
4287 def CMDdiff(parser, args): 4151 def CMDdiff(parser, args):
4288 """Shows differences between local tree and last upload.""" 4152 """Shows differences between local tree and last upload."""
4289 auth.add_auth_options(parser) 4153 auth.add_auth_options(parser)
4290 options, args = parser.parse_args(args) 4154 options, args = parser.parse_args(args)
4291 auth_config = auth.extract_auth_config_from_options(options) 4155 auth_config = auth.extract_auth_config_from_options(options)
4292 if args: 4156 if args:
4293 parser.error('Unrecognized args: %s' % ' '.join(args)) 4157 parser.error('Unrecognized args: %s' % ' '.join(args))
4294 4158
4295 # Uncommitted (staged and unstaged) changes will be destroyed by 4159 # Uncommitted (staged and unstaged) changes will be destroyed by
4296 # "git reset --hard" if there are merging conflicts in CMDPatchIssue(). 4160 # "git reset --hard" if there are merging conflicts in PatchIssue().
4297 # Staged changes would be committed along with the patch from last 4161 # Staged changes would be committed along with the patch from last
4298 # upload, hence counted toward the "last upload" side in the final 4162 # upload, hence counted toward the "last upload" side in the final
4299 # diff output, and this is not what we want. 4163 # diff output, and this is not what we want.
4300 if git_common.is_dirty_git_tree('diff'): 4164 if git_common.is_dirty_git_tree('diff'):
4301 return 1 4165 return 1
4302 4166
4303 cl = Changelist(auth_config=auth_config) 4167 cl = Changelist(auth_config=auth_config)
4304 issue = cl.GetIssue() 4168 issue = cl.GetIssue()
4305 branch = cl.GetBranch() 4169 branch = cl.GetBranch()
4306 if not issue: 4170 if not issue:
4307 DieWithError('No issue found for current branch (%s)' % branch) 4171 DieWithError('No issue found for current branch (%s)' % branch)
4308 TMP_BRANCH = 'git-cl-diff' 4172 TMP_BRANCH = 'git-cl-diff'
4309 base_branch = cl.GetCommonAncestorWithUpstream() 4173 base_branch = cl.GetCommonAncestorWithUpstream()
4310 4174
4311 # Create a new branch based on the merge-base 4175 # Create a new branch based on the merge-base
4312 RunGit(['checkout', '-q', '-b', TMP_BRANCH, base_branch]) 4176 RunGit(['checkout', '-q', '-b', TMP_BRANCH, base_branch])
4313 try: 4177 try:
4314 rtn = cl.CMDPatchIssue(issue, reject=False, nocommit=False, directory=None) 4178 # Patch in the latest changes from rietveld.
4179 rtn = PatchIssue(issue, False, False, None, auth_config)
4315 if rtn != 0: 4180 if rtn != 0:
4316 RunGit(['reset', '--hard']) 4181 RunGit(['reset', '--hard'])
4317 return rtn 4182 return rtn
4318 4183
4319 # Switch back to starting branch and diff against the temporary 4184 # Switch back to starting branch and diff against the temporary
4320 # branch containing the latest rietveld patch. 4185 # branch containing the latest rietveld patch.
4321 subprocess2.check_call(['git', 'diff', TMP_BRANCH, branch, '--']) 4186 subprocess2.check_call(['git', 'diff', TMP_BRANCH, branch, '--'])
4322 finally: 4187 finally:
4323 RunGit(['checkout', '-q', branch]) 4188 RunGit(['checkout', '-q', branch])
4324 RunGit(['branch', '-D', TMP_BRANCH]) 4189 RunGit(['branch', '-D', TMP_BRANCH])
(...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after
4512 stdout = RunCommand(cmd + [gn_diff_file], cwd=top_dir) 4377 stdout = RunCommand(cmd + [gn_diff_file], cwd=top_dir)
4513 if opts.diff: 4378 if opts.diff:
4514 sys.stdout.write(stdout) 4379 sys.stdout.write(stdout)
4515 4380
4516 return return_value 4381 return return_value
4517 4382
4518 4383
4519 @subcommand.usage('<codereview url or issue id>') 4384 @subcommand.usage('<codereview url or issue id>')
4520 def CMDcheckout(parser, args): 4385 def CMDcheckout(parser, args):
4521 """Checks out a branch associated with a given Rietveld issue.""" 4386 """Checks out a branch associated with a given Rietveld issue."""
4522 # TODO(tandrii): consider adding this for Gerrit?
4523 _, args = parser.parse_args(args) 4387 _, args = parser.parse_args(args)
4524 4388
4525 if len(args) != 1: 4389 if len(args) != 1:
4526 parser.print_help() 4390 parser.print_help()
4527 return 1 4391 return 1
4528 4392
4529 issue_arg = ParseIssueNumberArgument(args[0]) 4393 target_issue = ParseIssueNum(args[0])
4530 if issue_arg.valid: 4394 if target_issue == None:
4531 parser.print_help() 4395 parser.print_help()
4532 return 1 4396 return 1
4533 target_issue = issue_arg.issue
4534 4397
4535 key_and_issues = [x.split() for x in RunGit( 4398 key_and_issues = [x.split() for x in RunGit(
4536 ['config', '--local', '--get-regexp', r'branch\..*\.rietveldissue']) 4399 ['config', '--local', '--get-regexp', r'branch\..*\.rietveldissue'])
4537 .splitlines()] 4400 .splitlines()]
4538 branches = [] 4401 branches = []
4539 for key, issue in key_and_issues: 4402 for key, issue in key_and_issues:
4540 if issue == target_issue: 4403 if issue == target_issue:
4541 branches.append(re.sub(r'branch\.(.*)\.rietveldissue', r'\1', key)) 4404 branches.append(re.sub(r'branch\.(.*)\.rietveldissue', r'\1', key))
4542 4405
4543 if len(branches) == 0: 4406 if len(branches) == 0:
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
4614 if __name__ == '__main__': 4477 if __name__ == '__main__':
4615 # These affect sys.stdout so do it outside of main() to simplify mocks in 4478 # These affect sys.stdout so do it outside of main() to simplify mocks in
4616 # unit testing. 4479 # unit testing.
4617 fix_encoding.fix_encoding() 4480 fix_encoding.fix_encoding()
4618 colorama.init(wrap="TERM" not in os.environ) 4481 colorama.init(wrap="TERM" not in os.environ)
4619 try: 4482 try:
4620 sys.exit(main(sys.argv[1:])) 4483 sys.exit(main(sys.argv[1:]))
4621 except KeyboardInterrupt: 4484 except KeyboardInterrupt:
4622 sys.stderr.write('interrupted\n') 4485 sys.stderr.write('interrupted\n')
4623 sys.exit(1) 4486 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