diff --git a/gcl.py b/gcl.py index 1f1c2c3078..cdb86a198f 100755 --- a/gcl.py +++ b/gcl.py @@ -39,7 +39,10 @@ import breakpad # pylint: disable=W0611 # gcl now depends on gclient. from scm import SVN + import gclient_utils +import owners +import presubmit_support __version__ = '1.2' @@ -70,6 +73,7 @@ FILES_CACHE = {} DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" DEFAULT_LINT_IGNORE_REGEX = r"$^" +REVIEWERS_REGEX = r'\s*R=(.+)' def CheckHomeForFile(filename): """Checks the users home dir for the existence of the given file. Returns @@ -286,7 +290,10 @@ class ChangeInfo(object): self.name = name self.issue = int(issue) self.patchset = int(patchset) - self.description = description + self._description = None + self._subject = None + self._reviewers = None + self._set_description(description) if files is None: files = [] self._files = files @@ -298,6 +305,44 @@ class ChangeInfo(object): # Set the default value. self.rietveld = GetCodeReviewSetting('CODE_REVIEW_SERVER') + def _get_description(self): + return self._description + + def _set_description(self, description): + # TODO(dpranke): Cloned from git_cl.py. These should be shared. + if not description: + self._description = description + return + + parsed_lines = [] + reviewers_re = re.compile(REVIEWERS_REGEX) + reviewers = '' + subject = '' + for l in description.splitlines(): + if not subject: + subject = l + matched_reviewers = reviewers_re.match(l) + if matched_reviewers: + reviewers = matched_reviewers.group(1) + parsed_lines.append(l) + + if len(subject) > 100: + subject = subject[:97] + '...' + + self._subject = subject + self._reviewers = reviewers + self._description = '\n'.join(parsed_lines) + + description = property(_get_description, _set_description) + + @property + def reviewers(self): + return self._reviewers + + @property + def subject(self): + return self._subject + def NeedsUpload(self): return self.needs_upload @@ -714,10 +759,16 @@ def GenerateDiff(files, root=None): def OptionallyDoPresubmitChecks(change_info, committing, args): if FilterFlag(args, "--no_presubmit") or FilterFlag(args, "--force"): - return True + return presubmit_support.PresubmitOutput() return DoPresubmitChecks(change_info, committing, True) +def suggest_reviewers(change_info, affected_files): + owners_db = owners.Database(change_info.LocalRoot(), fopen=file, + os_path=os.path) + return owners_db.reviewers_for(affected_files) + + def defer_attributes(a, b): """Copy attributes from an object (like a function) to another.""" for x in dir(a): @@ -797,7 +848,9 @@ def CMDupload(change_info, args): if not change_info.GetFiles(): print "Nothing to upload, changelist is empty." return 0 - if not OptionallyDoPresubmitChecks(change_info, False, args): + + output = OptionallyDoPresubmitChecks(change_info, False, args) + if not output.should_continue(): return 1 no_watchlists = (FilterFlag(args, "--no_watchlists") or FilterFlag(args, "--no-watchlists")) @@ -808,6 +861,13 @@ def CMDupload(change_info, args): upload_arg = ["upload.py", "-y"] upload_arg.append("--server=%s" % change_info.rietveld) + + reviewers = change_info.reviewers or output.reviewers + if (reviewers and + not any(arg.startswith('-r') or arg.startswith('--reviewer') for + arg in args)): + upload_arg.append('--reviewers=%s' % ','.join(reviewers)) + upload_arg.extend(args) desc_file = "" @@ -841,15 +901,8 @@ def CMDupload(change_info, args): if cc_list: upload_arg.append("--cc=" + cc_list) upload_arg.append("--description_file=" + desc_file + "") - if change_info.description: - subject = change_info.description[:77] - if subject.find("\r\n") != -1: - subject = subject[:subject.find("\r\n")] - if subject.find("\n") != -1: - subject = subject[:subject.find("\n")] - if len(change_info.description) > 77: - subject = subject + "..." - upload_arg.append("--message=" + subject) + if change_info.subject: + upload_arg.append("--message=" + change_info.subject) if GetCodeReviewSetting("PRIVATE") == "True": upload_arg.append("--private") @@ -981,7 +1034,7 @@ def CMDcommit(change_info, args): revision = re.compile(".*?\nCommitted revision (\d+)", re.DOTALL).match(output).group(1) viewvc_url = GetCodeReviewSetting("VIEW_VC") - change_info.description = change_info.description + '\n' + change_info.description += '\n' if viewvc_url: change_info.description += "\nCommitted: " + viewvc_url + revision change_info.CloseIssue() @@ -1043,6 +1096,13 @@ def CMDchange(args): affected_files = [x for x in other_files if file_re.match(x[0])] unaffected_files = [x for x in other_files if not file_re.match(x[0])] + suggested_reviewers = suggest_reviewers(change_info, affected_files) + if suggested_reviewers: + reviewers_re = re.compile(REVIEWERS_REGEX) + if not any( + reviewers_re.match(l) for l in description.splitlines()): + description += '\nR=' + ','.join(suggested_reviewers) + '\n' + separator1 = ("\n---All lines above this line become the description.\n" "---Repository Root: " + change_info.GetLocalRoot() + "\n" "---Paths in this changelist (" + change_info.name + "):\n") @@ -1160,8 +1220,6 @@ def CMDlint(change_info, args): def DoPresubmitChecks(change_info, committing, may_prompt): """Imports presubmit, then calls presubmit.DoPresubmitChecks.""" - # Need to import here to avoid circular dependency. - import presubmit_support root_presubmit = GetCachedFile('PRESUBMIT.py', use_root=True) change = presubmit_support.SvnChange(change_info.name, change_info.description, @@ -1175,13 +1233,14 @@ def DoPresubmitChecks(change_info, committing, may_prompt): output_stream=sys.stdout, input_stream=sys.stdin, default_presubmit=root_presubmit, - may_prompt=may_prompt) + may_prompt=may_prompt, + tbr=False, + host_url=change_info.rietveld) if not output.should_continue() and may_prompt: # TODO(dpranke): move into DoPresubmitChecks(), unify cmd line args. print "\nPresubmit errors, can't continue (use --no_presubmit to bypass)" - # TODO(dpranke): Return the output object and make use of it. - return output.should_continue() + return output @no_args diff --git a/tests/gcl_unittest.py b/tests/gcl_unittest.py index c532a4e913..a88d9766f7 100755 --- a/tests/gcl_unittest.py +++ b/tests/gcl_unittest.py @@ -12,6 +12,7 @@ from super_mox import mox, SuperMoxTestBase import gcl +import presubmit_support class GclTestsBase(SuperMoxTestBase): @@ -56,13 +57,14 @@ class GclUnittest(GclTestsBase): 'GetCodeReviewSetting', 'GetEditor', 'GetFilesNotInCL', 'GetInfoDir', 'GetModifiedFiles', 'GetRepositoryRoot', 'ListFiles', 'LoadChangelistInfoForMultiple', 'MISSING_TEST_MSG', - 'OptionallyDoPresubmitChecks', 'REPOSITORY_ROOT', + 'OptionallyDoPresubmitChecks', 'REPOSITORY_ROOT', 'REVIEWERS_REGEX', 'RunShell', 'RunShellWithReturnCode', 'SVN', 'TryChange', 'UnknownFiles', 'Warn', 'attrs', 'breakpad', 'defer_attributes', 'gclient_utils', 'getpass', 'json', 'main', 'need_change', 'need_change_and_args', 'no_args', - 'optparse', 'os', 'random', 're', 'string', 'subprocess', 'sys', - 'tempfile', 'time', 'upload', 'urllib2', + 'optparse', 'os', 'owners', 'presubmit_support', 'random', 're', + 'string', 'subprocess', 'suggest_reviewers', 'sys', 'tempfile', 'time', + 'upload', 'urllib2', ] # If this test fails, you should add the relevant test. self.compareMembers(gcl, members) @@ -149,7 +151,8 @@ class ChangeInfoUnittest(GclTestsBase): 'NeedsUpload', 'PrimeLint', 'Save', 'SendToRietveld', 'UpdateRietveldDescription', 'description', 'issue', 'name', - 'needs_upload', 'patch', 'patchset', 'rietveld', + 'needs_upload', 'patch', 'patchset', 'reviewers', 'rietveld', + 'subject' ] # If this test fails, you should add the relevant test. self.compareMembers( @@ -258,7 +261,8 @@ class CMDuploadUnittest(GclTestsBase): change_info.patch = None change_info.rietveld = 'my_server' files = [item[1] for item in change_info.files] - gcl.DoPresubmitChecks(change_info, False, True).AndReturn(True) + output = presubmit_support.PresubmitOutput() + gcl.DoPresubmitChecks(change_info, False, True).AndReturn(output) #gcl.GetCodeReviewSetting('CODE_REVIEW_SERVER').AndReturn('my_server') gcl.os.getcwd().AndReturn('somewhere') change_info.GetFiles().AndReturn(change_info.files) @@ -293,7 +297,8 @@ class CMDuploadUnittest(GclTestsBase): self.fake_root_dir, 'my_server') self.mox.StubOutWithMock(change_info, 'Save') change_info.Save() - gcl.DoPresubmitChecks(change_info, False, True).AndReturn(True) + output = presubmit_support.PresubmitOutput() + gcl.DoPresubmitChecks(change_info, False, True).AndReturn(output) gcl.tempfile.mkstemp(text=True).AndReturn((42, 'descfile')) gcl.os.write(42, change_info.description) gcl.os.close(42) @@ -327,7 +332,8 @@ class CMDuploadUnittest(GclTestsBase): self.fake_root_dir, 'my_server') self.mox.StubOutWithMock(change_info, 'Save') change_info.Save() - gcl.DoPresubmitChecks(change_info, False, True).AndReturn(True) + output = presubmit_support.PresubmitOutput() + gcl.DoPresubmitChecks(change_info, False, True).AndReturn(output) gcl.tempfile.mkstemp(text=True).AndReturn((42, 'descfile')) gcl.os.write(42, change_info.description) gcl.os.close(42)