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

Side by Side Diff: git_cl.py

Issue 1877893002: Refactor git cl RietveldUpload and GerritUpload. (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 | no next file » | 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 1408 matching lines...) Expand 10 before | Expand all | Expand 10 after
1419 directory: switch to directory before applying the patch. Rietveld only. 1419 directory: switch to directory before applying the patch. Rietveld only.
1420 """ 1420 """
1421 raise NotImplementedError() 1421 raise NotImplementedError()
1422 1422
1423 @staticmethod 1423 @staticmethod
1424 def ParseIssueURL(parsed_url): 1424 def ParseIssueURL(parsed_url):
1425 """Parses url and returns instance of _ParsedIssueNumberArgument or None if 1425 """Parses url and returns instance of _ParsedIssueNumberArgument or None if
1426 failed.""" 1426 failed."""
1427 raise NotImplementedError() 1427 raise NotImplementedError()
1428 1428
1429 def CMDUploadChange(self, options, args, change):
1430 """Uploads a change to codereview."""
1431 raise NotImplementedError()
1432
1429 1433
1430 class _RietveldChangelistImpl(_ChangelistCodereviewBase): 1434 class _RietveldChangelistImpl(_ChangelistCodereviewBase):
1431 def __init__(self, changelist, auth_config=None, rietveld_server=None): 1435 def __init__(self, changelist, auth_config=None, rietveld_server=None):
1432 super(_RietveldChangelistImpl, self).__init__(changelist) 1436 super(_RietveldChangelistImpl, self).__init__(changelist)
1433 assert settings, 'must be initialized in _ChangelistCodereviewBase' 1437 assert settings, 'must be initialized in _ChangelistCodereviewBase'
1434 settings.GetDefaultServerUrl() 1438 settings.GetDefaultServerUrl()
1435 1439
1436 self._rietveld_server = rietveld_server 1440 self._rietveld_server = rietveld_server
1437 self._auth_config = auth_config 1441 self._auth_config = auth_config
1438 self._props = None 1442 self._props = None
1439 self._rpc_server = None 1443 self._rpc_server = None
1440 1444
1441 def GetAuthConfig(self):
1442 return self._auth_config
1443
1444 def GetCodereviewServer(self): 1445 def GetCodereviewServer(self):
1445 if not self._rietveld_server: 1446 if not self._rietveld_server:
1446 # If we're on a branch then get the server potentially associated 1447 # If we're on a branch then get the server potentially associated
1447 # with that branch. 1448 # with that branch.
1448 if self.GetIssue(): 1449 if self.GetIssue():
1449 rietveld_server_setting = self.GetCodereviewServerSetting() 1450 rietveld_server_setting = self.GetCodereviewServerSetting()
1450 if rietveld_server_setting: 1451 if rietveld_server_setting:
1451 self._rietveld_server = gclient_utils.UpgradeToHttps(RunGit( 1452 self._rietveld_server = gclient_utils.UpgradeToHttps(RunGit(
1452 ['config', rietveld_server_setting], error_ok=True).strip()) 1453 ['config', rietveld_server_setting], error_ok=True).strip())
1453 if not self._rietveld_server: 1454 if not self._rietveld_server:
(...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after
1680 # Rietveld patch: https://domain/download/issue<number>_<patchset>.diff 1681 # Rietveld patch: https://domain/download/issue<number>_<patchset>.diff
1681 match = re.match(r'/download/issue(\d+)_(\d+).diff$', parsed_url.path) 1682 match = re.match(r'/download/issue(\d+)_(\d+).diff$', parsed_url.path)
1682 if match: 1683 if match:
1683 return _RietveldParsedIssueNumberArgument( 1684 return _RietveldParsedIssueNumberArgument(
1684 issue=int(match.group(1)), 1685 issue=int(match.group(1)),
1685 patchset=int(match.group(2)), 1686 patchset=int(match.group(2)),
1686 hostname=parsed_url.netloc, 1687 hostname=parsed_url.netloc,
1687 patch_url=gclient_utils.UpgradeToHttps(parsed_url.geturl())) 1688 patch_url=gclient_utils.UpgradeToHttps(parsed_url.geturl()))
1688 return None 1689 return None
1689 1690
1691 def CMDUploadChange(self, options, args, change):
1692 """Upload the patch to Rietveld."""
1693 upload_args = ['--assume_yes'] # Don't ask about untracked files.
1694 upload_args.extend(['--server', self.GetCodereviewServer()])
1695 # TODO(tandrii): refactor this ugliness into _RietveldChangelistImpl.
1696 upload_args.extend(auth.auth_config_to_command_options(self._auth_config))
1697 if options.emulate_svn_auto_props:
1698 upload_args.append('--emulate_svn_auto_props')
1699
1700 change_desc = None
1701
1702 if options.email is not None:
1703 upload_args.extend(['--email', options.email])
1704
1705 if self.GetIssue():
1706 if options.title:
1707 upload_args.extend(['--title', options.title])
1708 if options.message:
1709 upload_args.extend(['--message', options.message])
1710 upload_args.extend(['--issue', str(self.GetIssue())])
1711 print ('This branch is associated with issue %s. '
1712 'Adding patch to that issue.' % self.GetIssue())
1713 else:
1714 if options.title:
1715 upload_args.extend(['--title', options.title])
1716 message = (options.title or options.message or
1717 CreateDescriptionFromLog(args))
1718 change_desc = ChangeDescription(message)
1719 if options.reviewers or options.tbr_owners:
1720 change_desc.update_reviewers(options.reviewers,
1721 options.tbr_owners,
1722 change)
1723 if not options.force:
1724 change_desc.prompt()
1725
1726 if not change_desc.description:
1727 print "Description is empty; aborting."
1728 return 1
1729
1730 upload_args.extend(['--message', change_desc.description])
1731 if change_desc.get_reviewers():
1732 upload_args.append('--reviewers=%s' % ','.join(
1733 change_desc.get_reviewers()))
1734 if options.send_mail:
1735 if not change_desc.get_reviewers():
1736 DieWithError("Must specify reviewers to send email.")
1737 upload_args.append('--send_mail')
1738
1739 # We check this before applying rietveld.private assuming that in
1740 # rietveld.cc only addresses which we can send private CLs to are listed
1741 # if rietveld.private is set, and so we should ignore rietveld.cc only
1742 # when --private is specified explicitly on the command line.
1743 if options.private:
1744 logging.warn('rietveld.cc is ignored since private flag is specified. '
1745 'You need to review and add them manually if necessary.')
1746 cc = self.GetCCListWithoutDefault()
1747 else:
1748 cc = self.GetCCList()
1749 cc = ','.join(filter(None, (cc, ','.join(options.cc))))
1750 if cc:
1751 upload_args.extend(['--cc', cc])
1752
1753 if options.private or settings.GetDefaultPrivateFlag() == "True":
1754 upload_args.append('--private')
1755
1756 upload_args.extend(['--git_similarity', str(options.similarity)])
1757 if not options.find_copies:
1758 upload_args.extend(['--git_no_find_copies'])
1759
1760 # Include the upstream repo's URL in the change -- this is useful for
1761 # projects that have their source spread across multiple repos.
1762 remote_url = self.GetGitBaseUrlFromConfig()
1763 if not remote_url:
1764 if settings.GetIsGitSvn():
1765 remote_url = self.GetGitSvnRemoteUrl()
1766 else:
1767 if self.GetRemoteUrl() and '/' in self.GetUpstreamBranch():
1768 remote_url = '%s@%s' % (self.GetRemoteUrl(),
1769 self.GetUpstreamBranch().split('/')[-1])
1770 if remote_url:
1771 upload_args.extend(['--base_url', remote_url])
1772 remote, remote_branch = self.GetRemoteBranch()
1773 target_ref = GetTargetRef(remote, remote_branch, options.target_branch,
1774 settings.GetPendingRefPrefix())
1775 if target_ref:
1776 upload_args.extend(['--target_ref', target_ref])
1777
1778 # Look for dependent patchsets. See crbug.com/480453 for more details.
1779 remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch())
1780 upstream_branch = ShortBranchName(upstream_branch)
1781 if remote is '.':
1782 # A local branch is being tracked.
1783 local_branch = ShortBranchName(upstream_branch)
1784 if settings.GetIsSkipDependencyUpload(local_branch):
1785 print
1786 print ('Skipping dependency patchset upload because git config '
1787 'branch.%s.skip-deps-uploads is set to True.' % local_branch)
1788 print
1789 else:
1790 auth_config = auth.extract_auth_config_from_options(options)
1791 branch_cl = Changelist(branchref=local_branch,
1792 auth_config=auth_config)
1793 branch_cl_issue_url = branch_cl.GetIssueURL()
1794 branch_cl_issue = branch_cl.GetIssue()
1795 branch_cl_patchset = branch_cl.GetPatchset()
1796 if branch_cl_issue_url and branch_cl_issue and branch_cl_patchset:
1797 upload_args.extend(
1798 ['--depends_on_patchset', '%s:%s' % (
1799 branch_cl_issue, branch_cl_patchset)])
1800 print (
1801 '\n'
1802 'The current branch (%s) is tracking a local branch (%s) with '
1803 'an associated CL.\n'
1804 'Adding %s/#ps%s as a dependency patchset.\n'
1805 '\n' % (self.GetBranch(), local_branch, branch_cl_issue_url,
1806 branch_cl_patchset))
1807
1808 project = settings.GetProject()
1809 if project:
1810 upload_args.extend(['--project', project])
1811
1812 if options.cq_dry_run:
1813 upload_args.extend(['--cq_dry_run'])
1814
1815 try:
1816 upload_args = ['upload'] + upload_args + args
1817 logging.info('upload.RealMain(%s)', upload_args)
1818 issue, patchset = upload.RealMain(upload_args)
1819 issue = int(issue)
1820 patchset = int(patchset)
1821 except KeyboardInterrupt:
1822 sys.exit(1)
1823 except:
1824 # If we got an exception after the user typed a description for their
1825 # change, back up the description before re-raising.
1826 if change_desc:
1827 backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE)
1828 print('\nGot exception while uploading -- saving description to %s\n' %
1829 backup_path)
1830 backup_file = open(backup_path, 'w')
1831 backup_file.write(change_desc.description)
1832 backup_file.close()
1833 raise
1834
1835 if not self.GetIssue():
1836 self.SetIssue(issue)
1837 self.SetPatchset(patchset)
1838
1839 if options.use_commit_queue:
1840 self.SetFlag('commit', '1')
1841 return 0
1842
1690 1843
1691 class _GerritChangelistImpl(_ChangelistCodereviewBase): 1844 class _GerritChangelistImpl(_ChangelistCodereviewBase):
1692 def __init__(self, changelist, auth_config=None): 1845 def __init__(self, changelist, auth_config=None):
1693 # auth_config is Rietveld thing, kept here to preserve interface only. 1846 # auth_config is Rietveld thing, kept here to preserve interface only.
1694 super(_GerritChangelistImpl, self).__init__(changelist) 1847 super(_GerritChangelistImpl, self).__init__(changelist)
1695 self._change_id = None 1848 self._change_id = None
1696 self._gerrit_server = None # e.g. https://chromium-review.googlesource.com 1849 self._gerrit_server = None # e.g. https://chromium-review.googlesource.com
1697 self._gerrit_host = None # e.g. chromium-review.googlesource.com 1850 self._gerrit_host = None # e.g. chromium-review.googlesource.com
1698 1851
1699 def _GetGerritHost(self): 1852 def _GetGerritHost(self):
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after
1819 # TODO(tandrii) 1972 # TODO(tandrii)
1820 raise NotImplementedError() 1973 raise NotImplementedError()
1821 1974
1822 def CloseIssue(self): 1975 def CloseIssue(self):
1823 gerrit_util.AbandonChange(self._GetGerritHost(), self.GetIssue(), msg='') 1976 gerrit_util.AbandonChange(self._GetGerritHost(), self.GetIssue(), msg='')
1824 1977
1825 def SubmitIssue(self, wait_for_merge=True): 1978 def SubmitIssue(self, wait_for_merge=True):
1826 gerrit_util.SubmitChange(self._GetGerritHost(), self.GetIssue(), 1979 gerrit_util.SubmitChange(self._GetGerritHost(), self.GetIssue(),
1827 wait_for_merge=wait_for_merge) 1980 wait_for_merge=wait_for_merge)
1828 1981
1829 def _GetChangeDetail(self, options): 1982 def _GetChangeDetail(self, options=None, issue=None):
1830 return gerrit_util.GetChangeDetail(self._GetGerritHost(), self.GetIssue(), 1983 options = options or []
1831 options) 1984 issue = issue or self.GetIssue()
1985 assert issue, 'issue required to query Gerrit'
1986 return gerrit_util.GetChangeDetail(self._GetGerritHost(), options, issue)
1832 1987
1833 def CMDLand(self, force, bypass_hooks, verbose): 1988 def CMDLand(self, force, bypass_hooks, verbose):
1834 if git_common.is_dirty_git_tree('land'): 1989 if git_common.is_dirty_git_tree('land'):
1835 return 1 1990 return 1
1836 differs = True 1991 differs = True
1837 last_upload = RunGit(['config', 1992 last_upload = RunGit(['config',
1838 'branch.%s.gerritsquashhash' % self.GetBranch()], 1993 'branch.%s.gerritsquashhash' % self.GetBranch()],
1839 error_ok=True).strip() 1994 error_ok=True).strip()
1840 # Note: git diff outputs nothing if there is no diff. 1995 # Note: git diff outputs nothing if there is no diff.
1841 if not last_upload or RunGit(['diff', last_upload]).strip(): 1996 if not last_upload or RunGit(['diff', last_upload]).strip():
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
1915 else: 2070 else:
1916 part = parsed_url.path 2071 part = parsed_url.path
1917 match = re.match('(/c)?/(\d+)(/(\d+)?/?)?$', part) 2072 match = re.match('(/c)?/(\d+)(/(\d+)?/?)?$', part)
1918 if match: 2073 if match:
1919 return _ParsedIssueNumberArgument( 2074 return _ParsedIssueNumberArgument(
1920 issue=int(match.group(2)), 2075 issue=int(match.group(2)),
1921 patchset=int(match.group(4)) if match.group(4) else None, 2076 patchset=int(match.group(4)) if match.group(4) else None,
1922 hostname=parsed_url.netloc) 2077 hostname=parsed_url.netloc)
1923 return None 2078 return None
1924 2079
2080 def CMDUploadChange(self, options, args, change):
2081 """Upload the current branch to Gerrit."""
2082 # We assume the remote called "origin" is the one we want.
2083 # It is probably not worthwhile to support different workflows.
2084 gerrit_remote = 'origin'
2085
2086 remote, remote_branch = self.GetRemoteBranch()
2087 branch = GetTargetRef(remote, remote_branch, options.target_branch,
2088 pending_prefix='')
2089
2090 if options.title:
2091 # TODO(tandrii): it's now supported by Gerrit, implement!
2092 print "\nPatch titles (-t) are not supported in Gerrit. Aborting..."
2093 return 1
2094
2095 if options.squash:
2096 if not self.GetIssue():
2097 # TODO(tandrii): deperecate this after 2016Q2. Backwards compatibility
2098 # with shadow branch, which used to contain change-id for a given
2099 # branch, using which we can fetch actual issue number and set it as the
2100 # property of the branch, which is the new way.
2101 message = RunGitSilent([
2102 'show', '--format=%B', '-s',
2103 'refs/heads/git_cl_uploads/%s' % self.GetBranch()])
2104 if message:
2105 change_ids = git_footers.get_footer_change_id(message.strip())
2106 if change_ids and len(change_ids) == 1:
2107 details = self._GetChangeDetail(issue=change_ids[0])
2108 if details:
2109 print('WARNING: found old upload in branch git_cl_uploads/%s '
2110 'corresponding to issue %s' %
2111 (self.GetBranch(), details['_number']))
2112 self.SetIssue(details['_number'])
2113 if not self.GetIssue():
2114 DieWithError(
2115 '\n' # For readability of the blob below.
2116 'Found old upload in branch git_cl_uploads/%s, '
2117 'but failed to find corresponding Gerrit issue.\n'
2118 'If you know the issue number, set it manually first:\n'
2119 ' git cl issue 123456\n'
2120 'If you intended to upload this CL as new issue, '
2121 'just delete or rename the old upload branch:\n'
2122 ' git rename-branch git_cl_uploads/%s old_upload-%s\n'
2123 'After that, please run git cl upload again.' %
2124 tuple([self.GetBranch()] * 3))
2125 # End of backwards compatability.
2126
2127 if self.GetIssue():
2128 # Try to get the message from a previous upload.
2129 message = self.GetDescription()
2130 if not message:
2131 DieWithError(
2132 'failed to fetch description from current Gerrit issue %d\n'
2133 '%s' % (self.GetIssue(), self.GetIssueURL()))
2134 change_id = self._GetChangeDetail()['change_id']
2135 while True:
2136 footer_change_ids = git_footers.get_footer_change_id(message)
2137 if footer_change_ids == [change_id]:
2138 break
2139 if not footer_change_ids:
2140 message = git_footers.add_footer_change_id(message, change_id)
2141 print('WARNING: appended missing Change-Id to issue description')
2142 continue
2143 # There is already a valid footer but with different or several ids.
2144 # Doing this automatically is non-trivial as we don't want to lose
2145 # existing other footers, yet we want to append just 1 desired
2146 # Change-Id. Thus, just create a new footer, but let user verify the
2147 # new description.
2148 message = '%s\n\nChange-Id: %s' % (message, change_id)
2149 print(
2150 'WARNING: issue %s has Change-Id footer(s):\n'
2151 ' %s\n'
2152 'but issue has Change-Id %s, according to Gerrit.\n'
2153 'Please, check the proposed correction to the description, '
2154 'and edit it if necessary but keep the "Change-Id: %s" footer\n'
2155 % (self.GetIssue(), '\n '.join(footer_change_ids), change_id,
2156 change_id))
2157 ask_for_data('Press enter to edit now, Ctrl+C to abort')
2158 if not options.force:
2159 change_desc = ChangeDescription(message)
2160 change_desc.prompt()
2161 message = change_desc.description
2162 if not message:
2163 DieWithError("Description is empty. Aborting...")
2164 # Continue the while loop.
2165 # Sanity check of this code - we should end up with proper message
2166 # footer.
2167 assert [change_id] == git_footers.get_footer_change_id(message)
2168 change_desc = ChangeDescription(message)
2169 else:
2170 change_desc = ChangeDescription(
2171 options.message or CreateDescriptionFromLog(args))
2172 if not options.force:
2173 change_desc.prompt()
2174 if not change_desc.description:
2175 DieWithError("Description is empty. Aborting...")
2176 message = change_desc.description
2177 change_ids = git_footers.get_footer_change_id(message)
2178 if len(change_ids) > 1:
2179 DieWithError('too many Change-Id footers, at most 1 allowed.')
2180 if not change_ids:
2181 # Generate the Change-Id automatically.
2182 message = git_footers.add_footer_change_id(
2183 message, GenerateGerritChangeId(message))
2184 change_desc.set_description(message)
2185 change_ids = git_footers.get_footer_change_id(message)
2186 assert len(change_ids) == 1
2187 change_id = change_ids[0]
2188
2189 remote, upstream_branch = self.FetchUpstreamTuple(self.GetBranch())
2190 if remote is '.':
2191 # If our upstream branch is local, we base our squashed commit on its
2192 # squashed version.
2193 upstream_branch_name = scm.GIT.ShortBranchName(upstream_branch)
2194 # Check the squashed hash of the parent.
2195 parent = RunGit(['config',
2196 'branch.%s.gerritsquashhash' % upstream_branch_name],
2197 error_ok=True).strip()
2198 # Verify that the upstream branch has been uploaded too, otherwise
2199 # Gerrit will create additional CLs when uploading.
2200 if not parent or (RunGitSilent(['rev-parse', upstream_branch + ':']) !=
2201 RunGitSilent(['rev-parse', parent + ':'])):
2202 # TODO(tandrii): remove "old depot_tools" part on April 12, 2016.
2203 DieWithError(
2204 'Upload upstream branch %s first.\n'
2205 'Note: maybe you\'ve uploaded it with --no-squash or with an old '
2206 'version of depot_tools. If so, then re-upload it with:\n'
2207 ' git cl upload --squash\n' % upstream_branch_name)
2208 else:
2209 parent = self.GetCommonAncestorWithUpstream()
2210
2211 tree = RunGit(['rev-parse', 'HEAD:']).strip()
2212 ref_to_push = RunGit(['commit-tree', tree, '-p', parent,
2213 '-m', message]).strip()
2214 else:
2215 change_desc = ChangeDescription(
2216 options.message or CreateDescriptionFromLog(args))
2217 if not change_desc.description:
2218 DieWithError("Description is empty. Aborting...")
2219
2220 if not git_footers.get_footer_change_id(change_desc.description):
2221 DownloadGerritHook(False)
2222 change_desc.set_description(AddChangeIdToCommitMessage(options, args))
2223 ref_to_push = 'HEAD'
2224 parent = '%s/%s' % (gerrit_remote, branch)
2225 change_id = git_footers.get_footer_change_id(change_desc.description)[0]
2226
2227 assert change_desc
2228 commits = RunGitSilent(['rev-list', '%s..%s' % (parent,
2229 ref_to_push)]).splitlines()
2230 if len(commits) > 1:
2231 print('WARNING: This will upload %d commits. Run the following command '
2232 'to see which commits will be uploaded: ' % len(commits))
2233 print('git log %s..%s' % (parent, ref_to_push))
2234 print('You can also use `git squash-branch` to squash these into a '
2235 'single commit.')
2236 ask_for_data('About to upload; enter to confirm.')
2237
2238 if options.reviewers or options.tbr_owners:
2239 change_desc.update_reviewers(options.reviewers, options.tbr_owners,
2240 change)
2241
2242 receive_options = []
2243 cc = self.GetCCList().split(',')
2244 if options.cc:
2245 cc.extend(options.cc)
2246 cc = filter(None, cc)
2247 if cc:
2248 receive_options += ['--cc=' + email for email in cc]
2249 if change_desc.get_reviewers():
2250 receive_options.extend(
2251 '--reviewer=' + email for email in change_desc.get_reviewers())
2252
2253 git_command = ['push']
2254 if receive_options:
2255 git_command.append('--receive-pack=git receive-pack %s' %
2256 ' '.join(receive_options))
2257 git_command += [gerrit_remote, ref_to_push + ':refs/for/' + branch]
2258 push_stdout = gclient_utils.CheckCallAndFilter(
2259 ['git'] + git_command,
2260 print_stdout=True,
2261 # Flush after every line: useful for seeing progress when running as
2262 # recipe.
2263 filter_fn=lambda _: sys.stdout.flush())
2264
2265 if options.squash:
2266 regex = re.compile(r'remote:\s+https?://[\w\-\.\/]*/(\d+)\s.*')
2267 change_numbers = [m.group(1)
2268 for m in map(regex.match, push_stdout.splitlines())
2269 if m]
2270 if len(change_numbers) != 1:
2271 DieWithError(
2272 ('Created|Updated %d issues on Gerrit, but only 1 expected.\n'
2273 'Change-Id: %s') % (len(change_numbers), change_id))
2274 self.SetIssue(change_numbers[0])
2275 RunGit(['config', 'branch.%s.gerritsquashhash' % self.GetBranch(),
2276 ref_to_push])
2277 return 0
2278
2279
1925 2280
1926 _CODEREVIEW_IMPLEMENTATIONS = { 2281 _CODEREVIEW_IMPLEMENTATIONS = {
1927 'rietveld': _RietveldChangelistImpl, 2282 'rietveld': _RietveldChangelistImpl,
1928 'gerrit': _GerritChangelistImpl, 2283 'gerrit': _GerritChangelistImpl,
1929 } 2284 }
1930 2285
1931 2286
1932 class ChangeDescription(object): 2287 class ChangeDescription(object):
1933 """Contains a parsed form of the change description.""" 2288 """Contains a parsed form of the change description."""
1934 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$' 2289 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$'
(...skipping 852 matching lines...) Expand 10 before | Expand all | Expand 10 after
2787 lines.append('') 3142 lines.append('')
2788 # Note: Gerrit's commit-hook actually cleans message of some lines and 3143 # Note: Gerrit's commit-hook actually cleans message of some lines and
2789 # whitespace. This code is not doing this, but it clearly won't decrease 3144 # whitespace. This code is not doing this, but it clearly won't decrease
2790 # entropy. 3145 # entropy.
2791 lines.append(message) 3146 lines.append(message)
2792 change_hash = RunCommand(['git', 'hash-object', '-t', 'commit', '--stdin'], 3147 change_hash = RunCommand(['git', 'hash-object', '-t', 'commit', '--stdin'],
2793 stdin='\n'.join(lines)) 3148 stdin='\n'.join(lines))
2794 return 'I%s' % change_hash.strip() 3149 return 'I%s' % change_hash.strip()
2795 3150
2796 3151
2797 def GerritUpload(options, args, cl, change):
2798 """upload the current branch to gerrit."""
2799 # TODO(tandrii): refactor this to be a method of _GerritChangelistImpl,
2800 # to avoid private members accessors below.
2801
2802 # We assume the remote called "origin" is the one we want.
2803 # It is probably not worthwhile to support different workflows.
2804 gerrit_remote = 'origin'
2805
2806 remote, remote_branch = cl.GetRemoteBranch()
2807 branch = GetTargetRef(remote, remote_branch, options.target_branch,
2808 pending_prefix='')
2809
2810 if options.title:
2811 print "\nPatch titles (-t) are not supported in Gerrit. Aborting..."
2812 return 1
2813
2814 if options.squash:
2815 if not cl.GetIssue():
2816 # TODO(tandrii): deperecate this after 2016Q2.
2817 # Backwards compatibility with shadow branch, which used to contain
2818 # change-id for a given branch, using which we can fetch actual issue
2819 # number and set it as the property of the branch, which is the new way.
2820 message = RunGitSilent(['show', '--format=%B', '-s',
2821 'refs/heads/git_cl_uploads/%s' % cl.GetBranch()])
2822 if message:
2823 change_ids = git_footers.get_footer_change_id(message.strip())
2824 if change_ids and len(change_ids) == 1:
2825 details = gerrit_util.GetChangeDetail(
2826 cl._codereview_impl._GetGerritHost(), change_ids[0])
2827 if details:
2828 print('WARNING: found old upload in branch git_cl_uploads/%s '
2829 'corresponding to issue %s' %
2830 (cl.GetBranch(), details['_number']))
2831 cl.SetIssue(details['_number'])
2832 if not cl.GetIssue():
2833 DieWithError(
2834 '\n' # For readability of the blob below.
2835 'Found old upload in branch git_cl_uploads/%s, '
2836 'but failed to find corresponding Gerrit issue.\n'
2837 'If you know the issue number, set it manually first:\n'
2838 ' git cl issue 123456\n'
2839 'If you intended to upload this CL as new issue, '
2840 'just delete or rename the old upload branch:\n'
2841 ' git rename-branch git_cl_uploads/%s old_upload-%s\n'
2842 'After that, please run git cl upload again.' %
2843 tuple([cl.GetBranch()] * 3))
2844 # End of backwards compatability.
2845
2846 if cl.GetIssue():
2847 # Try to get the message from a previous upload.
2848 message = cl.GetDescription()
2849 if not message:
2850 DieWithError(
2851 'failed to fetch description from current Gerrit issue %d\n'
2852 '%s' % (cl.GetIssue(), cl.GetIssueURL()))
2853 change_id = cl._codereview_impl._GetChangeDetail([])['change_id']
2854 while True:
2855 footer_change_ids = git_footers.get_footer_change_id(message)
2856 if footer_change_ids == [change_id]:
2857 break
2858 if not footer_change_ids:
2859 message = git_footers.add_footer_change_id(message, change_id)
2860 print('WARNING: appended missing Change-Id to issue description')
2861 continue
2862 # There is already a valid footer but with different or several ids.
2863 # Doing this automatically is non-trivial as we don't want to lose
2864 # existing other footers, yet we want to append just 1 desired
2865 # Change-Id. Thus, just create a new footer, but let user verify the new
2866 # description.
2867 message = '%s\n\nChange-Id: %s' % (message, change_id)
2868 print(
2869 'WARNING: issue %s has Change-Id footer(s):\n'
2870 ' %s\n'
2871 'but issue has Change-Id %s, according to Gerrit.\n'
2872 'Please, check the proposed correction to the description, '
2873 'and edit it if necessary but keep the "Change-Id: %s" footer\n'
2874 % (cl.GetIssue(), '\n '.join(footer_change_ids), change_id,
2875 change_id))
2876 ask_for_data('Press enter to edit now, Ctrl+C to abort')
2877 if not options.force:
2878 change_desc = ChangeDescription(message)
2879 change_desc.prompt()
2880 message = change_desc.description
2881 if not message:
2882 DieWithError("Description is empty. Aborting...")
2883 # Continue the while loop.
2884 # Sanity check of this code - we should end up with proper message footer.
2885 assert [change_id] == git_footers.get_footer_change_id(message)
2886 change_desc = ChangeDescription(message)
2887 else:
2888 change_desc = ChangeDescription(
2889 options.message or CreateDescriptionFromLog(args))
2890 if not options.force:
2891 change_desc.prompt()
2892 if not change_desc.description:
2893 DieWithError("Description is empty. Aborting...")
2894 message = change_desc.description
2895 change_ids = git_footers.get_footer_change_id(message)
2896 if len(change_ids) > 1:
2897 DieWithError('too many Change-Id footers, at most 1 allowed.')
2898 if not change_ids:
2899 # Generate the Change-Id automatically.
2900 message = git_footers.add_footer_change_id(
2901 message, GenerateGerritChangeId(message))
2902 change_desc.set_description(message)
2903 change_ids = git_footers.get_footer_change_id(message)
2904 assert len(change_ids) == 1
2905 change_id = change_ids[0]
2906
2907 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
2908 if remote is '.':
2909 # If our upstream branch is local, we base our squashed commit on its
2910 # squashed version.
2911 upstream_branch_name = scm.GIT.ShortBranchName(upstream_branch)
2912 # Check the squashed hash of the parent.
2913 parent = RunGit(['config',
2914 'branch.%s.gerritsquashhash' % upstream_branch_name],
2915 error_ok=True).strip()
2916 # Verify that the upstream branch has been uploaded too, otherwise
2917 # Gerrit will create additional CLs when uploading.
2918 if not parent or (RunGitSilent(['rev-parse', upstream_branch + ':']) !=
2919 RunGitSilent(['rev-parse', parent + ':'])):
2920 # TODO(tandrii): remove "old depot_tools" part on April 12, 2016.
2921 DieWithError(
2922 'Upload upstream branch %s first.\n'
2923 'Note: maybe you\'ve uploaded it with --no-squash or with an old\n'
2924 ' version of depot_tools. If so, then re-upload it with:\n'
2925 ' git cl upload --squash\n' % upstream_branch_name)
2926 else:
2927 parent = cl.GetCommonAncestorWithUpstream()
2928
2929 tree = RunGit(['rev-parse', 'HEAD:']).strip()
2930 ref_to_push = RunGit(['commit-tree', tree, '-p', parent,
2931 '-m', message]).strip()
2932 else:
2933 change_desc = ChangeDescription(
2934 options.message or CreateDescriptionFromLog(args))
2935 if not change_desc.description:
2936 DieWithError("Description is empty. Aborting...")
2937
2938 if not git_footers.get_footer_change_id(change_desc.description):
2939 DownloadGerritHook(False)
2940 change_desc.set_description(AddChangeIdToCommitMessage(options, args))
2941 ref_to_push = 'HEAD'
2942 parent = '%s/%s' % (gerrit_remote, branch)
2943 change_id = git_footers.get_footer_change_id(change_desc.description)[0]
2944
2945 assert change_desc
2946 commits = RunGitSilent(['rev-list', '%s..%s' % (parent,
2947 ref_to_push)]).splitlines()
2948 if len(commits) > 1:
2949 print('WARNING: This will upload %d commits. Run the following command '
2950 'to see which commits will be uploaded: ' % len(commits))
2951 print('git log %s..%s' % (parent, ref_to_push))
2952 print('You can also use `git squash-branch` to squash these into a single '
2953 'commit.')
2954 ask_for_data('About to upload; enter to confirm.')
2955
2956 if options.reviewers or options.tbr_owners:
2957 change_desc.update_reviewers(options.reviewers, options.tbr_owners, change)
2958
2959 receive_options = []
2960 cc = cl.GetCCList().split(',')
2961 if options.cc:
2962 cc.extend(options.cc)
2963 cc = filter(None, cc)
2964 if cc:
2965 receive_options += ['--cc=' + email for email in cc]
2966 if change_desc.get_reviewers():
2967 receive_options.extend(
2968 '--reviewer=' + email for email in change_desc.get_reviewers())
2969
2970 git_command = ['push']
2971 if receive_options:
2972 git_command.append('--receive-pack=git receive-pack %s' %
2973 ' '.join(receive_options))
2974 git_command += [gerrit_remote, ref_to_push + ':refs/for/' + branch]
2975 push_stdout = gclient_utils.CheckCallAndFilter(
2976 ['git'] + git_command,
2977 print_stdout=True,
2978 # Flush after every line: useful for seeing progress when running as
2979 # recipe.
2980 filter_fn=lambda _: sys.stdout.flush())
2981
2982 if options.squash:
2983 regex = re.compile(r'remote:\s+https?://[\w\-\.\/]*/(\d+)\s.*')
2984 change_numbers = [m.group(1)
2985 for m in map(regex.match, push_stdout.splitlines())
2986 if m]
2987 if len(change_numbers) != 1:
2988 DieWithError(
2989 ('Created|Updated %d issues on Gerrit, but only 1 expected.\n'
2990 'Change-Id: %s') % (len(change_numbers), change_id))
2991 cl.SetIssue(change_numbers[0])
2992 RunGit(['config', 'branch.%s.gerritsquashhash' % cl.GetBranch(),
2993 ref_to_push])
2994 return 0
2995
2996
2997 def GetTargetRef(remote, remote_branch, target_branch, pending_prefix): 3152 def GetTargetRef(remote, remote_branch, target_branch, pending_prefix):
2998 """Computes the remote branch ref to use for the CL. 3153 """Computes the remote branch ref to use for the CL.
2999 3154
3000 Args: 3155 Args:
3001 remote (str): The git remote for the CL. 3156 remote (str): The git remote for the CL.
3002 remote_branch (str): The git remote branch for the CL. 3157 remote_branch (str): The git remote branch for the CL.
3003 target_branch (str): The target branch specified by the user. 3158 target_branch (str): The target branch specified by the user.
3004 pending_prefix (str): The pending prefix from the settings. 3159 pending_prefix (str): The pending prefix from the settings.
3005 """ 3160 """
3006 if not (remote and remote_branch): 3161 if not (remote and remote_branch):
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
3042 remote_branch = remote_branch.replace('refs/remotes/%s/' % remote, 3197 remote_branch = remote_branch.replace('refs/remotes/%s/' % remote,
3043 'refs/heads/') 3198 'refs/heads/')
3044 elif remote_branch.startswith('refs/remotes/branch-heads'): 3199 elif remote_branch.startswith('refs/remotes/branch-heads'):
3045 remote_branch = remote_branch.replace('refs/remotes/', 'refs/') 3200 remote_branch = remote_branch.replace('refs/remotes/', 'refs/')
3046 # If a pending prefix exists then replace refs/ with it. 3201 # If a pending prefix exists then replace refs/ with it.
3047 if pending_prefix: 3202 if pending_prefix:
3048 remote_branch = remote_branch.replace('refs/', pending_prefix) 3203 remote_branch = remote_branch.replace('refs/', pending_prefix)
3049 return remote_branch 3204 return remote_branch
3050 3205
3051 3206
3052 def RietveldUpload(options, args, cl, change):
3053 """upload the patch to rietveld."""
3054 upload_args = ['--assume_yes'] # Don't ask about untracked files.
3055 upload_args.extend(['--server', cl.GetCodereviewServer()])
3056 # TODO(tandrii): refactor this ugliness into _RietveldChangelistImpl.
3057 upload_args.extend(auth.auth_config_to_command_options(
3058 cl._codereview_impl.GetAuthConfig()))
3059 if options.emulate_svn_auto_props:
3060 upload_args.append('--emulate_svn_auto_props')
3061
3062 change_desc = None
3063
3064 if options.email is not None:
3065 upload_args.extend(['--email', options.email])
3066
3067 if cl.GetIssue():
3068 if options.title:
3069 upload_args.extend(['--title', options.title])
3070 if options.message:
3071 upload_args.extend(['--message', options.message])
3072 upload_args.extend(['--issue', str(cl.GetIssue())])
3073 print ("This branch is associated with issue %s. "
3074 "Adding patch to that issue." % cl.GetIssue())
3075 else:
3076 if options.title:
3077 upload_args.extend(['--title', options.title])
3078 message = options.title or options.message or CreateDescriptionFromLog(args)
3079 change_desc = ChangeDescription(message)
3080 if options.reviewers or options.tbr_owners:
3081 change_desc.update_reviewers(options.reviewers,
3082 options.tbr_owners,
3083 change)
3084 if not options.force:
3085 change_desc.prompt()
3086
3087 if not change_desc.description:
3088 print "Description is empty; aborting."
3089 return 1
3090
3091 upload_args.extend(['--message', change_desc.description])
3092 if change_desc.get_reviewers():
3093 upload_args.append('--reviewers=' + ','.join(change_desc.get_reviewers()))
3094 if options.send_mail:
3095 if not change_desc.get_reviewers():
3096 DieWithError("Must specify reviewers to send email.")
3097 upload_args.append('--send_mail')
3098
3099 # We check this before applying rietveld.private assuming that in
3100 # rietveld.cc only addresses which we can send private CLs to are listed
3101 # if rietveld.private is set, and so we should ignore rietveld.cc only when
3102 # --private is specified explicitly on the command line.
3103 if options.private:
3104 logging.warn('rietveld.cc is ignored since private flag is specified. '
3105 'You need to review and add them manually if necessary.')
3106 cc = cl.GetCCListWithoutDefault()
3107 else:
3108 cc = cl.GetCCList()
3109 cc = ','.join(filter(None, (cc, ','.join(options.cc))))
3110 if cc:
3111 upload_args.extend(['--cc', cc])
3112
3113 if options.private or settings.GetDefaultPrivateFlag() == "True":
3114 upload_args.append('--private')
3115
3116 upload_args.extend(['--git_similarity', str(options.similarity)])
3117 if not options.find_copies:
3118 upload_args.extend(['--git_no_find_copies'])
3119
3120 # Include the upstream repo's URL in the change -- this is useful for
3121 # projects that have their source spread across multiple repos.
3122 remote_url = cl.GetGitBaseUrlFromConfig()
3123 if not remote_url:
3124 if settings.GetIsGitSvn():
3125 remote_url = cl.GetGitSvnRemoteUrl()
3126 else:
3127 if cl.GetRemoteUrl() and '/' in cl.GetUpstreamBranch():
3128 remote_url = (cl.GetRemoteUrl() + '@'
3129 + cl.GetUpstreamBranch().split('/')[-1])
3130 if remote_url:
3131 upload_args.extend(['--base_url', remote_url])
3132 remote, remote_branch = cl.GetRemoteBranch()
3133 target_ref = GetTargetRef(remote, remote_branch, options.target_branch,
3134 settings.GetPendingRefPrefix())
3135 if target_ref:
3136 upload_args.extend(['--target_ref', target_ref])
3137
3138 # Look for dependent patchsets. See crbug.com/480453 for more details.
3139 remote, upstream_branch = cl.FetchUpstreamTuple(cl.GetBranch())
3140 upstream_branch = ShortBranchName(upstream_branch)
3141 if remote is '.':
3142 # A local branch is being tracked.
3143 local_branch = ShortBranchName(upstream_branch)
3144 if settings.GetIsSkipDependencyUpload(local_branch):
3145 print
3146 print ('Skipping dependency patchset upload because git config '
3147 'branch.%s.skip-deps-uploads is set to True.' % local_branch)
3148 print
3149 else:
3150 auth_config = auth.extract_auth_config_from_options(options)
3151 branch_cl = Changelist(branchref=local_branch, auth_config=auth_config)
3152 branch_cl_issue_url = branch_cl.GetIssueURL()
3153 branch_cl_issue = branch_cl.GetIssue()
3154 branch_cl_patchset = branch_cl.GetPatchset()
3155 if branch_cl_issue_url and branch_cl_issue and branch_cl_patchset:
3156 upload_args.extend(
3157 ['--depends_on_patchset', '%s:%s' % (
3158 branch_cl_issue, branch_cl_patchset)])
3159 print
3160 print ('The current branch (%s) is tracking a local branch (%s) with '
3161 'an associated CL.') % (cl.GetBranch(), local_branch)
3162 print 'Adding %s/#ps%s as a dependency patchset.' % (
3163 branch_cl_issue_url, branch_cl_patchset)
3164 print
3165
3166 project = settings.GetProject()
3167 if project:
3168 upload_args.extend(['--project', project])
3169
3170 if options.cq_dry_run:
3171 upload_args.extend(['--cq_dry_run'])
3172
3173 try:
3174 upload_args = ['upload'] + upload_args + args
3175 logging.info('upload.RealMain(%s)', upload_args)
3176 issue, patchset = upload.RealMain(upload_args)
3177 issue = int(issue)
3178 patchset = int(patchset)
3179 except KeyboardInterrupt:
3180 sys.exit(1)
3181 except:
3182 # If we got an exception after the user typed a description for their
3183 # change, back up the description before re-raising.
3184 if change_desc:
3185 backup_path = os.path.expanduser(DESCRIPTION_BACKUP_FILE)
3186 print '\nGot exception while uploading -- saving description to %s\n' \
3187 % backup_path
3188 backup_file = open(backup_path, 'w')
3189 backup_file.write(change_desc.description)
3190 backup_file.close()
3191 raise
3192
3193 if not cl.GetIssue():
3194 cl.SetIssue(issue)
3195 cl.SetPatchset(patchset)
3196
3197 if options.use_commit_queue:
3198 cl.SetFlag('commit', '1')
3199 return 0
3200
3201
3202 def cleanup_list(l): 3207 def cleanup_list(l):
3203 """Fixes a list so that comma separated items are put as individual items. 3208 """Fixes a list so that comma separated items are put as individual items.
3204 3209
3205 So that "--reviewers joe@c,john@c --reviewers joa@c" results in 3210 So that "--reviewers joe@c,john@c --reviewers joa@c" results in
3206 options.reviewers == sorted(['joe@c', 'john@c', 'joa@c']). 3211 options.reviewers == sorted(['joe@c', 'john@c', 'joa@c']).
3207 """ 3212 """
3208 items = sum((i.split(',') for i in l), []) 3213 items = sum((i.split(',') for i in l), [])
3209 stripped_items = (i.strip() for i in items) 3214 stripped_items = (i.strip() for i in items)
3210 return sorted(filter(None, stripped_items)) 3215 return sorted(filter(None, stripped_items))
3211 3216
(...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after
3342 ask_for_data('About to upload; enter to confirm.') 3347 ask_for_data('About to upload; enter to confirm.')
3343 3348
3344 print_stats(options.similarity, options.find_copies, args) 3349 print_stats(options.similarity, options.find_copies, args)
3345 if cl.IsGerrit(): 3350 if cl.IsGerrit():
3346 if options.squash and options.no_squash: 3351 if options.squash and options.no_squash:
3347 DieWithError('Can only use one of --squash or --no-squash') 3352 DieWithError('Can only use one of --squash or --no-squash')
3348 3353
3349 options.squash = ((settings.GetSquashGerritUploads() or options.squash) and 3354 options.squash = ((settings.GetSquashGerritUploads() or options.squash) and
3350 not options.no_squash) 3355 not options.no_squash)
3351 3356
3352 ret = GerritUpload(options, args, cl, change) 3357 ret = cl.CMDUploadChange(options, args, change)
3353 else:
3354 ret = RietveldUpload(options, args, cl, change)
3355 if not ret: 3358 if not ret:
3356 git_set_branch_value('last-upload-hash', 3359 git_set_branch_value('last-upload-hash',
3357 RunGit(['rev-parse', 'HEAD']).strip()) 3360 RunGit(['rev-parse', 'HEAD']).strip())
3358 # Run post upload hooks, if specified. 3361 # Run post upload hooks, if specified.
3359 if settings.GetRunPostUploadHook(): 3362 if settings.GetRunPostUploadHook():
3360 presubmit_support.DoPostUploadExecuter( 3363 presubmit_support.DoPostUploadExecuter(
3361 change, 3364 change,
3362 cl, 3365 cl,
3363 settings.GetRoot(), 3366 settings.GetRoot(),
3364 options.verbose, 3367 options.verbose,
(...skipping 1269 matching lines...) Expand 10 before | Expand all | Expand 10 after
4634 if __name__ == '__main__': 4637 if __name__ == '__main__':
4635 # These affect sys.stdout so do it outside of main() to simplify mocks in 4638 # These affect sys.stdout so do it outside of main() to simplify mocks in
4636 # unit testing. 4639 # unit testing.
4637 fix_encoding.fix_encoding() 4640 fix_encoding.fix_encoding()
4638 setup_color.init() 4641 setup_color.init()
4639 try: 4642 try:
4640 sys.exit(main(sys.argv[1:])) 4643 sys.exit(main(sys.argv[1:]))
4641 except KeyboardInterrupt: 4644 except KeyboardInterrupt:
4642 sys.stderr.write('interrupted\n') 4645 sys.stderr.write('interrupted\n')
4643 sys.exit(1) 4646 sys.exit(1)
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698