OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 # Copyright (C) 2008 Evan Martin <martine@danga.com> | 6 # Copyright (C) 2008 Evan Martin <martine@danga.com> |
7 | 7 |
8 """A git-command for integrating reviews on Rietveld and Gerrit.""" | 8 """A git-command for integrating reviews on Rietveld and Gerrit.""" |
9 | 9 |
10 from __future__ import print_function | 10 from __future__ import print_function |
(...skipping 22 matching lines...) Expand all Loading... |
33 | 33 |
34 try: | 34 try: |
35 import readline # pylint: disable=F0401,W0611 | 35 import readline # pylint: disable=F0401,W0611 |
36 except ImportError: | 36 except ImportError: |
37 pass | 37 pass |
38 | 38 |
39 from third_party import colorama | 39 from third_party import colorama |
40 from third_party import httplib2 | 40 from third_party import httplib2 |
41 from third_party import upload | 41 from third_party import upload |
42 import auth | 42 import auth |
| 43 import checkout |
43 import clang_format | 44 import clang_format |
44 import commit_queue | 45 import commit_queue |
45 import dart_format | 46 import dart_format |
46 import setup_color | 47 import setup_color |
47 import fix_encoding | 48 import fix_encoding |
48 import gclient_utils | 49 import gclient_utils |
49 import gerrit_util | 50 import gerrit_util |
50 import git_cache | 51 import git_cache |
51 import git_common | 52 import git_common |
52 import git_footers | 53 import git_footers |
(...skipping 930 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
983 def __init__(self, issue=None, patchset=None, hostname=None): | 984 def __init__(self, issue=None, patchset=None, hostname=None): |
984 self.issue = issue | 985 self.issue = issue |
985 self.patchset = patchset | 986 self.patchset = patchset |
986 self.hostname = hostname | 987 self.hostname = hostname |
987 | 988 |
988 @property | 989 @property |
989 def valid(self): | 990 def valid(self): |
990 return self.issue is not None | 991 return self.issue is not None |
991 | 992 |
992 | 993 |
993 class _RietveldParsedIssueNumberArgument(_ParsedIssueNumberArgument): | |
994 def __init__(self, *args, **kwargs): | |
995 self.patch_url = kwargs.pop('patch_url', None) | |
996 super(_RietveldParsedIssueNumberArgument, self).__init__(*args, **kwargs) | |
997 | |
998 | |
999 def ParseIssueNumberArgument(arg): | 994 def ParseIssueNumberArgument(arg): |
1000 """Parses the issue argument and returns _ParsedIssueNumberArgument.""" | 995 """Parses the issue argument and returns _ParsedIssueNumberArgument.""" |
1001 fail_result = _ParsedIssueNumberArgument() | 996 fail_result = _ParsedIssueNumberArgument() |
1002 | 997 |
1003 if arg.isdigit(): | 998 if arg.isdigit(): |
1004 return _ParsedIssueNumberArgument(issue=int(arg)) | 999 return _ParsedIssueNumberArgument(issue=int(arg)) |
1005 if not arg.startswith('http'): | 1000 if not arg.startswith('http'): |
1006 return fail_result | 1001 return fail_result |
1007 url = gclient_utils.UpgradeToHttps(arg) | 1002 url = gclient_utils.UpgradeToHttps(arg) |
1008 try: | 1003 try: |
(...skipping 796 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1805 DieWithError( | 1800 DieWithError( |
1806 '\nFailed to fetch issue description. HTTP error %d' % e.code) | 1801 '\nFailed to fetch issue description. HTTP error %d' % e.code) |
1807 except urllib2.URLError as e: | 1802 except urllib2.URLError as e: |
1808 print('Warning: Failed to retrieve CL description due to network ' | 1803 print('Warning: Failed to retrieve CL description due to network ' |
1809 'failure.', file=sys.stderr) | 1804 'failure.', file=sys.stderr) |
1810 return '' | 1805 return '' |
1811 | 1806 |
1812 def GetMostRecentPatchset(self): | 1807 def GetMostRecentPatchset(self): |
1813 return self.GetIssueProperties()['patchsets'][-1] | 1808 return self.GetIssueProperties()['patchsets'][-1] |
1814 | 1809 |
1815 def GetPatchSetDiff(self, issue, patchset): | |
1816 return self.RpcServer().get( | |
1817 '/download/issue%s_%s.diff' % (issue, patchset)) | |
1818 | |
1819 def GetIssueProperties(self): | 1810 def GetIssueProperties(self): |
1820 if self._props is None: | 1811 if self._props is None: |
1821 issue = self.GetIssue() | 1812 issue = self.GetIssue() |
1822 if not issue: | 1813 if not issue: |
1823 self._props = {} | 1814 self._props = {} |
1824 else: | 1815 else: |
1825 self._props = self.RpcServer().get_issue_properties(issue, True) | 1816 self._props = self.RpcServer().get_issue_properties(issue, True) |
1826 return self._props | 1817 return self._props |
1827 | 1818 |
1828 def CannotTriggerTryJobReason(self): | 1819 def CannotTriggerTryJobReason(self): |
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1965 self.SetFlags({'commit': '1', 'cq_dry_run': '0'}) | 1956 self.SetFlags({'commit': '1', 'cq_dry_run': '0'}) |
1966 elif new_state == _CQState.NONE: | 1957 elif new_state == _CQState.NONE: |
1967 self.SetFlags({'commit': '0', 'cq_dry_run': '0'}) | 1958 self.SetFlags({'commit': '0', 'cq_dry_run': '0'}) |
1968 else: | 1959 else: |
1969 assert new_state == _CQState.DRY_RUN | 1960 assert new_state == _CQState.DRY_RUN |
1970 self.SetFlags({'commit': '1', 'cq_dry_run': '1'}) | 1961 self.SetFlags({'commit': '1', 'cq_dry_run': '1'}) |
1971 | 1962 |
1972 | 1963 |
1973 def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit, | 1964 def CMDPatchWithParsedIssue(self, parsed_issue_arg, reject, nocommit, |
1974 directory): | 1965 directory): |
1975 # TODO(maruel): Use apply_issue.py | |
1976 | |
1977 # PatchIssue should never be called with a dirty tree. It is up to the | 1966 # PatchIssue should never be called with a dirty tree. It is up to the |
1978 # caller to check this, but just in case we assert here since the | 1967 # caller to check this, but just in case we assert here since the |
1979 # consequences of the caller not checking this could be dire. | 1968 # consequences of the caller not checking this could be dire. |
1980 assert(not git_common.is_dirty_git_tree('apply')) | 1969 assert(not git_common.is_dirty_git_tree('apply')) |
1981 assert(parsed_issue_arg.valid) | 1970 assert(parsed_issue_arg.valid) |
1982 self._changelist.issue = parsed_issue_arg.issue | 1971 self._changelist.issue = parsed_issue_arg.issue |
1983 if parsed_issue_arg.hostname: | 1972 if parsed_issue_arg.hostname: |
1984 self._rietveld_server = 'https://%s' % parsed_issue_arg.hostname | 1973 self._rietveld_server = 'https://%s' % parsed_issue_arg.hostname |
1985 | 1974 |
1986 if (isinstance(parsed_issue_arg, _RietveldParsedIssueNumberArgument) and | 1975 patchset = parsed_issue_arg.patchset or self.GetMostRecentPatchset() |
1987 parsed_issue_arg.patch_url): | 1976 patchset_object = self.RpcServer().get_patch(self.GetIssue(), patchset) |
1988 assert parsed_issue_arg.patchset | 1977 scm_obj = checkout.GitCheckout(settings.GetRoot(), None, None, None, None) |
1989 patchset = parsed_issue_arg.patchset | |
1990 patch_data = urllib2.urlopen(parsed_issue_arg.patch_url).read() | |
1991 else: | |
1992 patchset = parsed_issue_arg.patchset or self.GetMostRecentPatchset() | |
1993 patch_data = self.GetPatchSetDiff(self.GetIssue(), patchset) | |
1994 | |
1995 # Switch up to the top-level directory, if necessary, in preparation for | |
1996 # applying the patch. | |
1997 top = settings.GetRelativeRoot() | |
1998 if top: | |
1999 os.chdir(top) | |
2000 | |
2001 # Git patches have a/ at the beginning of source paths. We strip that out | |
2002 # with a sed script rather than the -p flag to patch so we can feed either | |
2003 # Git or svn-style patches into the same apply command. | |
2004 # re.sub() should be used but flags=re.MULTILINE is only in python 2.7. | |
2005 try: | 1978 try: |
2006 patch_data = subprocess2.check_output( | 1979 scm_obj.apply_patch(patchset_object) |
2007 ['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'], stdin=patch_data) | 1980 except Exception as e: |
2008 except subprocess2.CalledProcessError: | 1981 print(str(e)) |
2009 DieWithError('Git patch mungling failed.') | |
2010 logging.info(patch_data) | |
2011 | |
2012 # We use "git apply" to apply the patch instead of "patch" so that we can | |
2013 # pick up file adds. | |
2014 # The --index flag means: also insert into the index (so we catch adds). | |
2015 cmd = ['git', 'apply', '--index', '-p0'] | |
2016 if directory: | |
2017 cmd.extend(('--directory', directory)) | |
2018 if reject: | |
2019 cmd.append('--reject') | |
2020 elif IsGitVersionAtLeast('1.7.12'): | |
2021 cmd.append('--3way') | |
2022 try: | |
2023 subprocess2.check_call(cmd, env=GetNoGitPagerEnv(), | |
2024 stdin=patch_data, stdout=subprocess2.VOID) | |
2025 except subprocess2.CalledProcessError: | |
2026 print('Failed to apply the patch') | |
2027 return 1 | 1982 return 1 |
2028 | 1983 |
2029 # If we had an issue, commit the current state and register the issue. | 1984 # If we had an issue, commit the current state and register the issue. |
2030 if not nocommit: | 1985 if not nocommit: |
2031 RunGit(['commit', '-m', (self.GetDescription() + '\n\n' + | 1986 RunGit(['commit', '-m', (self.GetDescription() + '\n\n' + |
2032 'patch from issue %(i)s at patchset ' | 1987 'patch from issue %(i)s at patchset ' |
2033 '%(p)s (http://crrev.com/%(i)s#ps%(p)s)' | 1988 '%(p)s (http://crrev.com/%(i)s#ps%(p)s)' |
2034 % {'i': self.GetIssue(), 'p': patchset})]) | 1989 % {'i': self.GetIssue(), 'p': patchset})]) |
2035 self.SetIssue(self.GetIssue()) | 1990 self.SetIssue(self.GetIssue()) |
2036 self.SetPatchset(patchset) | 1991 self.SetPatchset(patchset) |
2037 print('Committed patch locally.') | 1992 print('Committed patch locally.') |
2038 else: | 1993 else: |
2039 print('Patch applied to index.') | 1994 print('Patch applied to index.') |
2040 return 0 | 1995 return 0 |
2041 | 1996 |
2042 @staticmethod | 1997 @staticmethod |
2043 def ParseIssueURL(parsed_url): | 1998 def ParseIssueURL(parsed_url): |
2044 if not parsed_url.scheme or not parsed_url.scheme.startswith('http'): | 1999 if not parsed_url.scheme or not parsed_url.scheme.startswith('http'): |
2045 return None | 2000 return None |
2046 # Rietveld patch: https://domain/<number>/#ps<patchset> | 2001 # Rietveld patch: https://domain/<number>/#ps<patchset> |
2047 match = re.match(r'/(\d+)/$', parsed_url.path) | 2002 match = re.match(r'/(\d+)/$', parsed_url.path) |
2048 match2 = re.match(r'ps(\d+)$', parsed_url.fragment) | 2003 match2 = re.match(r'ps(\d+)$', parsed_url.fragment) |
2049 if match and match2: | 2004 if match and match2: |
2050 return _RietveldParsedIssueNumberArgument( | 2005 return _ParsedIssueNumberArgument( |
2051 issue=int(match.group(1)), | 2006 issue=int(match.group(1)), |
2052 patchset=int(match2.group(1)), | 2007 patchset=int(match2.group(1)), |
2053 hostname=parsed_url.netloc) | 2008 hostname=parsed_url.netloc) |
2054 # Typical url: https://domain/<issue_number>[/[other]] | 2009 # Typical url: https://domain/<issue_number>[/[other]] |
2055 match = re.match('/(\d+)(/.*)?$', parsed_url.path) | 2010 match = re.match('/(\d+)(/.*)?$', parsed_url.path) |
2056 if match: | 2011 if match: |
2057 return _RietveldParsedIssueNumberArgument( | 2012 return _ParsedIssueNumberArgument( |
2058 issue=int(match.group(1)), | 2013 issue=int(match.group(1)), |
2059 hostname=parsed_url.netloc) | 2014 hostname=parsed_url.netloc) |
2060 # Rietveld patch: https://domain/download/issue<number>_<patchset>.diff | 2015 # Rietveld patch: https://domain/download/issue<number>_<patchset>.diff |
2061 match = re.match(r'/download/issue(\d+)_(\d+).diff$', parsed_url.path) | 2016 match = re.match(r'/download/issue(\d+)_(\d+).diff$', parsed_url.path) |
2062 if match: | 2017 if match: |
2063 return _RietveldParsedIssueNumberArgument( | 2018 return _ParsedIssueNumberArgument( |
2064 issue=int(match.group(1)), | 2019 issue=int(match.group(1)), |
2065 patchset=int(match.group(2)), | 2020 patchset=int(match.group(2)), |
2066 hostname=parsed_url.netloc, | 2021 hostname=parsed_url.netloc) |
2067 patch_url=gclient_utils.UpgradeToHttps(parsed_url.geturl())) | |
2068 return None | 2022 return None |
2069 | 2023 |
2070 def CMDUploadChange(self, options, args, change): | 2024 def CMDUploadChange(self, options, args, change): |
2071 """Upload the patch to Rietveld.""" | 2025 """Upload the patch to Rietveld.""" |
2072 upload_args = ['--assume_yes'] # Don't ask about untracked files. | 2026 upload_args = ['--assume_yes'] # Don't ask about untracked files. |
2073 upload_args.extend(['--server', self.GetCodereviewServer()]) | 2027 upload_args.extend(['--server', self.GetCodereviewServer()]) |
2074 upload_args.extend(auth.auth_config_to_command_options(self._auth_config)) | 2028 upload_args.extend(auth.auth_config_to_command_options(self._auth_config)) |
2075 if options.emulate_svn_auto_props: | 2029 if options.emulate_svn_auto_props: |
2076 upload_args.append('--emulate_svn_auto_props') | 2030 upload_args.append('--emulate_svn_auto_props') |
2077 | 2031 |
(...skipping 3336 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5414 if __name__ == '__main__': | 5368 if __name__ == '__main__': |
5415 # These affect sys.stdout so do it outside of main() to simplify mocks in | 5369 # These affect sys.stdout so do it outside of main() to simplify mocks in |
5416 # unit testing. | 5370 # unit testing. |
5417 fix_encoding.fix_encoding() | 5371 fix_encoding.fix_encoding() |
5418 setup_color.init() | 5372 setup_color.init() |
5419 try: | 5373 try: |
5420 sys.exit(main(sys.argv[1:])) | 5374 sys.exit(main(sys.argv[1:])) |
5421 except KeyboardInterrupt: | 5375 except KeyboardInterrupt: |
5422 sys.stderr.write('interrupted\n') | 5376 sys.stderr.write('interrupted\n') |
5423 sys.exit(1) | 5377 sys.exit(1) |
OLD | NEW |