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

Side by Side Diff: gcl.py

Issue 6696010: Modify gcl to use suggested_reviewer output from presubmit_support. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | git_cl/git_cl.py » ('j') | git_cl/git_cl.py » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2010 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 """\ 6 """\
7 Wrapper script around Rietveld's upload.py that simplifies working with groups 7 Wrapper script around Rietveld's upload.py that simplifies working with groups
8 of files. 8 of files.
9 """ 9 """
10 10
(...skipping 17 matching lines...) Expand all
28 import json 28 import json
29 # Some versions of python2.5 have an incomplete json module. Check to make 29 # Some versions of python2.5 have an incomplete json module. Check to make
30 # sure loads exists. 30 # sure loads exists.
31 # pylint: disable=W0104 31 # pylint: disable=W0104
32 json.loads 32 json.loads
33 except (ImportError, AttributeError): 33 except (ImportError, AttributeError):
34 # Import the one included in depot_tools. 34 # Import the one included in depot_tools.
35 sys.path.append(os.path.join(os.path.dirname(__file__), 'third_party')) 35 sys.path.append(os.path.join(os.path.dirname(__file__), 'third_party'))
36 import simplejson as json # pylint: disable=F0401 36 import simplejson as json # pylint: disable=F0401
37 37
38 import breakpad # pylint: disable=W0611
39
40 # gcl now depends on gclient. 38 # gcl now depends on gclient.
41 from scm import SVN 39 from scm import SVN
40
41 import breakpad # pylint: disable=W0611
M-A Ruel 2011/03/15 19:11:57 breakpad _must_ be the first.
42 import gclient_utils 42 import gclient_utils
43 import owners
44 import presubmit_support
43 45
44 __version__ = '1.2' 46 __version__ = '1.2'
45 47
46 48
47 CODEREVIEW_SETTINGS = { 49 CODEREVIEW_SETTINGS = {
48 # To make gcl send reviews to a server, check in a file named 50 # To make gcl send reviews to a server, check in a file named
49 # "codereview.settings" (see |CODEREVIEW_SETTINGS_FILE| below) to your 51 # "codereview.settings" (see |CODEREVIEW_SETTINGS_FILE| below) to your
50 # project's base directory and add the following line to codereview.settings: 52 # project's base directory and add the following line to codereview.settings:
51 # CODE_REVIEW_SERVER: codereview.yourserver.org 53 # CODE_REVIEW_SERVER: codereview.yourserver.org
52 } 54 }
(...skipping 10 matching lines...) Expand all
63 # Warning message when the change appears to be missing tests. 65 # Warning message when the change appears to be missing tests.
64 MISSING_TEST_MSG = "Change contains new or modified methods, but no new tests!" 66 MISSING_TEST_MSG = "Change contains new or modified methods, but no new tests!"
65 67
66 # Global cache of files cached in GetCacheDir(). 68 # Global cache of files cached in GetCacheDir().
67 FILES_CACHE = {} 69 FILES_CACHE = {}
68 70
69 # Valid extensions for files we want to lint. 71 # Valid extensions for files we want to lint.
70 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" 72 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
71 DEFAULT_LINT_IGNORE_REGEX = r"$^" 73 DEFAULT_LINT_IGNORE_REGEX = r"$^"
72 74
75 REVIEWERS_REGEX = r'\s*R=(.+)'
73 76
74 def CheckHomeForFile(filename): 77 def CheckHomeForFile(filename):
75 """Checks the users home dir for the existence of the given file. Returns 78 """Checks the users home dir for the existence of the given file. Returns
76 the path to the file if it's there, or None if it is not. 79 the path to the file if it's there, or None if it is not.
77 """ 80 """
78 home_vars = ['HOME'] 81 home_vars = ['HOME']
79 if sys.platform in ('cygwin', 'win32'): 82 if sys.platform in ('cygwin', 'win32'):
80 home_vars.append('USERPROFILE') 83 home_vars.append('USERPROFILE')
81 for home_var in home_vars: 84 for home_var in home_vars:
82 home = os.getenv(home_var) 85 home = os.getenv(home_var)
(...skipping 196 matching lines...) Expand 10 before | Expand all | Expand 10 after
279 rietveld: rietveld server for this change 282 rietveld: rietveld server for this change
280 """ 283 """
281 # Kept for unit test support. This is for the old format, it's deprecated. 284 # Kept for unit test support. This is for the old format, it's deprecated.
282 _SEPARATOR = "\n-----\n" 285 _SEPARATOR = "\n-----\n"
283 286
284 def __init__(self, name, issue, patchset, description, files, local_root, 287 def __init__(self, name, issue, patchset, description, files, local_root,
285 rietveld, needs_upload=False): 288 rietveld, needs_upload=False):
286 self.name = name 289 self.name = name
287 self.issue = int(issue) 290 self.issue = int(issue)
288 self.patchset = int(patchset) 291 self.patchset = int(patchset)
289 self.description = description 292 self._description = None
293 self._subject = None
294 self._reviewers = None
295 self._set_description(description)
290 if files is None: 296 if files is None:
291 files = [] 297 files = []
292 self._files = files 298 self._files = files
293 self.patch = None 299 self.patch = None
294 self._local_root = local_root 300 self._local_root = local_root
295 self.needs_upload = needs_upload 301 self.needs_upload = needs_upload
296 self.rietveld = rietveld 302 self.rietveld = rietveld
297 if not self.rietveld: 303 if not self.rietveld:
298 # Set the default value. 304 # Set the default value.
299 self.rietveld = GetCodeReviewSetting('CODE_REVIEW_SERVER') 305 self.rietveld = GetCodeReviewSetting('CODE_REVIEW_SERVER')
300 306
307 def _get_description(self):
308 return self._description
309
310 def _set_description(self, description):
311 # TODO(dpranke): Cloned from git_cl.py. These should be shared.
312 if not description:
313 self._description = description
314 return
315
316 parsed_lines = []
317 reviewers_re = re.compile(REVIEWERS_REGEX)
318 reviewers = ''
319 subject = ''
320 for l in description.splitlines():
321 if not subject:
322 subject = l
323 matched_reviewers = reviewers_re.match(l)
324 if matched_reviewers:
325 reviewers = matched_reviewers.group(1)
326 parsed_lines.append(l)
327
328 if subject.find("\r\n") != -1:
M-A Ruel 2011/03/15 19:11:57 http://docs.python.org/library/stdtypes.html#str.s
chase 2011/03/15 20:48:58 single quotes here and below, please
329 subject = subject[:subject.find("\r\n")]
330 if subject.find("\n") != -1:
331 subject = subject[:subject.find("\n")]
332 if len(subject) > 77:
M-A Ruel 2011/03/15 19:11:57 The limit is in fact 100. if len(subject) > 100
333 subject = subject + "..."
M-A Ruel 2011/03/15 19:11:57 subject[:97]
334
335 self._subject = subject
336 self._reviewers = reviewers
337 self._description = '\n'.join(parsed_lines)
338
339 description = property(_get_description, _set_description)
M-A Ruel 2011/03/15 19:11:57 Use python 2.2 format instead: http://docs.python.
340
341 @property
342 def reviewers(self):
343 return self._reviewers
344
345 @property
346 def subject(self):
347 return self._subject
348
301 def NeedsUpload(self): 349 def NeedsUpload(self):
302 return self.needs_upload 350 return self.needs_upload
303 351
304 def GetFileNames(self): 352 def GetFileNames(self):
305 """Returns the list of file names included in this change.""" 353 """Returns the list of file names included in this change."""
306 return [f[1] for f in self._files] 354 return [f[1] for f in self._files]
307 355
308 def GetFiles(self): 356 def GetFiles(self):
309 """Returns the list of files included in this change with their status.""" 357 """Returns the list of files included in this change with their status."""
310 return self._files 358 return self._files
(...skipping 396 matching lines...) Expand 10 before | Expand all | Expand 10 after
707 755
708 return editor 756 return editor
709 757
710 758
711 def GenerateDiff(files, root=None): 759 def GenerateDiff(files, root=None):
712 return SVN.GenerateDiff(files, root=root) 760 return SVN.GenerateDiff(files, root=root)
713 761
714 762
715 def OptionallyDoPresubmitChecks(change_info, committing, args): 763 def OptionallyDoPresubmitChecks(change_info, committing, args):
716 if FilterFlag(args, "--no_presubmit") or FilterFlag(args, "--force"): 764 if FilterFlag(args, "--no_presubmit") or FilterFlag(args, "--force"):
717 return True 765 return presubmit_support.PresubmitOutput()
718 return DoPresubmitChecks(change_info, committing, True) 766 return DoPresubmitChecks(change_info, committing, True)
719 767
720 768
769 def suggest_reviewers(change_info, affected_files):
770 owners_db = owners.Database(change_info.LocalRoot(), fopen=file,
771 os_path=os.path)
772 return owners_db.reviewers_for(affected_files)
773
774
721 def defer_attributes(a, b): 775 def defer_attributes(a, b):
722 """Copy attributes from an object (like a function) to another.""" 776 """Copy attributes from an object (like a function) to another."""
723 for x in dir(a): 777 for x in dir(a):
724 if not getattr(b, x, None): 778 if not getattr(b, x, None):
725 setattr(b, x, getattr(a, x)) 779 setattr(b, x, getattr(a, x))
726 780
727 781
728 def need_change(function): 782 def need_change(function):
729 """Converts args -> change_info.""" 783 """Converts args -> change_info."""
730 # pylint: disable=W0612,W0621 784 # pylint: disable=W0612,W0621
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
790 def CMDupload(change_info, args): 844 def CMDupload(change_info, args):
791 """Uploads the changelist to the server for review. 845 """Uploads the changelist to the server for review.
792 846
793 This does not submit a try job; use gcl try to submit a try job. 847 This does not submit a try job; use gcl try to submit a try job.
794 """ 848 """
795 if '-s' in args or '--server' in args: 849 if '-s' in args or '--server' in args:
796 ErrorExit('Don\'t use the -s flag, fix codereview.settings instead') 850 ErrorExit('Don\'t use the -s flag, fix codereview.settings instead')
797 if not change_info.GetFiles(): 851 if not change_info.GetFiles():
798 print "Nothing to upload, changelist is empty." 852 print "Nothing to upload, changelist is empty."
799 return 0 853 return 0
800 if not OptionallyDoPresubmitChecks(change_info, False, args): 854
855 output = OptionallyDoPresubmitChecks(change_info, False, args)
856 if not output.should_continue():
801 return 1 857 return 1
802 no_watchlists = (FilterFlag(args, "--no_watchlists") or 858 no_watchlists = (FilterFlag(args, "--no_watchlists") or
803 FilterFlag(args, "--no-watchlists")) 859 FilterFlag(args, "--no-watchlists"))
804 860
805 # Map --send-mail to --send_mail 861 # Map --send-mail to --send_mail
806 if FilterFlag(args, "--send-mail"): 862 if FilterFlag(args, "--send-mail"):
807 args.append("--send_mail") 863 args.append("--send_mail")
808 864
809 upload_arg = ["upload.py", "-y"] 865 upload_arg = ["upload.py", "-y"]
810 upload_arg.append("--server=%s" % change_info.rietveld) 866 upload_arg.append("--server=%s" % change_info.rietveld)
867
868 reviewers = change_info.reviewers or output.reviewers
869 if (reviewers and
870 not any(arg.startswith('-r') or arg.startswith('--reviewer') for
871 arg in args)):
872 upload_arg.append('--reviewers=%s' % ','.join(reviewers))
873
811 upload_arg.extend(args) 874 upload_arg.extend(args)
812 875
813 desc_file = "" 876 desc_file = ""
814 if change_info.issue: # Uploading a new patchset. 877 if change_info.issue: # Uploading a new patchset.
815 found_message = False 878 found_message = False
816 for arg in args: 879 for arg in args:
817 if arg.startswith("--message") or arg.startswith("-m"): 880 if arg.startswith("--message") or arg.startswith("-m"):
818 found_message = True 881 found_message = True
819 break 882 break
820 883
(...skipping 13 matching lines...) Expand all
834 watchlist = watchlists.Watchlists(change_info.GetLocalRoot()) 897 watchlist = watchlists.Watchlists(change_info.GetLocalRoot())
835 watchers = watchlist.GetWatchersForPaths(change_info.GetFileNames()) 898 watchers = watchlist.GetWatchersForPaths(change_info.GetFileNames())
836 899
837 cc_list = GetCodeReviewSetting("CC_LIST") 900 cc_list = GetCodeReviewSetting("CC_LIST")
838 if not no_watchlists and watchers: 901 if not no_watchlists and watchers:
839 # Filter out all empty elements and join by ',' 902 # Filter out all empty elements and join by ','
840 cc_list = ','.join(filter(None, [cc_list] + watchers)) 903 cc_list = ','.join(filter(None, [cc_list] + watchers))
841 if cc_list: 904 if cc_list:
842 upload_arg.append("--cc=" + cc_list) 905 upload_arg.append("--cc=" + cc_list)
843 upload_arg.append("--description_file=" + desc_file + "") 906 upload_arg.append("--description_file=" + desc_file + "")
844 if change_info.description: 907 if change_info.subject:
845 subject = change_info.description[:77] 908 upload_arg.append("--message=" + change_info.subject)
846 if subject.find("\r\n") != -1:
847 subject = subject[:subject.find("\r\n")]
848 if subject.find("\n") != -1:
849 subject = subject[:subject.find("\n")]
850 if len(change_info.description) > 77:
851 subject = subject + "..."
852 upload_arg.append("--message=" + subject)
853 909
854 if GetCodeReviewSetting("PRIVATE") == "True": 910 if GetCodeReviewSetting("PRIVATE") == "True":
855 upload_arg.append("--private") 911 upload_arg.append("--private")
856 912
857 # Change the current working directory before calling upload.py so that it 913 # Change the current working directory before calling upload.py so that it
858 # shows the correct base. 914 # shows the correct base.
859 previous_cwd = os.getcwd() 915 previous_cwd = os.getcwd()
860 os.chdir(change_info.GetLocalRoot()) 916 os.chdir(change_info.GetLocalRoot())
861 # If we have a lot of files with long paths, then we won't be able to fit 917 # If we have a lot of files with long paths, then we won't be able to fit
862 # the command to "svn diff". Instead, we generate the diff manually for 918 # the command to "svn diff". Instead, we generate the diff manually for
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after
977 os.remove(targets_filename) 1033 os.remove(targets_filename)
978 if output.find("Committed revision") != -1: 1034 if output.find("Committed revision") != -1:
979 change_info.Delete() 1035 change_info.Delete()
980 1036
981 if change_info.issue: 1037 if change_info.issue:
982 revision = re.compile(".*?\nCommitted revision (\d+)", 1038 revision = re.compile(".*?\nCommitted revision (\d+)",
983 re.DOTALL).match(output).group(1) 1039 re.DOTALL).match(output).group(1)
984 viewvc_url = GetCodeReviewSetting("VIEW_VC") 1040 viewvc_url = GetCodeReviewSetting("VIEW_VC")
985 change_info.description = change_info.description + '\n' 1041 change_info.description = change_info.description + '\n'
986 if viewvc_url: 1042 if viewvc_url:
987 change_info.description += "\nCommitted: " + viewvc_url + revision 1043 change_info.description = (change_info.description +
1044 "\nCommitted: " + viewvc_url + revision)
chase 2011/03/15 20:48:58 can this be reverted? seems like there's no diffe
988 change_info.CloseIssue() 1045 change_info.CloseIssue()
989 os.chdir(previous_cwd) 1046 os.chdir(previous_cwd)
990 return 0 1047 return 0
991 1048
992 1049
993 def CMDchange(args): 1050 def CMDchange(args):
994 """Creates or edits a changelist. 1051 """Creates or edits a changelist.
995 1052
996 Only scans the current directory and subdirectories.""" 1053 Only scans the current directory and subdirectories."""
997 if len(args) == 0: 1054 if len(args) == 0:
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
1037 description = change_info.description 1094 description = change_info.description
1038 1095
1039 other_files = GetFilesNotInCL() 1096 other_files = GetFilesNotInCL()
1040 1097
1041 # Edited files (as opposed to files with only changed properties) will have 1098 # Edited files (as opposed to files with only changed properties) will have
1042 # a letter for the first character in the status string. 1099 # a letter for the first character in the status string.
1043 file_re = re.compile(r"^[a-z].+\Z", re.IGNORECASE) 1100 file_re = re.compile(r"^[a-z].+\Z", re.IGNORECASE)
1044 affected_files = [x for x in other_files if file_re.match(x[0])] 1101 affected_files = [x for x in other_files if file_re.match(x[0])]
1045 unaffected_files = [x for x in other_files if not file_re.match(x[0])] 1102 unaffected_files = [x for x in other_files if not file_re.match(x[0])]
1046 1103
1104 suggested_reviewers = suggest_reviewers(change_info, affected_files)
1105 if suggested_reviewers:
1106 reviewers_re = re.compile(REVIEWERS_REGEX)
1107 if not any(reviewers_re.match(l) for
M-A Ruel 2011/03/15 19:11:57 if not any( reviewers_re.match(l) for l in des
1108 l in description.splitlines()):
1109 description += '\nR=' + ','.join(suggested_reviewers) + '\n'
1110
1047 separator1 = ("\n---All lines above this line become the description.\n" 1111 separator1 = ("\n---All lines above this line become the description.\n"
1048 "---Repository Root: " + change_info.GetLocalRoot() + "\n" 1112 "---Repository Root: " + change_info.GetLocalRoot() + "\n"
1049 "---Paths in this changelist (" + change_info.name + "):\n") 1113 "---Paths in this changelist (" + change_info.name + "):\n")
1050 separator2 = "\n\n---Paths modified but not in any changelist:\n\n" 1114 separator2 = "\n\n---Paths modified but not in any changelist:\n\n"
1051 text = (description + separator1 + '\n' + 1115 text = (description + separator1 + '\n' +
1052 '\n'.join([f[0] + f[1] for f in change_info.GetFiles()])) 1116 '\n'.join([f[0] + f[1] for f in change_info.GetFiles()]))
1053 1117
1054 if change_info.Exists(): 1118 if change_info.Exists():
1055 text += (separator2 + 1119 text += (separator2 +
1056 '\n'.join([f[0] + f[1] for f in affected_files]) + '\n') 1120 '\n'.join([f[0] + f[1] for f in affected_files]) + '\n')
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after
1154 else: 1218 else:
1155 print "Skipping file %s" % filename 1219 print "Skipping file %s" % filename
1156 1220
1157 print "Total errors found: %d\n" % cpplint._cpplint_state.error_count 1221 print "Total errors found: %d\n" % cpplint._cpplint_state.error_count
1158 os.chdir(previous_cwd) 1222 os.chdir(previous_cwd)
1159 return 1 1223 return 1
1160 1224
1161 1225
1162 def DoPresubmitChecks(change_info, committing, may_prompt): 1226 def DoPresubmitChecks(change_info, committing, may_prompt):
1163 """Imports presubmit, then calls presubmit.DoPresubmitChecks.""" 1227 """Imports presubmit, then calls presubmit.DoPresubmitChecks."""
1164 # Need to import here to avoid circular dependency.
1165 import presubmit_support
1166 root_presubmit = GetCachedFile('PRESUBMIT.py', use_root=True) 1228 root_presubmit = GetCachedFile('PRESUBMIT.py', use_root=True)
1167 change = presubmit_support.SvnChange(change_info.name, 1229 change = presubmit_support.SvnChange(change_info.name,
1168 change_info.description, 1230 change_info.description,
1169 change_info.GetLocalRoot(), 1231 change_info.GetLocalRoot(),
1170 change_info.GetFiles(), 1232 change_info.GetFiles(),
1171 change_info.issue, 1233 change_info.issue,
1172 change_info.patchset) 1234 change_info.patchset)
1173 output = presubmit_support.DoPresubmitChecks(change=change, 1235 output = presubmit_support.DoPresubmitChecks(change=change,
1174 committing=committing, 1236 committing=committing,
1175 verbose=False, 1237 verbose=False,
1176 output_stream=sys.stdout, 1238 output_stream=sys.stdout,
1177 input_stream=sys.stdin, 1239 input_stream=sys.stdin,
1178 default_presubmit=root_presubmit, 1240 default_presubmit=root_presubmit,
1179 may_prompt=may_prompt) 1241 may_prompt=may_prompt,
1242 tbr=False,
1243 host_url=change_info.rietveld)
1180 if not output.should_continue() and may_prompt: 1244 if not output.should_continue() and may_prompt:
1181 # TODO(dpranke): move into DoPresubmitChecks(), unify cmd line args. 1245 # TODO(dpranke): move into DoPresubmitChecks(), unify cmd line args.
1182 print "\nPresubmit errors, can't continue (use --no_presubmit to bypass)" 1246 print "\nPresubmit errors, can't continue (use --no_presubmit to bypass)"
1183 1247
1184 # TODO(dpranke): Return the output object and make use of it. 1248 return output
1185 return output.should_continue()
1186 1249
1187 1250
1188 @no_args 1251 @no_args
1189 def CMDchanges(): 1252 def CMDchanges():
1190 """Lists all the changelists and their files.""" 1253 """Lists all the changelists and their files."""
1191 for cl in GetCLs(): 1254 for cl in GetCLs():
1192 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True) 1255 change_info = ChangeInfo.Load(cl, GetRepositoryRoot(), True, True)
1193 print "\n--- Changelist " + change_info.name + ":" 1256 print "\n--- Changelist " + change_info.name + ":"
1194 for filename in change_info.GetFiles(): 1257 for filename in change_info.GetFiles():
1195 print "".join(filename) 1258 print "".join(filename)
(...skipping 203 matching lines...) Expand 10 before | Expand all | Expand 10 after
1399 if e.code != 500: 1462 if e.code != 500:
1400 raise 1463 raise
1401 print >> sys.stderr, ( 1464 print >> sys.stderr, (
1402 'AppEngine is misbehaving and returned HTTP %d, again. Keep faith ' 1465 'AppEngine is misbehaving and returned HTTP %d, again. Keep faith '
1403 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e)) 1466 'and retry or visit go/isgaeup.\n%s') % (e.code, str(e))
1404 return 1 1467 return 1
1405 1468
1406 1469
1407 if __name__ == "__main__": 1470 if __name__ == "__main__":
1408 sys.exit(main(sys.argv[1:])) 1471 sys.exit(main(sys.argv[1:]))
OLDNEW
« no previous file with comments | « no previous file | git_cl/git_cl.py » ('j') | git_cl/git_cl.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698