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.""" |
9 | 9 |
10 from distutils.version import LooseVersion | 10 from distutils.version import LooseVersion |
11 from multiprocessing.pool import ThreadPool | 11 from multiprocessing.pool import ThreadPool |
12 import base64 | 12 import base64 |
13 import collections | 13 import collections |
14 import glob | 14 import glob |
15 import httplib | 15 import httplib |
16 import json | 16 import json |
17 import logging | 17 import logging |
18 import optparse | 18 import optparse |
(...skipping 21 matching lines...) Expand all Loading... |
40 from third_party import colorama | 40 from third_party import colorama |
41 from third_party import httplib2 | 41 from third_party import httplib2 |
42 from third_party import upload | 42 from third_party import upload |
43 import auth | 43 import auth |
44 from luci_hacks import trigger_luci_job as luci_trigger | 44 from luci_hacks import trigger_luci_job as luci_trigger |
45 import clang_format | 45 import clang_format |
46 import commit_queue | 46 import commit_queue |
47 import dart_format | 47 import dart_format |
48 import fix_encoding | 48 import fix_encoding |
49 import gclient_utils | 49 import gclient_utils |
50 import gerrit_util | |
51 import git_cache | 50 import git_cache |
52 import git_common | 51 import git_common |
53 import git_footers | 52 import git_footers |
54 import owners | 53 import owners |
55 import owners_finder | 54 import owners_finder |
56 import presubmit_support | 55 import presubmit_support |
57 import rietveld | 56 import rietveld |
58 import scm | 57 import scm |
59 import subcommand | 58 import subcommand |
60 import subprocess2 | 59 import subprocess2 |
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
150 | 149 |
151 def ask_for_data(prompt): | 150 def ask_for_data(prompt): |
152 try: | 151 try: |
153 return raw_input(prompt) | 152 return raw_input(prompt) |
154 except KeyboardInterrupt: | 153 except KeyboardInterrupt: |
155 # Hide the exception. | 154 # Hide the exception. |
156 sys.exit(1) | 155 sys.exit(1) |
157 | 156 |
158 | 157 |
159 def git_set_branch_value(key, value): | 158 def git_set_branch_value(key, value): |
160 branch = GetCurrentBranch() | 159 branch = Changelist().GetBranch() |
161 if not branch: | 160 if not branch: |
162 return | 161 return |
163 | 162 |
164 cmd = ['config'] | 163 cmd = ['config'] |
165 if isinstance(value, int): | 164 if isinstance(value, int): |
166 cmd.append('--int') | 165 cmd.append('--int') |
167 git_key = 'branch.%s.%s' % (branch, key) | 166 git_key = 'branch.%s.%s' % (branch, key) |
168 RunGit(cmd + [git_key, str(value)]) | 167 RunGit(cmd + [git_key, str(value)]) |
169 | 168 |
170 | 169 |
171 def git_get_branch_default(key, default): | 170 def git_get_branch_default(key, default): |
172 branch = GetCurrentBranch() | 171 branch = Changelist().GetBranch() |
173 if branch: | 172 if branch: |
174 git_key = 'branch.%s.%s' % (branch, key) | 173 git_key = 'branch.%s.%s' % (branch, key) |
175 (_, stdout) = RunGitWithCode(['config', '--int', '--get', git_key]) | 174 (_, stdout) = RunGitWithCode(['config', '--int', '--get', git_key]) |
176 try: | 175 try: |
177 return int(stdout.strip()) | 176 return int(stdout.strip()) |
178 except ValueError: | 177 except ValueError: |
179 pass | 178 pass |
180 return default | 179 return default |
181 | 180 |
182 | 181 |
(...skipping 624 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
807 def _GetBranchConfig(self, branch_name, param, **kwargs): | 806 def _GetBranchConfig(self, branch_name, param, **kwargs): |
808 return self._GetConfig('branch.' + branch_name + '.' + param, **kwargs) | 807 return self._GetConfig('branch.' + branch_name + '.' + param, **kwargs) |
809 | 808 |
810 def _GetConfig(self, param, **kwargs): | 809 def _GetConfig(self, param, **kwargs): |
811 self.LazyUpdateIfNeeded() | 810 self.LazyUpdateIfNeeded() |
812 return RunGit(['config', param], **kwargs).strip() | 811 return RunGit(['config', param], **kwargs).strip() |
813 | 812 |
814 | 813 |
815 def ShortBranchName(branch): | 814 def ShortBranchName(branch): |
816 """Convert a name like 'refs/heads/foo' to just 'foo'.""" | 815 """Convert a name like 'refs/heads/foo' to just 'foo'.""" |
817 return branch.replace('refs/heads/', '', 1) | 816 return branch.replace('refs/heads/', '') |
818 | |
819 | |
820 def GetCurrentBranchRef(): | |
821 """Returns branch ref (e.g., refs/heads/master) or None.""" | |
822 return RunGit(['symbolic-ref', 'HEAD'], | |
823 stderr=subprocess2.VOID, error_ok=True).strip() or None | |
824 | |
825 | |
826 def GetCurrentBranch(): | |
827 """Returns current branch or None. | |
828 | |
829 For refs/heads/* branches, returns just last part. For others, full ref. | |
830 """ | |
831 branchref = GetCurrentBranchRef() | |
832 if branchref: | |
833 return ShortBranchName(branchref) | |
834 return None | |
835 | 817 |
836 | 818 |
837 class Changelist(object): | 819 class Changelist(object): |
838 """Changelist works with one changelist in local branch. | 820 def __init__(self, branchref=None, issue=None, auth_config=None): |
839 | |
840 Supports two codereview backends: Rietveld or Gerrit, selected at object | |
841 creation. | |
842 | |
843 Not safe for concurrent multi-{thread,process} use. | |
844 """ | |
845 | |
846 def __init__(self, branchref=None, issue=None, codereview=None, **kwargs): | |
847 """Create a new ChangeList instance. | |
848 | |
849 If issue is given, the codereview must be given too. | |
850 | |
851 If `codereview` is given, it must be 'rietveld' or 'gerrit'. | |
852 Otherwise, it's decided based on current configuration of the local branch, | |
853 with default being 'rietveld' for backwards compatibility. | |
854 See _load_codereview_impl for more details. | |
855 | |
856 **kwargs will be passed directly to codereview implementation. | |
857 """ | |
858 # Poke settings so we get the "configure your server" message if necessary. | 821 # Poke settings so we get the "configure your server" message if necessary. |
859 global settings | 822 global settings |
860 if not settings: | 823 if not settings: |
861 # Happens when git_cl.py is used as a utility library. | 824 # Happens when git_cl.py is used as a utility library. |
862 settings = Settings() | 825 settings = Settings() |
863 | 826 settings.GetDefaultServerUrl() |
864 if issue: | |
865 assert codereview, 'codereview must be known, if issue is known' | |
866 | |
867 self.branchref = branchref | 827 self.branchref = branchref |
868 if self.branchref: | 828 if self.branchref: |
869 self.branch = ShortBranchName(self.branchref) | 829 self.branch = ShortBranchName(self.branchref) |
870 else: | 830 else: |
871 self.branch = None | 831 self.branch = None |
| 832 self.rietveld_server = None |
872 self.upstream_branch = None | 833 self.upstream_branch = None |
873 self.lookedup_issue = False | 834 self.lookedup_issue = False |
874 self.issue = issue or None | 835 self.issue = issue or None |
875 self.has_description = False | 836 self.has_description = False |
876 self.description = None | 837 self.description = None |
877 self.lookedup_patchset = False | 838 self.lookedup_patchset = False |
878 self.patchset = None | 839 self.patchset = None |
879 self.cc = None | 840 self.cc = None |
880 self.watchers = () | 841 self.watchers = () |
| 842 self._auth_config = auth_config |
| 843 self._props = None |
881 self._remote = None | 844 self._remote = None |
| 845 self._rpc_server = None |
882 | 846 |
883 self._codereview_impl = None | 847 @property |
884 self._load_codereview_impl(codereview, **kwargs) | 848 def auth_config(self): |
885 | 849 return self._auth_config |
886 def _load_codereview_impl(self, codereview=None, **kwargs): | |
887 if codereview: | |
888 codereview = codereview.lower() | |
889 if codereview == 'gerrit': | |
890 self._codereview_impl = _GerritChangelistImpl(self, **kwargs) | |
891 elif codereview == 'rietveld': | |
892 self._codereview_impl = _RietveldChangelistImpl(self, **kwargs) | |
893 else: | |
894 assert codereview in ('rietveld', 'gerrit') | |
895 return | |
896 | |
897 # Automatic selection. | |
898 assert not self.issue | |
899 # Check if this branch is associated with Rietveld => Rieveld. | |
900 self._codereview_impl = _RietveldChangelistImpl(self, **kwargs) | |
901 if self.GetIssue(force_lookup=True): | |
902 return | |
903 | |
904 tmp_rietveld = self._codereview_impl # Save Rietveld object. | |
905 | |
906 # Check if this branch has Gerrit issue associated => Gerrit. | |
907 self._codereview_impl = _GerritChangelistImpl(self, **kwargs) | |
908 if self.GetIssue(force_lookup=True): | |
909 return | |
910 | |
911 # OK, no issue is set for this branch. | |
912 # If Gerrit is set repo-wide => Gerrit. | |
913 if settings.GetIsGerrit(): | |
914 return | |
915 | |
916 self._codereview_impl = tmp_rietveld | |
917 return | |
918 | |
919 | 850 |
920 def GetCCList(self): | 851 def GetCCList(self): |
921 """Return the users cc'd on this CL. | 852 """Return the users cc'd on this CL. |
922 | 853 |
923 Return is a string suitable for passing to gcl with the --cc flag. | 854 Return is a string suitable for passing to gcl with the --cc flag. |
924 """ | 855 """ |
925 if self.cc is None: | 856 if self.cc is None: |
926 base_cc = settings.GetDefaultCCList() | 857 base_cc = settings.GetDefaultCCList() |
927 more_cc = ','.join(self.watchers) | 858 more_cc = ','.join(self.watchers) |
928 self.cc = ','.join(filter(None, (base_cc, more_cc))) or '' | 859 self.cc = ','.join(filter(None, (base_cc, more_cc))) or '' |
929 return self.cc | 860 return self.cc |
930 | 861 |
931 def GetCCListWithoutDefault(self): | 862 def GetCCListWithoutDefault(self): |
932 """Return the users cc'd on this CL excluding default ones.""" | 863 """Return the users cc'd on this CL excluding default ones.""" |
933 if self.cc is None: | 864 if self.cc is None: |
934 self.cc = ','.join(self.watchers) | 865 self.cc = ','.join(self.watchers) |
935 return self.cc | 866 return self.cc |
936 | 867 |
937 def SetWatchers(self, watchers): | 868 def SetWatchers(self, watchers): |
938 """Set the list of email addresses that should be cc'd based on the changed | 869 """Set the list of email addresses that should be cc'd based on the changed |
939 files in this CL. | 870 files in this CL. |
940 """ | 871 """ |
941 self.watchers = watchers | 872 self.watchers = watchers |
942 | 873 |
943 def GetBranch(self): | 874 def GetBranch(self): |
944 """Returns the short branch name, e.g. 'master'.""" | 875 """Returns the short branch name, e.g. 'master'.""" |
945 if not self.branch: | 876 if not self.branch: |
946 branchref = GetCurrentBranchRef() | 877 branchref = RunGit(['symbolic-ref', 'HEAD'], |
| 878 stderr=subprocess2.VOID, error_ok=True).strip() |
947 if not branchref: | 879 if not branchref: |
948 return None | 880 return None |
949 self.branchref = branchref | 881 self.branchref = branchref |
950 self.branch = ShortBranchName(self.branchref) | 882 self.branch = ShortBranchName(self.branchref) |
951 return self.branch | 883 return self.branch |
952 | 884 |
953 def GetBranchRef(self): | 885 def GetBranchRef(self): |
954 """Returns the full branch name, e.g. 'refs/heads/master'.""" | 886 """Returns the full branch name, e.g. 'refs/heads/master'.""" |
955 self.GetBranch() # Poke the lazy loader. | 887 self.GetBranch() # Poke the lazy loader. |
956 return self.branchref | 888 return self.branchref |
(...skipping 23 matching lines...) Expand all Loading... |
980 if 'origin/master' in remote_branches: | 912 if 'origin/master' in remote_branches: |
981 # Fall back on origin/master if it exits. | 913 # Fall back on origin/master if it exits. |
982 remote = 'origin' | 914 remote = 'origin' |
983 upstream_branch = 'refs/heads/master' | 915 upstream_branch = 'refs/heads/master' |
984 elif 'origin/trunk' in remote_branches: | 916 elif 'origin/trunk' in remote_branches: |
985 # Fall back on origin/trunk if it exists. Generally a shared | 917 # Fall back on origin/trunk if it exists. Generally a shared |
986 # git-svn clone | 918 # git-svn clone |
987 remote = 'origin' | 919 remote = 'origin' |
988 upstream_branch = 'refs/heads/trunk' | 920 upstream_branch = 'refs/heads/trunk' |
989 else: | 921 else: |
990 DieWithError( | 922 DieWithError("""Unable to determine default branch to diff against. |
991 'Unable to determine default branch to diff against.\n' | 923 Either pass complete "git diff"-style arguments, like |
992 'Either pass complete "git diff"-style arguments, like\n' | 924 git cl upload origin/master |
993 ' git cl upload origin/master\n' | 925 or verify this branch is set up to track another (via the --track argument to |
994 'or verify this branch is set up to track another \n' | 926 "git checkout -b ...").""") |
995 '(via the --track argument to "git checkout -b ...").') | |
996 | 927 |
997 return remote, upstream_branch | 928 return remote, upstream_branch |
998 | 929 |
999 def GetCommonAncestorWithUpstream(self): | 930 def GetCommonAncestorWithUpstream(self): |
1000 upstream_branch = self.GetUpstreamBranch() | 931 upstream_branch = self.GetUpstreamBranch() |
1001 if not BranchExists(upstream_branch): | 932 if not BranchExists(upstream_branch): |
1002 DieWithError('The upstream for the current branch (%s) does not exist ' | 933 DieWithError('The upstream for the current branch (%s) does not exist ' |
1003 'anymore.\nPlease fix it and try again.' % self.GetBranch()) | 934 'anymore.\nPlease fix it and try again.' % self.GetBranch()) |
1004 return git_common.get_or_create_merge_base(self.GetBranch(), | 935 return git_common.get_or_create_merge_base(self.GetBranch(), |
1005 upstream_branch) | 936 upstream_branch) |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1124 remote, _ = self.GetRemoteBranch() | 1055 remote, _ = self.GetRemoteBranch() |
1125 url = RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip() | 1056 url = RunGit(['config', 'remote.%s.url' % remote], error_ok=True).strip() |
1126 | 1057 |
1127 # If URL is pointing to a local directory, it is probably a git cache. | 1058 # If URL is pointing to a local directory, it is probably a git cache. |
1128 if os.path.isdir(url): | 1059 if os.path.isdir(url): |
1129 url = RunGit(['config', 'remote.%s.url' % remote], | 1060 url = RunGit(['config', 'remote.%s.url' % remote], |
1130 error_ok=True, | 1061 error_ok=True, |
1131 cwd=url).strip() | 1062 cwd=url).strip() |
1132 return url | 1063 return url |
1133 | 1064 |
1134 def GetIssue(self, force_lookup=False): | 1065 def GetIssue(self): |
1135 """Returns the issue number as a int or None if not set.""" | 1066 """Returns the issue number as a int or None if not set.""" |
1136 if force_lookup or (self.issue is None and not self.lookedup_issue): | 1067 if self.issue is None and not self.lookedup_issue: |
1137 issue = RunGit(['config', self._codereview_impl.IssueSetting()], | 1068 issue = RunGit(['config', self._IssueSetting()], error_ok=True).strip() |
1138 error_ok=True).strip() | |
1139 self.issue = int(issue) or None if issue else None | 1069 self.issue = int(issue) or None if issue else None |
1140 self.lookedup_issue = True | 1070 self.lookedup_issue = True |
1141 return self.issue | 1071 return self.issue |
1142 | 1072 |
| 1073 def GetRietveldServer(self): |
| 1074 if not self.rietveld_server: |
| 1075 # If we're on a branch then get the server potentially associated |
| 1076 # with that branch. |
| 1077 if self.GetIssue(): |
| 1078 rietveld_server_config = self._RietveldServer() |
| 1079 if rietveld_server_config: |
| 1080 self.rietveld_server = gclient_utils.UpgradeToHttps(RunGit( |
| 1081 ['config', rietveld_server_config], error_ok=True).strip()) |
| 1082 if not self.rietveld_server: |
| 1083 self.rietveld_server = settings.GetDefaultServerUrl() |
| 1084 return self.rietveld_server |
| 1085 |
| 1086 def GetGerritServer(self): |
| 1087 # We don't support multiple Gerrit servers, and assume it to be same as |
| 1088 # origin, except with a '-review' suffix for first subdomain. |
| 1089 parts = urlparse.urlparse(self.GetRemoteUrl()).netloc.split('.') |
| 1090 parts[0] = parts[0] + '-review' |
| 1091 return 'https://%s' % '.'.join(parts) |
| 1092 |
1143 def GetIssueURL(self): | 1093 def GetIssueURL(self): |
1144 """Get the URL for a particular issue.""" | 1094 """Get the URL for a particular issue.""" |
1145 issue = self.GetIssue() | 1095 if not self.GetIssue(): |
1146 if not issue: | |
1147 return None | 1096 return None |
1148 return '%s/%s' % (self._codereview_impl.GetCodereviewServer(), issue) | 1097 if settings.GetIsGerrit(): |
| 1098 return '%s/%s' % (self.GetGerritServer(), self.GetIssue()) |
| 1099 return '%s/%s' % (self.GetRietveldServer(), self.GetIssue()) |
1149 | 1100 |
1150 def GetDescription(self, pretty=False): | 1101 def GetDescription(self, pretty=False): |
1151 if not self.has_description: | 1102 if not self.has_description: |
1152 if self.GetIssue(): | 1103 if self.GetIssue(): |
1153 self.description = self._codereview_impl.FetchDescription() | 1104 issue = self.GetIssue() |
| 1105 try: |
| 1106 self.description = self.RpcServer().get_description(issue).strip() |
| 1107 except urllib2.HTTPError as e: |
| 1108 if e.code == 404: |
| 1109 DieWithError( |
| 1110 ('\nWhile fetching the description for issue %d, received a ' |
| 1111 '404 (not found)\n' |
| 1112 'error. It is likely that you deleted this ' |
| 1113 'issue on the server. If this is the\n' |
| 1114 'case, please run\n\n' |
| 1115 ' git cl issue 0\n\n' |
| 1116 'to clear the association with the deleted issue. Then run ' |
| 1117 'this command again.') % issue) |
| 1118 else: |
| 1119 DieWithError( |
| 1120 '\nFailed to fetch issue description. HTTP error %d' % e.code) |
| 1121 except urllib2.URLError as e: |
| 1122 print >> sys.stderr, ( |
| 1123 'Warning: Failed to retrieve CL description due to network ' |
| 1124 'failure.') |
| 1125 self.description = '' |
| 1126 |
1154 self.has_description = True | 1127 self.has_description = True |
1155 if pretty: | 1128 if pretty: |
1156 wrapper = textwrap.TextWrapper() | 1129 wrapper = textwrap.TextWrapper() |
1157 wrapper.initial_indent = wrapper.subsequent_indent = ' ' | 1130 wrapper.initial_indent = wrapper.subsequent_indent = ' ' |
1158 return wrapper.fill(self.description) | 1131 return wrapper.fill(self.description) |
1159 return self.description | 1132 return self.description |
1160 | 1133 |
1161 def GetPatchset(self): | 1134 def GetPatchset(self): |
1162 """Returns the patchset number as a int or None if not set.""" | 1135 """Returns the patchset number as a int or None if not set.""" |
1163 if self.patchset is None and not self.lookedup_patchset: | 1136 if self.patchset is None and not self.lookedup_patchset: |
1164 patchset = RunGit(['config', self._codereview_impl.PatchsetSetting()], | 1137 patchset = RunGit(['config', self._PatchsetSetting()], |
1165 error_ok=True).strip() | 1138 error_ok=True).strip() |
1166 self.patchset = int(patchset) or None if patchset else None | 1139 self.patchset = int(patchset) or None if patchset else None |
1167 self.lookedup_patchset = True | 1140 self.lookedup_patchset = True |
1168 return self.patchset | 1141 return self.patchset |
1169 | 1142 |
1170 def SetPatchset(self, patchset): | 1143 def SetPatchset(self, patchset): |
1171 """Set this branch's patchset. If patchset=0, clears the patchset.""" | 1144 """Set this branch's patchset. If patchset=0, clears the patchset.""" |
1172 patchset_setting = self._codereview_impl.PatchsetSetting() | |
1173 if patchset: | 1145 if patchset: |
1174 RunGit(['config', patchset_setting, str(patchset)]) | 1146 RunGit(['config', self._PatchsetSetting(), str(patchset)]) |
1175 self.patchset = patchset | 1147 self.patchset = patchset |
1176 else: | 1148 else: |
1177 RunGit(['config', '--unset', patchset_setting], | 1149 RunGit(['config', '--unset', self._PatchsetSetting()], |
1178 stderr=subprocess2.PIPE, error_ok=True) | 1150 stderr=subprocess2.PIPE, error_ok=True) |
1179 self.patchset = None | 1151 self.patchset = None |
1180 | 1152 |
| 1153 def GetMostRecentPatchset(self): |
| 1154 return self.GetIssueProperties()['patchsets'][-1] |
| 1155 |
| 1156 def GetPatchSetDiff(self, issue, patchset): |
| 1157 return self.RpcServer().get( |
| 1158 '/download/issue%s_%s.diff' % (issue, patchset)) |
| 1159 |
| 1160 def GetIssueProperties(self): |
| 1161 if self._props is None: |
| 1162 issue = self.GetIssue() |
| 1163 if not issue: |
| 1164 self._props = {} |
| 1165 else: |
| 1166 self._props = self.RpcServer().get_issue_properties(issue, True) |
| 1167 return self._props |
| 1168 |
| 1169 def GetApprovingReviewers(self): |
| 1170 return get_approving_reviewers(self.GetIssueProperties()) |
| 1171 |
| 1172 def AddComment(self, message): |
| 1173 return self.RpcServer().add_comment(self.GetIssue(), message) |
| 1174 |
1181 def SetIssue(self, issue=None): | 1175 def SetIssue(self, issue=None): |
1182 """Set this branch's issue. If issue isn't given, clears the issue.""" | 1176 """Set this branch's issue. If issue isn't given, clears the issue.""" |
1183 issue_setting = self._codereview_impl.IssueSetting() | |
1184 codereview_setting = self._codereview_impl.GetCodereviewServerSetting() | |
1185 if issue: | 1177 if issue: |
1186 self.issue = issue | 1178 self.issue = issue |
1187 RunGit(['config', issue_setting, str(issue)]) | 1179 RunGit(['config', self._IssueSetting(), str(issue)]) |
1188 codereview_server = self._codereview_impl.GetCodereviewServer() | 1180 if not settings.GetIsGerrit() and self.rietveld_server: |
1189 if codereview_server: | 1181 RunGit(['config', self._RietveldServer(), self.rietveld_server]) |
1190 RunGit(['config', codereview_setting, codereview_server]) | |
1191 else: | 1182 else: |
1192 current_issue = self.GetIssue() | 1183 current_issue = self.GetIssue() |
1193 if current_issue: | 1184 if current_issue: |
1194 RunGit(['config', '--unset', issue_setting]) | 1185 RunGit(['config', '--unset', self._IssueSetting()]) |
1195 self.issue = None | 1186 self.issue = None |
1196 self.SetPatchset(None) | 1187 self.SetPatchset(None) |
1197 | 1188 |
1198 def GetChange(self, upstream_branch, author): | 1189 def GetChange(self, upstream_branch, author): |
1199 if not self.GitSanityChecks(upstream_branch): | 1190 if not self.GitSanityChecks(upstream_branch): |
1200 DieWithError('\nGit sanity check failure') | 1191 DieWithError('\nGit sanity check failure') |
1201 | 1192 |
1202 root = settings.GetRelativeRoot() | 1193 root = settings.GetRelativeRoot() |
1203 if not root: | 1194 if not root: |
1204 root = '.' | 1195 root = '.' |
(...skipping 29 matching lines...) Expand all Loading... |
1234 return presubmit_support.GitChange( | 1225 return presubmit_support.GitChange( |
1235 name, | 1226 name, |
1236 description, | 1227 description, |
1237 absroot, | 1228 absroot, |
1238 files, | 1229 files, |
1239 issue, | 1230 issue, |
1240 patchset, | 1231 patchset, |
1241 author, | 1232 author, |
1242 upstream=upstream_branch) | 1233 upstream=upstream_branch) |
1243 | 1234 |
1244 def UpdateDescription(self, description): | |
1245 self.description = description | |
1246 return self._codereview_impl.UpdateDescriptionRemote(description) | |
1247 | |
1248 def RunHook(self, committing, may_prompt, verbose, change): | |
1249 """Calls sys.exit() if the hook fails; returns a HookResults otherwise.""" | |
1250 try: | |
1251 return presubmit_support.DoPresubmitChecks(change, committing, | |
1252 verbose=verbose, output_stream=sys.stdout, input_stream=sys.stdin, | |
1253 default_presubmit=None, may_prompt=may_prompt, | |
1254 rietveld_obj=self._codereview_impl.GetRieveldObjForPresubmit()) | |
1255 except presubmit_support.PresubmitFailure, e: | |
1256 DieWithError( | |
1257 ('%s\nMaybe your depot_tools is out of date?\n' | |
1258 'If all fails, contact maruel@') % e) | |
1259 | |
1260 # Forward methods to codereview specific implementation. | |
1261 | |
1262 def CloseIssue(self): | |
1263 return self._codereview_impl.CloseIssue() | |
1264 | |
1265 def GetStatus(self): | |
1266 return self._codereview_impl.GetStatus() | |
1267 | |
1268 def GetCodereviewServer(self): | |
1269 return self._codereview_impl.GetCodereviewServer() | |
1270 | |
1271 def GetApprovingReviewers(self): | |
1272 return self._codereview_impl.GetApprovingReviewers() | |
1273 | |
1274 def GetMostRecentPatchset(self): | |
1275 return self._codereview_impl.GetMostRecentPatchset() | |
1276 | |
1277 def __getattr__(self, attr): | |
1278 # This is because lots of untested code accesses Rietveld-specific stuff | |
1279 # directly, and it's hard to fix for sure. So, just let it work, and fix | |
1280 # on a cases by case basis. | |
1281 return getattr(self._codereview_impl, attr) | |
1282 | |
1283 | |
1284 class _ChangelistCodereviewBase(object): | |
1285 """Abstract base class encapsulating codereview specifics of a changelist.""" | |
1286 def __init__(self, changelist): | |
1287 self._changelist = changelist # instance of Changelist | |
1288 | |
1289 def __getattr__(self, attr): | |
1290 # Forward methods to changelist. | |
1291 # TODO(tandrii): maybe clean up _GerritChangelistImpl and | |
1292 # _RietveldChangelistImpl to avoid this hack? | |
1293 return getattr(self._changelist, attr) | |
1294 | |
1295 def GetStatus(self): | 1235 def GetStatus(self): |
1296 """Apply a rough heuristic to give a simple summary of an issue's review | 1236 """Apply a rough heuristic to give a simple summary of an issue's review |
1297 or CQ status, assuming adherence to a common workflow. | 1237 or CQ status, assuming adherence to a common workflow. |
1298 | |
1299 Returns None if no issue for this branch, or specific string keywords. | |
1300 """ | |
1301 raise NotImplementedError() | |
1302 | |
1303 def GetCodereviewServer(self): | |
1304 """Returns server URL without end slash, like "https://codereview.com".""" | |
1305 raise NotImplementedError() | |
1306 | |
1307 def FetchDescription(self): | |
1308 """Fetches and returns description from the codereview server.""" | |
1309 raise NotImplementedError() | |
1310 | |
1311 def GetCodereviewServerSetting(self): | |
1312 """Returns git config setting for the codereview server.""" | |
1313 raise NotImplementedError() | |
1314 | |
1315 def IssueSetting(self): | |
1316 """Returns name of git config setting which stores issue number.""" | |
1317 raise NotImplementedError() | |
1318 | |
1319 def PatchsetSetting(self): | |
1320 """Returns name of git config setting which stores issue number.""" | |
1321 raise NotImplementedError() | |
1322 | |
1323 def GetRieveldObjForPresubmit(self): | |
1324 # This is an unfortunate Rietveld-embeddedness in presubmit. | |
1325 # For non-Rietveld codereviews, this probably should return a dummy object. | |
1326 raise NotImplementedError() | |
1327 | |
1328 def UpdateDescriptionRemote(self, description): | |
1329 """Update the description on codereview site.""" | |
1330 raise NotImplementedError() | |
1331 | |
1332 def CloseIssue(self): | |
1333 """Closes the issue.""" | |
1334 raise NotImplementedError() | |
1335 | |
1336 def GetApprovingReviewers(self): | |
1337 """Returns a list of reviewers approving the change. | |
1338 | |
1339 Note: not necessarily committers. | |
1340 """ | |
1341 raise NotImplementedError() | |
1342 | |
1343 def GetMostRecentPatchset(self): | |
1344 """Returns the most recent patchset number from the codereview site.""" | |
1345 raise NotImplementedError() | |
1346 | |
1347 | |
1348 class _RietveldChangelistImpl(_ChangelistCodereviewBase): | |
1349 def __init__(self, changelist, auth_config=None, rietveld_server=None): | |
1350 super(_RietveldChangelistImpl, self).__init__(changelist) | |
1351 assert settings, 'must be initialized in _ChangelistCodereviewBase' | |
1352 settings.GetDefaultServerUrl() | |
1353 | |
1354 self._rietveld_server = rietveld_server | |
1355 self._auth_config = auth_config | |
1356 self._props = None | |
1357 self._rpc_server = None | |
1358 | |
1359 def GetAuthConfig(self): | |
1360 return self._auth_config | |
1361 | |
1362 def GetCodereviewServer(self): | |
1363 if not self._rietveld_server: | |
1364 # If we're on a branch then get the server potentially associated | |
1365 # with that branch. | |
1366 if self.GetIssue(): | |
1367 rietveld_server_setting = self.GetCodereviewServerSetting() | |
1368 if rietveld_server_setting: | |
1369 self._rietveld_server = gclient_utils.UpgradeToHttps(RunGit( | |
1370 ['config', rietveld_server_setting], error_ok=True).strip()) | |
1371 if not self._rietveld_server: | |
1372 self._rietveld_server = settings.GetDefaultServerUrl() | |
1373 return self._rietveld_server | |
1374 | |
1375 def FetchDescription(self): | |
1376 issue = self.GetIssue() | |
1377 assert issue | |
1378 try: | |
1379 return self.RpcServer().get_description(issue).strip() | |
1380 except urllib2.HTTPError as e: | |
1381 if e.code == 404: | |
1382 DieWithError( | |
1383 ('\nWhile fetching the description for issue %d, received a ' | |
1384 '404 (not found)\n' | |
1385 'error. It is likely that you deleted this ' | |
1386 'issue on the server. If this is the\n' | |
1387 'case, please run\n\n' | |
1388 ' git cl issue 0\n\n' | |
1389 'to clear the association with the deleted issue. Then run ' | |
1390 'this command again.') % issue) | |
1391 else: | |
1392 DieWithError( | |
1393 '\nFailed to fetch issue description. HTTP error %d' % e.code) | |
1394 except urllib2.URLError as e: | |
1395 print >> sys.stderr, ( | |
1396 'Warning: Failed to retrieve CL description due to network ' | |
1397 'failure.') | |
1398 return '' | |
1399 | |
1400 def GetMostRecentPatchset(self): | |
1401 return self.GetIssueProperties()['patchsets'][-1] | |
1402 | |
1403 def GetPatchSetDiff(self, issue, patchset): | |
1404 return self.RpcServer().get( | |
1405 '/download/issue%s_%s.diff' % (issue, patchset)) | |
1406 | |
1407 def GetIssueProperties(self): | |
1408 if self._props is None: | |
1409 issue = self.GetIssue() | |
1410 if not issue: | |
1411 self._props = {} | |
1412 else: | |
1413 self._props = self.RpcServer().get_issue_properties(issue, True) | |
1414 return self._props | |
1415 | |
1416 def GetApprovingReviewers(self): | |
1417 return get_approving_reviewers(self.GetIssueProperties()) | |
1418 | |
1419 def AddComment(self, message): | |
1420 return self.RpcServer().add_comment(self.GetIssue(), message) | |
1421 | |
1422 def GetStatus(self): | |
1423 """Apply a rough heuristic to give a simple summary of an issue's review | |
1424 or CQ status, assuming adherence to a common workflow. | |
1425 | 1238 |
1426 Returns None if no issue for this branch, or one of the following keywords: | 1239 Returns None if no issue for this branch, or one of the following keywords: |
1427 * 'error' - error from review tool (including deleted issues) | 1240 * 'error' - error from review tool (including deleted issues) |
1428 * 'unsent' - not sent for review | 1241 * 'unsent' - not sent for review |
1429 * 'waiting' - waiting for review | 1242 * 'waiting' - waiting for review |
1430 * 'reply' - waiting for owner to reply to review | 1243 * 'reply' - waiting for owner to reply to review |
1431 * 'lgtm' - LGTM from at least one approved reviewer | 1244 * 'lgtm' - LGTM from at least one approved reviewer |
1432 * 'commit' - in the commit queue | 1245 * 'commit' - in the commit queue |
1433 * 'closed' - closed | 1246 * 'closed' - closed |
1434 """ | 1247 """ |
(...skipping 24 matching lines...) Expand all Loading... |
1459 messages = props.get('messages') or [] | 1272 messages = props.get('messages') or [] |
1460 | 1273 |
1461 if not messages: | 1274 if not messages: |
1462 # No message was sent. | 1275 # No message was sent. |
1463 return 'unsent' | 1276 return 'unsent' |
1464 if messages[-1]['sender'] != props.get('owner_email'): | 1277 if messages[-1]['sender'] != props.get('owner_email'): |
1465 # Non-LGTM reply from non-owner | 1278 # Non-LGTM reply from non-owner |
1466 return 'reply' | 1279 return 'reply' |
1467 return 'waiting' | 1280 return 'waiting' |
1468 | 1281 |
1469 def UpdateDescriptionRemote(self, description): | 1282 def RunHook(self, committing, may_prompt, verbose, change): |
| 1283 """Calls sys.exit() if the hook fails; returns a HookResults otherwise.""" |
| 1284 |
| 1285 try: |
| 1286 return presubmit_support.DoPresubmitChecks(change, committing, |
| 1287 verbose=verbose, output_stream=sys.stdout, input_stream=sys.stdin, |
| 1288 default_presubmit=None, may_prompt=may_prompt, |
| 1289 rietveld_obj=self.RpcServer()) |
| 1290 except presubmit_support.PresubmitFailure, e: |
| 1291 DieWithError( |
| 1292 ('%s\nMaybe your depot_tools is out of date?\n' |
| 1293 'If all fails, contact maruel@') % e) |
| 1294 |
| 1295 def UpdateDescription(self, description): |
| 1296 self.description = description |
1470 return self.RpcServer().update_description( | 1297 return self.RpcServer().update_description( |
1471 self.GetIssue(), self.description) | 1298 self.GetIssue(), self.description) |
1472 | 1299 |
1473 def CloseIssue(self): | 1300 def CloseIssue(self): |
| 1301 """Updates the description and closes the issue.""" |
1474 return self.RpcServer().close_issue(self.GetIssue()) | 1302 return self.RpcServer().close_issue(self.GetIssue()) |
1475 | 1303 |
1476 def SetFlag(self, flag, value): | 1304 def SetFlag(self, flag, value): |
1477 """Patchset must match.""" | 1305 """Patchset must match.""" |
1478 if not self.GetPatchset(): | 1306 if not self.GetPatchset(): |
1479 DieWithError('The patchset needs to match. Send another patchset.') | 1307 DieWithError('The patchset needs to match. Send another patchset.') |
1480 try: | 1308 try: |
1481 return self.RpcServer().set_flag( | 1309 return self.RpcServer().set_flag( |
1482 self.GetIssue(), self.GetPatchset(), flag, value) | 1310 self.GetIssue(), self.GetPatchset(), flag, value) |
1483 except urllib2.HTTPError, e: | 1311 except urllib2.HTTPError, e: |
1484 if e.code == 404: | 1312 if e.code == 404: |
1485 DieWithError('The issue %s doesn\'t exist.' % self.GetIssue()) | 1313 DieWithError('The issue %s doesn\'t exist.' % self.GetIssue()) |
1486 if e.code == 403: | 1314 if e.code == 403: |
1487 DieWithError( | 1315 DieWithError( |
1488 ('Access denied to issue %s. Maybe the patchset %s doesn\'t ' | 1316 ('Access denied to issue %s. Maybe the patchset %s doesn\'t ' |
1489 'match?') % (self.GetIssue(), self.GetPatchset())) | 1317 'match?') % (self.GetIssue(), self.GetPatchset())) |
1490 raise | 1318 raise |
1491 | 1319 |
1492 def RpcServer(self): | 1320 def RpcServer(self): |
1493 """Returns an upload.RpcServer() to access this review's rietveld instance. | 1321 """Returns an upload.RpcServer() to access this review's rietveld instance. |
1494 """ | 1322 """ |
1495 if not self._rpc_server: | 1323 if not self._rpc_server: |
1496 self._rpc_server = rietveld.CachingRietveld( | 1324 self._rpc_server = rietveld.CachingRietveld( |
1497 self.GetCodereviewServer(), | 1325 self.GetRietveldServer(), |
1498 self._auth_config or auth.make_auth_config()) | 1326 self._auth_config or auth.make_auth_config()) |
1499 return self._rpc_server | 1327 return self._rpc_server |
1500 | 1328 |
1501 def IssueSetting(self): | 1329 def _IssueSetting(self): |
1502 """Return the git setting that stores this change's issue.""" | 1330 """Return the git setting that stores this change's issue.""" |
1503 return 'branch.%s.rietveldissue' % self.GetBranch() | 1331 return 'branch.%s.rietveldissue' % self.GetBranch() |
1504 | 1332 |
1505 def PatchsetSetting(self): | 1333 def _PatchsetSetting(self): |
1506 """Return the git setting that stores this change's most recent patchset.""" | 1334 """Return the git setting that stores this change's most recent patchset.""" |
1507 return 'branch.%s.rietveldpatchset' % self.GetBranch() | 1335 return 'branch.%s.rietveldpatchset' % self.GetBranch() |
1508 | 1336 |
1509 def GetCodereviewServerSetting(self): | 1337 def _RietveldServer(self): |
1510 """Returns the git setting that stores this change's rietveld server.""" | 1338 """Returns the git setting that stores this change's rietveld server.""" |
1511 branch = self.GetBranch() | 1339 branch = self.GetBranch() |
1512 if branch: | 1340 if branch: |
1513 return 'branch.%s.rietveldserver' % branch | 1341 return 'branch.%s.rietveldserver' % branch |
1514 return None | 1342 return None |
1515 | 1343 |
1516 def GetRieveldObjForPresubmit(self): | |
1517 return self.RpcServer() | |
1518 | |
1519 | |
1520 class _GerritChangelistImpl(_ChangelistCodereviewBase): | |
1521 def __init__(self, changelist, auth_config=None): | |
1522 # auth_config is Rietveld thing, kept here to preserve interface only. | |
1523 super(_GerritChangelistImpl, self).__init__(changelist) | |
1524 self._change_id = None | |
1525 self._gerrit_server = None # e.g. https://chromium-review.googlesource.com | |
1526 self._gerrit_host = None # e.g. chromium-review.googlesource.com | |
1527 | |
1528 def _GetGerritHost(self): | |
1529 # Lazy load of configs. | |
1530 self.GetCodereviewServer() | |
1531 return self._gerrit_host | |
1532 | |
1533 def GetCodereviewServer(self): | |
1534 if not self._gerrit_server: | |
1535 # If we're on a branch then get the server potentially associated | |
1536 # with that branch. | |
1537 if self.GetIssue(): | |
1538 gerrit_server_setting = self.GetCodereviewServerSetting() | |
1539 if gerrit_server_setting: | |
1540 self._gerrit_server = RunGit(['config', gerrit_server_setting], | |
1541 error_ok=True).strip() | |
1542 if self._gerrit_server: | |
1543 self._gerrit_host = urlparse.urlparse(self._gerrit_server).netloc | |
1544 if not self._gerrit_server: | |
1545 # We assume repo to be hosted on Gerrit, and hence Gerrit server | |
1546 # has "-review" suffix for lowest level subdomain. | |
1547 parts = urlparse.urlparse(self.GetRemoteUrl()).netloc.split('.') | |
1548 parts[0] = parts[0] + '-review' | |
1549 self._gerrit_host = '.'.join(parts) | |
1550 self._gerrit_server = 'https://%s' % self._gerrit_host | |
1551 return self._gerrit_server | |
1552 | |
1553 def IssueSetting(self): | |
1554 """Return the git setting that stores this change's issue.""" | |
1555 return 'branch.%s.gerritissue' % self.GetBranch() | |
1556 | |
1557 def PatchsetSetting(self): | |
1558 """Return the git setting that stores this change's most recent patchset.""" | |
1559 return 'branch.%s.gerritpatchset' % self.GetBranch() | |
1560 | |
1561 def GetCodereviewServerSetting(self): | |
1562 """Returns the git setting that stores this change's Gerrit server.""" | |
1563 branch = self.GetBranch() | |
1564 if branch: | |
1565 return 'branch.%s.gerritserver' % branch | |
1566 return None | |
1567 | |
1568 def GetRieveldObjForPresubmit(self): | |
1569 class ThisIsNotRietveldIssue(object): | |
1570 def __getattr__(self, attr): | |
1571 print( | |
1572 'You aren\'t using Rietveld at the moment, but Gerrit.\n' | |
1573 'Using Rietveld in your PRESUBMIT scripts won\'t work.\n' | |
1574 'Please, either change your PRESUBIT to not use rietveld_obj.%s,\n' | |
1575 'or use Rietveld for codereview.\n' % attr) | |
1576 raise NotImplementedError() | |
1577 return ThisIsNotRietveldIssue() | |
1578 | |
1579 def GetStatus(self): | |
1580 # TODO(tandrii) | |
1581 raise NotImplementedError() | |
1582 | |
1583 def GetMostRecentPatchset(self): | |
1584 data = gerrit_util.GetChangeDetail(self._GetGerritHost(), self.GetIssue(), | |
1585 ['CURRENT_REVISION']) | |
1586 return data['revisions'][data['current_revision']]['_number'] | |
1587 | |
1588 def FetchDescription(self): | |
1589 data = gerrit_util.GetChangeDetail(self._GetGerritHost(), self.GetIssue(), | |
1590 ['COMMIT_FOOTERS', 'CURRENT_REVISION']) | |
1591 return data['revisions'][data['current_revision']]['commit_with_footers'] | |
1592 | |
1593 def UpdateDescriptionRemote(self, description): | |
1594 # TODO(tandrii) | |
1595 raise NotImplementedError() | |
1596 | |
1597 def CloseIssue(self): | |
1598 # TODO(tandrii) | |
1599 raise NotImplementedError() | |
1600 | |
1601 | 1344 |
1602 class ChangeDescription(object): | 1345 class ChangeDescription(object): |
1603 """Contains a parsed form of the change description.""" | 1346 """Contains a parsed form of the change description.""" |
1604 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$' | 1347 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$' |
1605 BUG_LINE = r'^[ \t]*(BUG)[ \t]*=[ \t]*(.*?)[ \t]*$' | 1348 BUG_LINE = r'^[ \t]*(BUG)[ \t]*=[ \t]*(.*?)[ \t]*$' |
1606 | 1349 |
1607 def __init__(self, description): | 1350 def __init__(self, description): |
1608 self._description_lines = (description or '').strip().splitlines() | 1351 self._description_lines = (description or '').strip().splitlines() |
1609 | 1352 |
1610 @property # www.logilab.org/ticket/89786 | 1353 @property # www.logilab.org/ticket/89786 |
(...skipping 525 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2136 return 0 | 1879 return 0 |
2137 | 1880 |
2138 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads']) | 1881 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads']) |
2139 if not branches: | 1882 if not branches: |
2140 print('No local branch found.') | 1883 print('No local branch found.') |
2141 return 0 | 1884 return 0 |
2142 | 1885 |
2143 changes = ( | 1886 changes = ( |
2144 Changelist(branchref=b, auth_config=auth_config) | 1887 Changelist(branchref=b, auth_config=auth_config) |
2145 for b in branches.splitlines()) | 1888 for b in branches.splitlines()) |
2146 # TODO(tandrii): refactor to use CLs list instead of branches list. | |
2147 branches = [c.GetBranch() for c in changes] | 1889 branches = [c.GetBranch() for c in changes] |
2148 alignment = max(5, max(len(b) for b in branches)) | 1890 alignment = max(5, max(len(b) for b in branches)) |
2149 print 'Branches associated with reviews:' | 1891 print 'Branches associated with reviews:' |
2150 output = get_cl_statuses(branches, | 1892 output = get_cl_statuses(branches, |
2151 fine_grained=not options.fast, | 1893 fine_grained=not options.fast, |
2152 max_processes=options.maxjobs, | 1894 max_processes=options.maxjobs, |
2153 auth_config=auth_config) | 1895 auth_config=auth_config) |
2154 | 1896 |
2155 branch_statuses = {} | 1897 branch_statuses = {} |
2156 alignment = max(5, max(len(ShortBranchName(b)) for b in branches)) | 1898 alignment = max(5, max(len(ShortBranchName(b)) for b in branches)) |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2252 options, args = parser.parse_args(args) | 1994 options, args = parser.parse_args(args) |
2253 auth_config = auth.extract_auth_config_from_options(options) | 1995 auth_config = auth.extract_auth_config_from_options(options) |
2254 | 1996 |
2255 issue = None | 1997 issue = None |
2256 if options.issue: | 1998 if options.issue: |
2257 try: | 1999 try: |
2258 issue = int(options.issue) | 2000 issue = int(options.issue) |
2259 except ValueError: | 2001 except ValueError: |
2260 DieWithError('A review issue id is expected to be a number') | 2002 DieWithError('A review issue id is expected to be a number') |
2261 | 2003 |
2262 cl = Changelist(issue=issue, codereview='rietveld', auth_config=auth_config) | 2004 cl = Changelist(issue=issue, auth_config=auth_config) |
2263 | 2005 |
2264 if options.comment: | 2006 if options.comment: |
2265 cl.AddComment(options.comment) | 2007 cl.AddComment(options.comment) |
2266 return 0 | 2008 return 0 |
2267 | 2009 |
2268 data = cl.GetIssueProperties() | 2010 data = cl.GetIssueProperties() |
2269 summary = [] | 2011 summary = [] |
2270 for message in sorted(data.get('messages', []), key=lambda x: x['date']): | 2012 for message in sorted(data.get('messages', []), key=lambda x: x['date']): |
2271 summary.append({ | 2013 summary.append({ |
2272 'date': message['date'], | 2014 'date': message['date'], |
(...skipping 358 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2631 remote_branch = remote_branch.replace('refs/remotes/', 'refs/') | 2373 remote_branch = remote_branch.replace('refs/remotes/', 'refs/') |
2632 # If a pending prefix exists then replace refs/ with it. | 2374 # If a pending prefix exists then replace refs/ with it. |
2633 if pending_prefix: | 2375 if pending_prefix: |
2634 remote_branch = remote_branch.replace('refs/', pending_prefix) | 2376 remote_branch = remote_branch.replace('refs/', pending_prefix) |
2635 return remote_branch | 2377 return remote_branch |
2636 | 2378 |
2637 | 2379 |
2638 def RietveldUpload(options, args, cl, change): | 2380 def RietveldUpload(options, args, cl, change): |
2639 """upload the patch to rietveld.""" | 2381 """upload the patch to rietveld.""" |
2640 upload_args = ['--assume_yes'] # Don't ask about untracked files. | 2382 upload_args = ['--assume_yes'] # Don't ask about untracked files. |
2641 upload_args.extend(['--server', cl.GetCodereviewServer()]) | 2383 upload_args.extend(['--server', cl.GetRietveldServer()]) |
2642 # TODO(tandrii): refactor this ugliness into _RietveldChangelistImpl. | 2384 upload_args.extend(auth.auth_config_to_command_options(cl.auth_config)) |
2643 upload_args.extend(auth.auth_config_to_command_options( | |
2644 cl._codereview_impl.GetAuthConfig())) | |
2645 if options.emulate_svn_auto_props: | 2385 if options.emulate_svn_auto_props: |
2646 upload_args.append('--emulate_svn_auto_props') | 2386 upload_args.append('--emulate_svn_auto_props') |
2647 | 2387 |
2648 change_desc = None | 2388 change_desc = None |
2649 | 2389 |
2650 if options.email is not None: | 2390 if options.email is not None: |
2651 upload_args.extend(['--email', options.email]) | 2391 upload_args.extend(['--email', options.email]) |
2652 | 2392 |
2653 if cl.GetIssue(): | 2393 if cl.GetIssue(): |
2654 if options.title: | 2394 if options.title: |
(...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2877 | 2617 |
2878 # Default to diffing against common ancestor of upstream branch | 2618 # Default to diffing against common ancestor of upstream branch |
2879 base_branch = cl.GetCommonAncestorWithUpstream() | 2619 base_branch = cl.GetCommonAncestorWithUpstream() |
2880 args = [base_branch, 'HEAD'] | 2620 args = [base_branch, 'HEAD'] |
2881 | 2621 |
2882 # Make sure authenticated to Rietveld before running expensive hooks. It is | 2622 # Make sure authenticated to Rietveld before running expensive hooks. It is |
2883 # a fast, best efforts check. Rietveld still can reject the authentication | 2623 # a fast, best efforts check. Rietveld still can reject the authentication |
2884 # during the actual upload. | 2624 # during the actual upload. |
2885 if not settings.GetIsGerrit() and auth_config.use_oauth2: | 2625 if not settings.GetIsGerrit() and auth_config.use_oauth2: |
2886 authenticator = auth.get_authenticator_for_host( | 2626 authenticator = auth.get_authenticator_for_host( |
2887 cl.GetCodereviewServer(), auth_config) | 2627 cl.GetRietveldServer(), auth_config) |
2888 if not authenticator.has_cached_credentials(): | 2628 if not authenticator.has_cached_credentials(): |
2889 raise auth.LoginRequiredError(cl.GetCodereviewServer()) | 2629 raise auth.LoginRequiredError(cl.GetRietveldServer()) |
2890 | 2630 |
2891 # Apply watchlists on upload. | 2631 # Apply watchlists on upload. |
2892 change = cl.GetChange(base_branch, None) | 2632 change = cl.GetChange(base_branch, None) |
2893 watchlist = watchlists.Watchlists(change.RepositoryRoot()) | 2633 watchlist = watchlists.Watchlists(change.RepositoryRoot()) |
2894 files = [f.LocalPath() for f in change.AffectedFiles()] | 2634 files = [f.LocalPath() for f in change.AffectedFiles()] |
2895 if not options.bypass_watchlists: | 2635 if not options.bypass_watchlists: |
2896 cl.SetWatchers(watchlist.GetWatchersForPaths(files)) | 2636 cl.SetWatchers(watchlist.GetWatchersForPaths(files)) |
2897 | 2637 |
2898 if not options.bypass_hooks: | 2638 if not options.bypass_hooks: |
2899 if options.reviewers or options.tbr_owners: | 2639 if options.reviewers or options.tbr_owners: |
(...skipping 573 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3473 return PatchIssue(issue_arg, options.reject, options.nocommit, | 3213 return PatchIssue(issue_arg, options.reject, options.nocommit, |
3474 options.directory, auth_config) | 3214 options.directory, auth_config) |
3475 | 3215 |
3476 | 3216 |
3477 def PatchIssue(issue_arg, reject, nocommit, directory, auth_config): | 3217 def PatchIssue(issue_arg, reject, nocommit, directory, auth_config): |
3478 # PatchIssue should never be called with a dirty tree. It is up to the | 3218 # PatchIssue should never be called with a dirty tree. It is up to the |
3479 # caller to check this, but just in case we assert here since the | 3219 # caller to check this, but just in case we assert here since the |
3480 # consequences of the caller not checking this could be dire. | 3220 # consequences of the caller not checking this could be dire. |
3481 assert(not git_common.is_dirty_git_tree('apply')) | 3221 assert(not git_common.is_dirty_git_tree('apply')) |
3482 | 3222 |
3483 # TODO(tandrii): implement for Gerrit. | |
3484 if type(issue_arg) is int or issue_arg.isdigit(): | 3223 if type(issue_arg) is int or issue_arg.isdigit(): |
3485 # Input is an issue id. Figure out the URL. | 3224 # Input is an issue id. Figure out the URL. |
3486 issue = int(issue_arg) | 3225 issue = int(issue_arg) |
3487 cl = Changelist(issue=issue, codereview='rietveld', auth_config=auth_config) | 3226 cl = Changelist(issue=issue, auth_config=auth_config) |
3488 patchset = cl.GetMostRecentPatchset() | 3227 patchset = cl.GetMostRecentPatchset() |
3489 patch_data = cl._codereview_impl.GetPatchSetDiff(issue, patchset) | 3228 patch_data = cl.GetPatchSetDiff(issue, patchset) |
3490 else: | 3229 else: |
3491 # Assume it's a URL to the patch. Default to https. | 3230 # Assume it's a URL to the patch. Default to https. |
3492 issue_url = gclient_utils.UpgradeToHttps(issue_arg) | 3231 issue_url = gclient_utils.UpgradeToHttps(issue_arg) |
3493 match = re.match(r'(.*?)/download/issue(\d+)_(\d+).diff', issue_url) | 3232 match = re.match(r'(.*?)/download/issue(\d+)_(\d+).diff', issue_url) |
3494 if not match: | 3233 if not match: |
3495 DieWithError('Must pass an issue ID or full URL for ' | 3234 DieWithError('Must pass an issue ID or full URL for ' |
3496 '\'Download raw patch set\'') | 3235 '\'Download raw patch set\'') |
3497 issue = int(match.group(2)) | 3236 issue = int(match.group(2)) |
3498 cl = Changelist(issue=issue, codereview='rietveld', | 3237 cl = Changelist(issue=issue, auth_config=auth_config) |
3499 rietvled_server=match.group(1), auth_config=auth_config) | 3238 cl.rietveld_server = match.group(1) |
3500 patchset = int(match.group(3)) | 3239 patchset = int(match.group(3)) |
3501 patch_data = urllib2.urlopen(issue_arg).read() | 3240 patch_data = urllib2.urlopen(issue_arg).read() |
3502 | 3241 |
3503 # Switch up to the top-level directory, if necessary, in preparation for | 3242 # Switch up to the top-level directory, if necessary, in preparation for |
3504 # applying the patch. | 3243 # applying the patch. |
3505 top = settings.GetRelativeRoot() | 3244 top = settings.GetRelativeRoot() |
3506 if top: | 3245 if top: |
3507 os.chdir(top) | 3246 os.chdir(top) |
3508 | 3247 |
3509 # Git patches have a/ at the beginning of source paths. We strip that out | 3248 # Git patches have a/ at the beginning of source paths. We strip that out |
(...skipping 23 matching lines...) Expand all Loading... |
3533 except subprocess2.CalledProcessError: | 3272 except subprocess2.CalledProcessError: |
3534 print 'Failed to apply the patch' | 3273 print 'Failed to apply the patch' |
3535 return 1 | 3274 return 1 |
3536 | 3275 |
3537 # If we had an issue, commit the current state and register the issue. | 3276 # If we had an issue, commit the current state and register the issue. |
3538 if not nocommit: | 3277 if not nocommit: |
3539 RunGit(['commit', '-m', (cl.GetDescription() + '\n\n' + | 3278 RunGit(['commit', '-m', (cl.GetDescription() + '\n\n' + |
3540 'patch from issue %(i)s at patchset ' | 3279 'patch from issue %(i)s at patchset ' |
3541 '%(p)s (http://crrev.com/%(i)s#ps%(p)s)' | 3280 '%(p)s (http://crrev.com/%(i)s#ps%(p)s)' |
3542 % {'i': issue, 'p': patchset})]) | 3281 % {'i': issue, 'p': patchset})]) |
3543 cl = Changelist(codereview='rietveld', auth_config=auth_config, | 3282 cl = Changelist(auth_config=auth_config) |
3544 rietveld_server=cl.GetCodereviewServer()) | |
3545 cl.SetIssue(issue) | 3283 cl.SetIssue(issue) |
3546 cl.SetPatchset(patchset) | 3284 cl.SetPatchset(patchset) |
3547 print "Committed patch locally." | 3285 print "Committed patch locally." |
3548 else: | 3286 else: |
3549 print "Patch applied to index." | 3287 print "Patch applied to index." |
3550 return 0 | 3288 return 0 |
3551 | 3289 |
3552 | 3290 |
3553 def CMDrebase(parser, args): | 3291 def CMDrebase(parser, args): |
3554 """Rebases current branch on top of svn repo.""" | 3292 """Rebases current branch on top of svn repo.""" |
(...skipping 714 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4269 if __name__ == '__main__': | 4007 if __name__ == '__main__': |
4270 # These affect sys.stdout so do it outside of main() to simplify mocks in | 4008 # These affect sys.stdout so do it outside of main() to simplify mocks in |
4271 # unit testing. | 4009 # unit testing. |
4272 fix_encoding.fix_encoding() | 4010 fix_encoding.fix_encoding() |
4273 colorama.init() | 4011 colorama.init() |
4274 try: | 4012 try: |
4275 sys.exit(main(sys.argv[1:])) | 4013 sys.exit(main(sys.argv[1:])) |
4276 except KeyboardInterrupt: | 4014 except KeyboardInterrupt: |
4277 sys.stderr.write('interrupted\n') | 4015 sys.stderr.write('interrupted\n') |
4278 sys.exit(1) | 4016 sys.exit(1) |
OLD | NEW |