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.""" | 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 |
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 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
149 | 149 |
150 def ask_for_data(prompt): | 150 def ask_for_data(prompt): |
151 try: | 151 try: |
152 return raw_input(prompt) | 152 return raw_input(prompt) |
153 except KeyboardInterrupt: | 153 except KeyboardInterrupt: |
154 # Hide the exception. | 154 # Hide the exception. |
155 sys.exit(1) | 155 sys.exit(1) |
156 | 156 |
157 | 157 |
158 def git_set_branch_value(key, value): | 158 def git_set_branch_value(key, value): |
159 branch = Changelist().GetBranch() | 159 branch = GetCurrentBranch() |
160 if not branch: | 160 if not branch: |
161 return | 161 return |
162 | 162 |
163 cmd = ['config'] | 163 cmd = ['config'] |
164 if isinstance(value, int): | 164 if isinstance(value, int): |
165 cmd.append('--int') | 165 cmd.append('--int') |
166 git_key = 'branch.%s.%s' % (branch, key) | 166 git_key = 'branch.%s.%s' % (branch, key) |
167 RunGit(cmd + [git_key, str(value)]) | 167 RunGit(cmd + [git_key, str(value)]) |
168 | 168 |
169 | 169 |
170 def git_get_branch_default(key, default): | 170 def git_get_branch_default(key, default): |
171 branch = Changelist().GetBranch() | 171 branch = GetCurrentBranch() |
172 if branch: | 172 if branch: |
173 git_key = 'branch.%s.%s' % (branch, key) | 173 git_key = 'branch.%s.%s' % (branch, key) |
174 (_, stdout) = RunGitWithCode(['config', '--int', '--get', git_key]) | 174 (_, stdout) = RunGitWithCode(['config', '--int', '--get', git_key]) |
175 try: | 175 try: |
176 return int(stdout.strip()) | 176 return int(stdout.strip()) |
177 except ValueError: | 177 except ValueError: |
178 pass | 178 pass |
179 return default | 179 return default |
180 | 180 |
181 | 181 |
(...skipping 624 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
806 def _GetBranchConfig(self, branch_name, param, **kwargs): | 806 def _GetBranchConfig(self, branch_name, param, **kwargs): |
807 return self._GetConfig('branch.' + branch_name + '.' + param, **kwargs) | 807 return self._GetConfig('branch.' + branch_name + '.' + param, **kwargs) |
808 | 808 |
809 def _GetConfig(self, param, **kwargs): | 809 def _GetConfig(self, param, **kwargs): |
810 self.LazyUpdateIfNeeded() | 810 self.LazyUpdateIfNeeded() |
811 return RunGit(['config', param], **kwargs).strip() | 811 return RunGit(['config', param], **kwargs).strip() |
812 | 812 |
813 | 813 |
814 def ShortBranchName(branch): | 814 def ShortBranchName(branch): |
815 """Convert a name like 'refs/heads/foo' to just 'foo'.""" | 815 """Convert a name like 'refs/heads/foo' to just 'foo'.""" |
816 return branch.replace('refs/heads/', '') | 816 return branch.replace('refs/heads/', '', 1) |
817 | 817 |
818 | 818 |
819 class Changelist(object): | 819 def GetCurrentBranchRef(): |
820 def __init__(self, branchref=None, issue=None, auth_config=None): | 820 """Returns branch ref (e.g., refs/heads/master) or None.""" |
821 return RunGit(['symbolic-ref', 'HEAD'], | |
Michael Achenbach
2016/03/16 09:28:19
The old implementation of ChangeList GetBranch cou
tandrii(chromium)
2016/03/21 15:06:05
Done.
| |
822 stderr=subprocess2.VOID, error_ok=True).strip() or None | |
823 | |
824 | |
825 def GetCurrentBranch(): | |
826 """Returns current branch or None. | |
827 | |
828 For refs/heads/* branches, returns just last part. For others, full ref. | |
829 """ | |
830 branchref = GetCurrentBranchRef() | |
831 if branchref: | |
832 return ShortBranchName(branchref) | |
833 return None | |
834 | |
835 | |
836 def Changelist(branchref=None, issue=None, auth_config=None): | |
Michael Achenbach
2016/03/16 09:28:19
Crazy stuff like this might break - not sure if it
tandrii(chromium)
2016/03/21 15:06:05
I'm uploading fixes to the rest of depot_tools. If
| |
837 """Returns Changelist instance for Gerrit or Rietveld automatically. | |
838 | |
839 If branch has exactly 1 associated issue, then whichever codereview the issue is from. | |
840 Otherwise, Rietveld. | |
841 """ | |
842 # TODO(tandrii): if issue is given, then probably codereview is known as well, | |
843 # so we should refactor the calling code and call appropriate implementation | |
844 # class directly. | |
Michael Achenbach
2016/03/16 09:28:19
As you write, this piece needs more refactoring in
tandrii(chromium)
2016/03/21 15:06:05
so, i haven't yet decided what signals should be u
| |
845 rietveld_cl = RietveldChangelist(branchref=branchref, issue=issue, | |
846 auth_config=auth_config) | |
847 # Check if there is associated Rietveld Issue first, | |
Michael Achenbach
2016/03/16 09:28:19
nit: s/Issue/issue
tandrii(chromium)
2016/03/21 15:06:05
Done.
| |
848 # so as to avoid checking Gerrit issue and polluting test cases. | |
849 if rietveld_cl.GetIssue(): | |
850 return rietveld_cl | |
851 assert not issue, 'if issue, then the "if" condition above must have held!' | |
852 | |
853 # GetIssue() actually calls GetBranch() which shells out git call, | |
854 # unless branchref is known beforehand. Avoid repeating for Gerrit case. | |
855 if not branchref: | |
856 branchref = rietveld_cl.branchref | |
857 | |
858 # Only load Gerrit if it has associated issue. | |
859 gerrit_cl = GerritChangelist(branchref=branchref, issue=issue) | |
860 if gerrit_cl.GetIssue(): | |
861 return gerrit_cl() | |
Michael Achenbach
2016/03/16 09:28:19
Why call?
tandrii(chromium)
2016/03/21 15:06:05
bug. I wonder, how the hell did it work in the tes
| |
862 | |
863 return rietveld_cl | |
864 | |
865 | |
866 class ChangelistBase(object): | |
867 """Generic Changelist implementation, not tied to codereview. | |
868 | |
869 Not safe for concurrent multi-{thread,process} use. | |
870 """ | |
871 | |
872 def __init__(self, branchref=None, issue=None): | |
821 # Poke settings so we get the "configure your server" message if necessary. | 873 # Poke settings so we get the "configure your server" message if necessary. |
822 global settings | 874 global settings |
823 if not settings: | 875 if not settings: |
824 # Happens when git_cl.py is used as a utility library. | 876 # Happens when git_cl.py is used as a utility library. |
825 settings = Settings() | 877 settings = Settings() |
826 settings.GetDefaultServerUrl() | |
827 self.branchref = branchref | 878 self.branchref = branchref |
828 if self.branchref: | 879 if self.branchref: |
829 self.branch = ShortBranchName(self.branchref) | 880 self.branch = ShortBranchName(self.branchref) |
830 else: | 881 else: |
831 self.branch = None | 882 self.branch = None |
832 self.rietveld_server = None | |
833 self.upstream_branch = None | 883 self.upstream_branch = None |
834 self.lookedup_issue = False | 884 self.lookedup_issue = False |
835 self.issue = issue or None | 885 self.issue = issue or None |
836 self.has_description = False | 886 self.has_description = False |
837 self.description = None | 887 self.description = None |
838 self.lookedup_patchset = False | 888 self.lookedup_patchset = False |
839 self.patchset = None | 889 self.patchset = None |
840 self.cc = None | 890 self.cc = None |
841 self.watchers = () | 891 self.watchers = () |
842 self._auth_config = auth_config | |
843 self._props = None | |
844 self._remote = None | 892 self._remote = None |
845 self._rpc_server = None | |
846 | |
847 @property | |
848 def auth_config(self): | |
849 return self._auth_config | |
850 | 893 |
851 def GetCCList(self): | 894 def GetCCList(self): |
852 """Return the users cc'd on this CL. | 895 """Return the users cc'd on this CL. |
853 | 896 |
854 Return is a string suitable for passing to gcl with the --cc flag. | 897 Return is a string suitable for passing to gcl with the --cc flag. |
855 """ | 898 """ |
856 if self.cc is None: | 899 if self.cc is None: |
857 base_cc = settings.GetDefaultCCList() | 900 base_cc = settings.GetDefaultCCList() |
858 more_cc = ','.join(self.watchers) | 901 more_cc = ','.join(self.watchers) |
859 self.cc = ','.join(filter(None, (base_cc, more_cc))) or '' | 902 self.cc = ','.join(filter(None, (base_cc, more_cc))) or '' |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
912 if 'origin/master' in remote_branches: | 955 if 'origin/master' in remote_branches: |
913 # Fall back on origin/master if it exits. | 956 # Fall back on origin/master if it exits. |
914 remote = 'origin' | 957 remote = 'origin' |
915 upstream_branch = 'refs/heads/master' | 958 upstream_branch = 'refs/heads/master' |
916 elif 'origin/trunk' in remote_branches: | 959 elif 'origin/trunk' in remote_branches: |
917 # Fall back on origin/trunk if it exists. Generally a shared | 960 # Fall back on origin/trunk if it exists. Generally a shared |
918 # git-svn clone | 961 # git-svn clone |
919 remote = 'origin' | 962 remote = 'origin' |
920 upstream_branch = 'refs/heads/trunk' | 963 upstream_branch = 'refs/heads/trunk' |
921 else: | 964 else: |
922 DieWithError("""Unable to determine default branch to diff against. | 965 DieWithError('Unable to determine default branch to diff against.\n' |
923 Either pass complete "git diff"-style arguments, like | 966 'Either pass complete "git diff"-style arguments, like\ n' |
924 git cl upload origin/master | 967 ' git cl upload origin/master\n' |
925 or verify this branch is set up to track another (via the --track argument to | 968 'or verify this branch is set up to track another \n' |
926 "git checkout -b ...").""") | 969 '(via the --track argument to "git checkout -b ...").') |
927 | 970 |
928 return remote, upstream_branch | 971 return remote, upstream_branch |
929 | 972 |
930 def GetCommonAncestorWithUpstream(self): | 973 def GetCommonAncestorWithUpstream(self): |
931 upstream_branch = self.GetUpstreamBranch() | 974 upstream_branch = self.GetUpstreamBranch() |
932 if not BranchExists(upstream_branch): | 975 if not BranchExists(upstream_branch): |
933 DieWithError('The upstream for the current branch (%s) does not exist ' | 976 DieWithError('The upstream for the current branch (%s) does not exist ' |
934 'anymore.\nPlease fix it and try again.' % self.GetBranch()) | 977 'anymore.\nPlease fix it and try again.' % self.GetBranch()) |
935 return git_common.get_or_create_merge_base(self.GetBranch(), | 978 return git_common.get_or_create_merge_base(self.GetBranch(), |
936 upstream_branch) | 979 upstream_branch) |
(...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1063 return url | 1106 return url |
1064 | 1107 |
1065 def GetIssue(self): | 1108 def GetIssue(self): |
1066 """Returns the issue number as a int or None if not set.""" | 1109 """Returns the issue number as a int or None if not set.""" |
1067 if self.issue is None and not self.lookedup_issue: | 1110 if self.issue is None and not self.lookedup_issue: |
1068 issue = RunGit(['config', self._IssueSetting()], error_ok=True).strip() | 1111 issue = RunGit(['config', self._IssueSetting()], error_ok=True).strip() |
1069 self.issue = int(issue) or None if issue else None | 1112 self.issue = int(issue) or None if issue else None |
1070 self.lookedup_issue = True | 1113 self.lookedup_issue = True |
1071 return self.issue | 1114 return self.issue |
1072 | 1115 |
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 | |
1093 def GetIssueURL(self): | 1116 def GetIssueURL(self): |
1094 """Get the URL for a particular issue.""" | 1117 """Get the URL for a particular issue.""" |
1095 if not self.GetIssue(): | 1118 issue = self.GetIssue() |
1119 if not issue: | |
1096 return None | 1120 return None |
1097 if settings.GetIsGerrit(): | 1121 return '%s/%s' % (self.GetCodereviewServer(), issue) |
1098 return '%s/%s' % (self.GetGerritServer(), self.GetIssue()) | |
1099 return '%s/%s' % (self.GetRietveldServer(), self.GetIssue()) | |
1100 | 1122 |
1101 def GetDescription(self, pretty=False): | 1123 def GetDescription(self, pretty=False): |
1102 if not self.has_description: | 1124 if not self.has_description: |
1103 if self.GetIssue(): | 1125 if self.GetIssue(): |
1104 issue = self.GetIssue() | 1126 self._FetchDescription() |
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 | |
1127 self.has_description = True | 1127 self.has_description = True |
1128 if pretty: | 1128 if pretty: |
1129 wrapper = textwrap.TextWrapper() | 1129 wrapper = textwrap.TextWrapper() |
1130 wrapper.initial_indent = wrapper.subsequent_indent = ' ' | 1130 wrapper.initial_indent = wrapper.subsequent_indent = ' ' |
1131 return wrapper.fill(self.description) | 1131 return wrapper.fill(self.description) |
1132 return self.description | 1132 return self.description |
Michael Achenbach
2016/03/16 09:28:19
Another case where the base class knows about the
tandrii(chromium)
2016/03/21 15:06:05
fixed.
| |
1133 | 1133 |
1134 def GetPatchset(self): | 1134 def GetPatchset(self): |
1135 """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.""" |
1136 if self.patchset is None and not self.lookedup_patchset: | 1136 if self.patchset is None and not self.lookedup_patchset: |
1137 patchset = RunGit(['config', self._PatchsetSetting()], | 1137 patchset = RunGit(['config', self._PatchsetSetting()], |
1138 error_ok=True).strip() | 1138 error_ok=True).strip() |
1139 self.patchset = int(patchset) or None if patchset else None | 1139 self.patchset = int(patchset) or None if patchset else None |
1140 self.lookedup_patchset = True | 1140 self.lookedup_patchset = True |
1141 return self.patchset | 1141 return self.patchset |
1142 | 1142 |
1143 def SetPatchset(self, patchset): | 1143 def SetPatchset(self, patchset): |
1144 """Set this branch's patchset. If patchset=0, clears the patchset.""" | 1144 """Set this branch's patchset. If patchset=0, clears the patchset.""" |
1145 if patchset: | 1145 if patchset: |
1146 RunGit(['config', self._PatchsetSetting(), str(patchset)]) | 1146 RunGit(['config', self._PatchsetSetting(), str(patchset)]) |
1147 self.patchset = patchset | 1147 self.patchset = patchset |
1148 else: | 1148 else: |
1149 RunGit(['config', '--unset', self._PatchsetSetting()], | 1149 RunGit(['config', '--unset', self._PatchsetSetting()], |
1150 stderr=subprocess2.PIPE, error_ok=True) | 1150 stderr=subprocess2.PIPE, error_ok=True) |
1151 self.patchset = None | 1151 self.patchset = None |
1152 | 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 | |
1175 def SetIssue(self, issue=None): | 1153 def SetIssue(self, issue=None): |
1176 """Set this branch's issue. If issue isn't given, clears the issue.""" | 1154 """Set this branch's issue. If issue isn't given, clears the issue.""" |
1177 if issue: | 1155 if issue: |
1178 self.issue = issue | 1156 self.issue = issue |
1179 RunGit(['config', self._IssueSetting(), str(issue)]) | 1157 RunGit(['config', self._IssueSetting(), str(issue)]) |
1180 if not settings.GetIsGerrit() and self.rietveld_server: | 1158 if not settings.GetIsGerrit() and self.rietveld_server: |
1181 RunGit(['config', self._RietveldServer(), self.rietveld_server]) | 1159 RunGit(['config', self._RietveldServer(), self.rietveld_server]) |
1182 else: | 1160 else: |
1183 current_issue = self.GetIssue() | 1161 current_issue = self.GetIssue() |
1184 if current_issue: | 1162 if current_issue: |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1225 return presubmit_support.GitChange( | 1203 return presubmit_support.GitChange( |
1226 name, | 1204 name, |
1227 description, | 1205 description, |
1228 absroot, | 1206 absroot, |
1229 files, | 1207 files, |
1230 issue, | 1208 issue, |
1231 patchset, | 1209 patchset, |
1232 author, | 1210 author, |
1233 upstream=upstream_branch) | 1211 upstream=upstream_branch) |
1234 | 1212 |
1213 def UpdateDescription(self, description): | |
1214 self.description = description | |
1215 return self._UpdateDescriptionRemote(self, description) | |
1216 | |
1217 def RunHook(self, committing, may_prompt, verbose, change): | |
1218 """Calls sys.exit() if the hook fails; returns a HookResults otherwise.""" | |
1219 | |
1220 try: | |
1221 return presubmit_support.DoPresubmitChecks(change, committing, | |
1222 verbose=verbose, output_stream=sys.stdout, input_stream=sys.stdin, | |
1223 default_presubmit=None, may_prompt=may_prompt, | |
1224 rietveld_obj=self._GetRieveldObjForPresubmit()) | |
1225 except presubmit_support.PresubmitFailure, e: | |
1226 DieWithError( | |
1227 ('%s\nMaybe your depot_tools is out of date?\n' | |
1228 'If all fails, contact maruel@') % e) | |
1229 | |
1230 # Codereview specific methods are implemented in respective child classes. | |
1231 | |
1235 def GetStatus(self): | 1232 def GetStatus(self): |
1236 """Apply a rough heuristic to give a simple summary of an issue's review | 1233 """Apply a rough heuristic to give a simple summary of an issue's review |
1237 or CQ status, assuming adherence to a common workflow. | 1234 or CQ status, assuming adherence to a common workflow. |
1235 | |
1236 Returns None if no issue for this branch, or specific string keywords. | |
1237 """ | |
1238 raise NotImplementedError() | |
1239 | |
1240 def GetCodereviewServer(self): | |
1241 """Returns server URL without end slash, like "https://codereview.com".""" | |
1242 raise NotImplementedError() | |
1243 | |
1244 def _FetchDescription(self): | |
1245 """Fetches and sets description from the codereview server.""" | |
1246 raise NotImplementedError() | |
1247 | |
1248 def _IssueSetting(self): | |
1249 """Returns name of git config setting which stores issue number.""" | |
Michael Achenbach
2016/03/16 09:28:19
Not sure about this documentation and the same bel
tandrii(chromium)
2016/03/21 15:06:05
Removed from subclasses and fixed here, to avoid c
| |
1250 raise NotImplementedError() | |
1251 | |
1252 def _PatchsetSetting(self): | |
1253 """Returns name of git config setting which stores issue number.""" | |
1254 raise NotImplementedError() | |
1255 | |
1256 def _GetRieveldObjForPresubmit(self): | |
1257 # This is an unfortunate Rietveld-embeddedness in presubmit. | |
1258 raise NotImplementedError() | |
1259 | |
1260 def _UpdateDescriptionRemote(self, description): | |
1261 raise NotImplementedError() | |
1262 | |
1263 def CloseIssue(self): | |
1264 """Updates the description and closes the issue.""" | |
1265 raise NotImplementedError() | |
1266 | |
1267 | |
1268 class RietveldChangelist(ChangelistBase): | |
1269 def __init__(self, branchref=None, issue=None, auth_config=None): | |
1270 super(RietveldChangelist, self).__init__(branchref, issue) | |
1271 assert settings | |
1272 settings.GetDefaultServerUrl() | |
1273 | |
1274 self.rietveld_server = None | |
1275 self._auth_config = auth_config | |
1276 self._props = None | |
1277 self._rpc_server = None | |
1278 | |
1279 @property | |
1280 def auth_config(self): | |
1281 return self._auth_config | |
1282 | |
1283 def GetCodereviewServer(self): | |
1284 # TODO: update all users to use GetCodereviewServer instead. | |
1285 return self.GetRietveldServer() | |
1286 | |
1287 def GetRietveldServer(self): | |
1288 if not self.rietveld_server: | |
1289 # If we're on a branch then get the server potentially associated | |
1290 # with that branch. | |
1291 if self.GetIssue(): | |
1292 rietveld_server_config = self._RietveldServer() | |
1293 if rietveld_server_config: | |
1294 self.rietveld_server = gclient_utils.UpgradeToHttps(RunGit( | |
1295 ['config', rietveld_server_config], error_ok=True).strip()) | |
1296 if not self.rietveld_server: | |
1297 self.rietveld_server = settings.GetDefaultServerUrl() | |
1298 return self.rietveld_server | |
1299 | |
1300 def _FetchDescription(self): | |
1301 """Fetches and sets description from the codereview server.""" | |
1302 issue = self.GetIssue() | |
1303 assert issue | |
1304 try: | |
1305 self.description = self.RpcServer().get_description(issue).strip() | |
1306 except urllib2.HTTPError as e: | |
1307 if e.code == 404: | |
1308 DieWithError( | |
1309 ('\nWhile fetching the description for issue %d, received a ' | |
1310 '404 (not found)\n' | |
1311 'error. It is likely that you deleted this ' | |
1312 'issue on the server. If this is the\n' | |
1313 'case, please run\n\n' | |
1314 ' git cl issue 0\n\n' | |
1315 'to clear the association with the deleted issue. Then run ' | |
1316 'this command again.') % issue) | |
1317 else: | |
1318 DieWithError( | |
1319 '\nFailed to fetch issue description. HTTP error %d' % e.code) | |
1320 except urllib2.URLError as e: | |
1321 print >> sys.stderr, ( | |
1322 'Warning: Failed to retrieve CL description due to network ' | |
1323 'failure.') | |
1324 self.description = '' | |
1325 | |
1326 def GetMostRecentPatchset(self): | |
1327 return self.GetIssueProperties()['patchsets'][-1] | |
1328 | |
1329 def GetPatchSetDiff(self, issue, patchset): | |
1330 return self.RpcServer().get( | |
1331 '/download/issue%s_%s.diff' % (issue, patchset)) | |
1332 | |
1333 def GetIssueProperties(self): | |
1334 if self._props is None: | |
1335 issue = self.GetIssue() | |
1336 if not issue: | |
1337 self._props = {} | |
1338 else: | |
1339 self._props = self.RpcServer().get_issue_properties(issue, True) | |
1340 return self._props | |
1341 | |
1342 def GetApprovingReviewers(self): | |
1343 return get_approving_reviewers(self.GetIssueProperties()) | |
1344 | |
1345 def AddComment(self, message): | |
1346 return self.RpcServer().add_comment(self.GetIssue(), message) | |
1347 | |
1348 def GetStatus(self): | |
1349 """Apply a rough heuristic to give a simple summary of an issue's review | |
1350 or CQ status, assuming adherence to a common workflow. | |
1238 | 1351 |
1239 Returns None if no issue for this branch, or one of the following keywords: | 1352 Returns None if no issue for this branch, or one of the following keywords: |
1240 * 'error' - error from review tool (including deleted issues) | 1353 * 'error' - error from review tool (including deleted issues) |
1241 * 'unsent' - not sent for review | 1354 * 'unsent' - not sent for review |
1242 * 'waiting' - waiting for review | 1355 * 'waiting' - waiting for review |
1243 * 'reply' - waiting for owner to reply to review | 1356 * 'reply' - waiting for owner to reply to review |
1244 * 'lgtm' - LGTM from at least one approved reviewer | 1357 * 'lgtm' - LGTM from at least one approved reviewer |
1245 * 'commit' - in the commit queue | 1358 * 'commit' - in the commit queue |
1246 * 'closed' - closed | 1359 * 'closed' - closed |
1247 """ | 1360 """ |
(...skipping 24 matching lines...) Expand all Loading... | |
1272 messages = props.get('messages') or [] | 1385 messages = props.get('messages') or [] |
1273 | 1386 |
1274 if not messages: | 1387 if not messages: |
1275 # No message was sent. | 1388 # No message was sent. |
1276 return 'unsent' | 1389 return 'unsent' |
1277 if messages[-1]['sender'] != props.get('owner_email'): | 1390 if messages[-1]['sender'] != props.get('owner_email'): |
1278 # Non-LGTM reply from non-owner | 1391 # Non-LGTM reply from non-owner |
1279 return 'reply' | 1392 return 'reply' |
1280 return 'waiting' | 1393 return 'waiting' |
1281 | 1394 |
1282 def RunHook(self, committing, may_prompt, verbose, change): | 1395 def _UpdateDescriptionRemote(self, description): |
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 | |
1297 return self.RpcServer().update_description( | 1396 return self.RpcServer().update_description( |
1298 self.GetIssue(), self.description) | 1397 self.GetIssue(), self.description) |
1299 | 1398 |
1300 def CloseIssue(self): | 1399 def CloseIssue(self): |
1301 """Updates the description and closes the issue.""" | 1400 """Updates the description and closes the issue.""" |
1302 return self.RpcServer().close_issue(self.GetIssue()) | 1401 return self.RpcServer().close_issue(self.GetIssue()) |
1303 | 1402 |
1304 def SetFlag(self, flag, value): | 1403 def SetFlag(self, flag, value): |
1305 """Patchset must match.""" | 1404 """Patchset must match.""" |
1306 if not self.GetPatchset(): | 1405 if not self.GetPatchset(): |
(...skipping 14 matching lines...) Expand all Loading... | |
1321 """Returns an upload.RpcServer() to access this review's rietveld instance. | 1420 """Returns an upload.RpcServer() to access this review's rietveld instance. |
1322 """ | 1421 """ |
1323 if not self._rpc_server: | 1422 if not self._rpc_server: |
1324 self._rpc_server = rietveld.CachingRietveld( | 1423 self._rpc_server = rietveld.CachingRietveld( |
1325 self.GetRietveldServer(), | 1424 self.GetRietveldServer(), |
1326 self._auth_config or auth.make_auth_config()) | 1425 self._auth_config or auth.make_auth_config()) |
1327 return self._rpc_server | 1426 return self._rpc_server |
1328 | 1427 |
1329 def _IssueSetting(self): | 1428 def _IssueSetting(self): |
1330 """Return the git setting that stores this change's issue.""" | 1429 """Return the git setting that stores this change's issue.""" |
1331 if settings.GetIsGerrit(): | |
1332 return 'branch.%s.gerritissue' % self.GetBranch() | |
1333 return 'branch.%s.rietveldissue' % self.GetBranch() | 1430 return 'branch.%s.rietveldissue' % self.GetBranch() |
1334 | 1431 |
1335 def _PatchsetSetting(self): | 1432 def _PatchsetSetting(self): |
1336 """Return the git setting that stores this change's most recent patchset.""" | 1433 """Return the git setting that stores this change's most recent patchset.""" |
1337 return 'branch.%s.rietveldpatchset' % self.GetBranch() | 1434 return 'branch.%s.rietveldpatchset' % self.GetBranch() |
1338 | 1435 |
1339 def _RietveldServer(self): | 1436 def _RietveldServer(self): |
1340 """Returns the git setting that stores this change's rietveld server.""" | 1437 """Returns the git setting that stores this change's rietveld server.""" |
1341 branch = self.GetBranch() | 1438 branch = self.GetBranch() |
1342 if branch: | 1439 if branch: |
1343 return 'branch.%s.rietveldserver' % branch | 1440 return 'branch.%s.rietveldserver' % branch |
1344 return None | 1441 return None |
1345 | 1442 |
1443 def _GetRieveldObjForPresubmit(self): | |
1444 return self.RpcServer() | |
1445 | |
1446 | |
1447 class GerritChangelist(ChangelistBase): | |
1448 def __init__(self, branchref=None, issue=None): | |
1449 super(GerritChangelist, self).__init__(branchref, issue) | |
1450 self._change_id = None | |
1451 self._gerrit_server = None | |
1452 | |
1453 def GetCodereviewServer(self): | |
1454 if not self._gerrit_server: | |
1455 # If we're on a branch then get the server potentially associated | |
1456 # with that branch. | |
1457 if self.GetIssue(): | |
1458 gerrit_server_setting = self._GerritServerSetting() | |
1459 if gerrit_server_setting: | |
1460 self._gerrit_server = RunGit(['config', gerrit_server_setting], | |
1461 error_ok=True).strip() | |
1462 if not self._gerrit_server: | |
1463 # We assume repo to be hosted on Gerrit, and hence Gerrit server | |
1464 # has "-review" suffix for lowest level subdomain. | |
1465 parts = urlparse.urlparse(self.GetRemoteUrl()).netloc.split('.') | |
1466 parts[0] = parts[0] + '-review' | |
1467 self._gerrit_server = 'https://%s' % '.'.join(parts) | |
1468 return self._gerrit_server | |
1469 | |
1470 def _IssueSetting(self): | |
1471 """Return the git setting that stores this change's issue.""" | |
1472 return 'branch.%s.gerritissue' % self.GetBranch() | |
1473 | |
1474 def _PatchsetSetting(self): | |
1475 """Return the git setting that stores this change's most recent patchset.""" | |
1476 return 'branch.%s.gerritpatchset' % self.GetBranch() | |
Michael Achenbach
2016/03/16 09:28:19
I maybe wouldn't call the CL a refactoring, as it
tandrii(chromium)
2016/03/21 15:06:05
Good eye :) Yeah, this bit is new, indeed. And so
| |
1477 | |
1478 def _GerritServerSetting(self): | |
1479 """Returns the git setting that stores this change's Gerrit server.""" | |
1480 branch = self.GetBranch() | |
1481 if branch: | |
1482 return 'branch.%s.gerritserver' % branch | |
1483 return None | |
1484 | |
1485 def _GetRieveldObjForPresubmit(self): | |
1486 class ThisIsNotRietveldIssue(object): | |
1487 def __getattr__(self, attr): | |
1488 def handler(*_, **__): | |
1489 print('You aren\'t using Rietveld at the moment, but Gerrit.\n' | |
1490 'Using Rietveld in your PRESUBMIT scripts won\'t work.\n' | |
1491 'Either change your PRESUBIT to not use rietveld_obj.%s,\n' | |
1492 'or use Rietveld for codereview.\n' % attr) | |
1493 raise NotImplementedError() | |
1494 return handler | |
1495 return ThisIsNotRietveldIssue() | |
1496 | |
1346 | 1497 |
1347 class ChangeDescription(object): | 1498 class ChangeDescription(object): |
1348 """Contains a parsed form of the change description.""" | 1499 """Contains a parsed form of the change description.""" |
1349 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$' | 1500 R_LINE = r'^[ \t]*(TBR|R)[ \t]*=[ \t]*(.*?)[ \t]*$' |
1350 BUG_LINE = r'^[ \t]*(BUG)[ \t]*=[ \t]*(.*?)[ \t]*$' | 1501 BUG_LINE = r'^[ \t]*(BUG)[ \t]*=[ \t]*(.*?)[ \t]*$' |
1351 | 1502 |
1352 def __init__(self, description): | 1503 def __init__(self, description): |
1353 self._description_lines = (description or '').strip().splitlines() | 1504 self._description_lines = (description or '').strip().splitlines() |
1354 | 1505 |
1355 @property # www.logilab.org/ticket/89786 | 1506 @property # www.logilab.org/ticket/89786 |
(...skipping 525 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1881 return 0 | 2032 return 0 |
1882 | 2033 |
1883 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads']) | 2034 branches = RunGit(['for-each-ref', '--format=%(refname)', 'refs/heads']) |
1884 if not branches: | 2035 if not branches: |
1885 print('No local branch found.') | 2036 print('No local branch found.') |
1886 return 0 | 2037 return 0 |
1887 | 2038 |
1888 changes = ( | 2039 changes = ( |
1889 Changelist(branchref=b, auth_config=auth_config) | 2040 Changelist(branchref=b, auth_config=auth_config) |
1890 for b in branches.splitlines()) | 2041 for b in branches.splitlines()) |
2042 # TODO(tandrii): refactor to use CLs list instead of branches list. | |
1891 branches = [c.GetBranch() for c in changes] | 2043 branches = [c.GetBranch() for c in changes] |
1892 alignment = max(5, max(len(b) for b in branches)) | 2044 alignment = max(5, max(len(b) for b in branches)) |
1893 print 'Branches associated with reviews:' | 2045 print 'Branches associated with reviews:' |
1894 output = get_cl_statuses(branches, | 2046 output = get_cl_statuses(branches, |
1895 fine_grained=not options.fast, | 2047 fine_grained=not options.fast, |
1896 max_processes=options.maxjobs, | 2048 max_processes=options.maxjobs, |
1897 auth_config=auth_config) | 2049 auth_config=auth_config) |
1898 | 2050 |
1899 branch_statuses = {} | 2051 branch_statuses = {} |
1900 alignment = max(5, max(len(ShortBranchName(b)) for b in branches)) | 2052 alignment = max(5, max(len(ShortBranchName(b)) for b in branches)) |
(...skipping 700 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2601 auth.add_auth_options(parser) | 2753 auth.add_auth_options(parser) |
2602 (options, args) = parser.parse_args(args) | 2754 (options, args) = parser.parse_args(args) |
2603 auth_config = auth.extract_auth_config_from_options(options) | 2755 auth_config = auth.extract_auth_config_from_options(options) |
2604 | 2756 |
2605 if git_common.is_dirty_git_tree('upload'): | 2757 if git_common.is_dirty_git_tree('upload'): |
2606 return 1 | 2758 return 1 |
2607 | 2759 |
2608 options.reviewers = cleanup_list(options.reviewers) | 2760 options.reviewers = cleanup_list(options.reviewers) |
2609 options.cc = cleanup_list(options.cc) | 2761 options.cc = cleanup_list(options.cc) |
2610 | 2762 |
2611 cl = Changelist(auth_config=auth_config) | 2763 if settings.GetIsGerrit(): |
2764 # TODO(tandrii): improve based on (repo, branch, options). | |
2765 cl = GerritChangelist() | |
Michael Achenbach
2016/03/16 09:28:19
I assume that's for the issue==None case? Or could
tandrii(chromium)
2016/03/21 15:06:05
I have exactly a use case for the latter, though i
Michael Achenbach
2016/03/24 09:58:11
Acknowledged.
| |
2766 else: | |
2767 cl = RietveldChangelist(auth_config=auth_config) | |
2612 if args: | 2768 if args: |
2613 # TODO(ukai): is it ok for gerrit case? | 2769 # TODO(ukai): is it ok for gerrit case? |
2614 base_branch = args[0] | 2770 base_branch = args[0] |
2615 else: | 2771 else: |
2616 if cl.GetBranch() is None: | 2772 if cl.GetBranch() is None: |
2617 DieWithError('Can\'t upload from detached HEAD state. Get on a branch!') | 2773 DieWithError('Can\'t upload from detached HEAD state. Get on a branch!') |
2618 | 2774 |
2619 # Default to diffing against common ancestor of upstream branch | 2775 # Default to diffing against common ancestor of upstream branch |
2620 base_branch = cl.GetCommonAncestorWithUpstream() | 2776 base_branch = cl.GetCommonAncestorWithUpstream() |
2621 args = [base_branch, 'HEAD'] | 2777 args = [base_branch, 'HEAD'] |
(...skipping 651 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
3273 except subprocess2.CalledProcessError: | 3429 except subprocess2.CalledProcessError: |
3274 print 'Failed to apply the patch' | 3430 print 'Failed to apply the patch' |
3275 return 1 | 3431 return 1 |
3276 | 3432 |
3277 # If we had an issue, commit the current state and register the issue. | 3433 # If we had an issue, commit the current state and register the issue. |
3278 if not nocommit: | 3434 if not nocommit: |
3279 RunGit(['commit', '-m', (cl.GetDescription() + '\n\n' + | 3435 RunGit(['commit', '-m', (cl.GetDescription() + '\n\n' + |
3280 'patch from issue %(i)s at patchset ' | 3436 'patch from issue %(i)s at patchset ' |
3281 '%(p)s (http://crrev.com/%(i)s#ps%(p)s)' | 3437 '%(p)s (http://crrev.com/%(i)s#ps%(p)s)' |
3282 % {'i': issue, 'p': patchset})]) | 3438 % {'i': issue, 'p': patchset})]) |
3283 cl = Changelist(auth_config=auth_config) | 3439 cl = RietveldChangelist(auth_config=auth_config) |
Michael Achenbach
2016/03/16 09:28:19
Does that deserve a TODO? Or is it a rietveld-only
tandrii(chromium)
2016/03/21 15:06:05
For now, it's sooo Rietveld-specific, that TODO at
Michael Achenbach
2016/03/24 09:58:11
Acknowledged.
| |
3284 cl.SetIssue(issue) | 3440 cl.SetIssue(issue) |
3285 cl.SetPatchset(patchset) | 3441 cl.SetPatchset(patchset) |
3286 print "Committed patch locally." | 3442 print "Committed patch locally." |
3287 else: | 3443 else: |
3288 print "Patch applied to index." | 3444 print "Patch applied to index." |
3289 return 0 | 3445 return 0 |
3290 | 3446 |
3291 | 3447 |
3292 def CMDrebase(parser, args): | 3448 def CMDrebase(parser, args): |
3293 """Rebases current branch on top of svn repo.""" | 3449 """Rebases current branch on top of svn repo.""" |
(...skipping 714 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
4008 if __name__ == '__main__': | 4164 if __name__ == '__main__': |
4009 # These affect sys.stdout so do it outside of main() to simplify mocks in | 4165 # These affect sys.stdout so do it outside of main() to simplify mocks in |
4010 # unit testing. | 4166 # unit testing. |
4011 fix_encoding.fix_encoding() | 4167 fix_encoding.fix_encoding() |
4012 colorama.init() | 4168 colorama.init() |
4013 try: | 4169 try: |
4014 sys.exit(main(sys.argv[1:])) | 4170 sys.exit(main(sys.argv[1:])) |
4015 except KeyboardInterrupt: | 4171 except KeyboardInterrupt: |
4016 sys.stderr.write('interrupted\n') | 4172 sys.stderr.write('interrupted\n') |
4017 sys.exit(1) | 4173 sys.exit(1) |
OLD | NEW |