| 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 |