| Index: git_cl/git_cl.py
|
| diff --git a/git_cl/git_cl.py b/git_cl/git_cl.py
|
| index ec3bd9770f0fba980cc42c68618548075de85467..c729f9b1cf0c6b2005f6522599576e3710e54b28 100644
|
| --- a/git_cl/git_cl.py
|
| +++ b/git_cl/git_cl.py
|
| @@ -54,7 +54,8 @@ def Popen(cmd, **kwargs):
|
|
|
|
|
| def RunCommand(cmd, error_ok=False, error_message=None,
|
| - redirect_stdout=True, swallow_stderr=False, **kwargs):
|
| + redirect_stdout=True, swallow_stderr=False,
|
| + tee_filter=None, **kwargs):
|
| if redirect_stdout:
|
| stdout = subprocess.PIPE
|
| else:
|
| @@ -64,7 +65,18 @@ def RunCommand(cmd, error_ok=False, error_message=None,
|
| else:
|
| stderr = None
|
| proc = Popen(cmd, stdout=stdout, stderr=stderr, **kwargs)
|
| - output = proc.communicate()[0]
|
| + if tee_filter:
|
| + output = ''
|
| + while True:
|
| + line = proc.stdout.readline()
|
| + if line == '' and proc.poll() != None:
|
| + break
|
| + if not tee_filter(line):
|
| + print line
|
| + output += line + '\n'
|
| + else:
|
| + output = proc.communicate()[0]
|
| +
|
| if not error_ok and proc.returncode != 0:
|
| DieWithError('Command "%s" failed.\n' % (' '.join(cmd)) +
|
| (error_message or output or ''))
|
| @@ -479,6 +491,76 @@ def GetCodereviewSettingsInteractively():
|
| # svn-based hackery.
|
|
|
|
|
| +class HookResults(object):
|
| + """Contains the parsed output of the presubmit hooks."""
|
| + def __init__(self, output_from_hooks=None):
|
| + self.reviewers = []
|
| + self.output = None
|
| + self._ParseOutputFromHooks(output_from_hooks)
|
| +
|
| + def _ParseOutputFromHooks(self, output_from_hooks):
|
| + if not output_from_hooks:
|
| + return
|
| + lines = []
|
| + reviewers = []
|
| + reviewer_regexp = re.compile('ADD: R=(.+)')
|
| + for l in output_from_hooks.splitlines():
|
| + m = reviewer_regexp.match(l)
|
| + if m:
|
| + reviewers.extend(m.group(1).split(','))
|
| + else:
|
| + lines.append(l)
|
| + self.output = '\n'.join(lines)
|
| + self.reviewers = ','.join(reviewers)
|
| +
|
| +
|
| +class ChangeDescription(object):
|
| + """Contains a parsed form of the change description."""
|
| + def __init__(self, subject, log_desc, reviewers):
|
| + self.subject = subject
|
| + self.log_desc = log_desc
|
| + self.reviewers = reviewers
|
| + self.description = self.log_desc
|
| +
|
| + def Update(self):
|
| + initial_text = """# Enter a description of the change.
|
| +# This will displayed on the codereview site.
|
| +# The first line will also be used as the subject of the review.
|
| +"""
|
| + initial_text += self.description
|
| + if 'R=' not in self.description and self.reviewers:
|
| + initial_text += '\nR=' + self.reviewers
|
| + if 'BUG=' not in self.description:
|
| + initial_text += '\nBUG='
|
| + if 'TEST=' not in self.description:
|
| + initial_text += '\nTEST='
|
| + self._ParseDescription(UserEditedLog(initial_text))
|
| +
|
| + def _ParseDescription(self, description):
|
| + if not description:
|
| + self.description = description
|
| + return
|
| +
|
| + parsed_lines = []
|
| + reviewers_regexp = re.compile('\s*R=(.+)')
|
| + reviewers = ''
|
| + subject = ''
|
| + for l in description.splitlines():
|
| + if not subject:
|
| + subject = l
|
| + matched_reviewers = reviewers_regexp.match(l)
|
| + if matched_reviewers:
|
| + reviewers = matched_reviewers.group(1)
|
| + parsed_lines.append(l)
|
| +
|
| + self.description = '\n'.join(parsed_lines) + '\n'
|
| + self.subject = subject
|
| + self.reviewers = reviewers
|
| +
|
| + def IsEmpty(self):
|
| + return not self.description
|
| +
|
| +
|
| def FindCodereviewSettingsFile(filename='codereview.settings'):
|
| """Finds the given file starting in the cwd and going up.
|
|
|
| @@ -669,13 +751,19 @@ def UserEditedLog(starting_text):
|
| return stripcomment_re.sub('', text).strip()
|
|
|
|
|
| -def RunHook(hook, upstream_branch, error_ok=False):
|
| +def RunHook(hook, upstream_branch, rietveld_server, tbr=False,
|
| + error_ok=False):
|
| """Run a given hook if it exists. By default, we fail on errors."""
|
| hook = '%s/%s' % (settings.GetRoot(), hook)
|
| if not os.path.exists(hook):
|
| - return
|
| - return RunCommand([hook, upstream_branch], error_ok=error_ok,
|
| - redirect_stdout=False)
|
| + return HookResults()
|
| + cmd = [hook]
|
| + if tbr:
|
| + cmd.append('--tbr')
|
| + cmd.extend(['--host-url=' + rietveld_server, upstream_branch])
|
| + output = RunCommand(cmd, error_ok=error_ok, redirect_stdout=True,
|
| + tee_filter=lambda l: l.startswith('ADD:'))
|
| + return HookResults(output)
|
|
|
|
|
| def CMDpresubmit(parser, args):
|
| @@ -691,20 +779,23 @@ def CMDpresubmit(parser, args):
|
| print 'Cannot presubmit with a dirty tree. You must commit locally first.'
|
| return 1
|
|
|
| + cl = Changelist()
|
| if args:
|
| base_branch = args[0]
|
| else:
|
| # Default to diffing against the "upstream" branch.
|
| - base_branch = Changelist().GetUpstreamBranch()
|
| + base_branch = cl.GetUpstreamBranch()
|
|
|
| if options.upload:
|
| print '*** Presubmit checks for UPLOAD would report: ***'
|
| return not RunHook(PREUPLOAD_HOOK, upstream_branch=base_branch,
|
| - error_ok=True)
|
| + rietveld_server=cl.GetRietveldServer(), tbr=False,
|
| + error_ok=True).output
|
| else:
|
| print '*** Presubmit checks for DCOMMIT would report: ***'
|
| return not RunHook(PREDCOMMIT_HOOK, upstream_branch=base_branch,
|
| - error_ok=True)
|
| + rietveld_server=cl.GetRietveldServer(), tbr=options.tbr,
|
| + error_ok=True).output
|
|
|
|
|
| @usage('[args to "git diff"]')
|
| @@ -743,7 +834,14 @@ def CMDupload(parser, args):
|
| args = [base_branch + "..."]
|
|
|
| if not options.bypass_hooks:
|
| - RunHook(PREUPLOAD_HOOK, upstream_branch=base_branch, error_ok=False)
|
| + hook_results = RunHook(PREUPLOAD_HOOK, upstream_branch=base_branch,
|
| + rietveld_server=cl.GetRietveldServer(), tbr=False,
|
| + error_ok=False)
|
| + else:
|
| + hook_results = HookResults()
|
| +
|
| + if not options.reviewers and hook_results.reviewers:
|
| + options.reviewers = hook_results.reviewers
|
|
|
| # --no-ext-diff is broken in some versions of Git, so try to work around
|
| # this by overriding the environment (but there is still a problem if the
|
| @@ -756,8 +854,6 @@ def CMDupload(parser, args):
|
|
|
| upload_args = ['--assume_yes'] # Don't ask about untracked files.
|
| upload_args.extend(['--server', cl.GetRietveldServer()])
|
| - if options.reviewers:
|
| - upload_args.extend(['--reviewers', options.reviewers])
|
| if options.emulate_svn_auto_props:
|
| upload_args.append('--emulate_svn_auto_props')
|
| if options.send_mail:
|
| @@ -778,28 +874,19 @@ def CMDupload(parser, args):
|
| "Adding patch to that issue." % cl.GetIssue())
|
| else:
|
| log_desc = CreateDescriptionFromLog(args)
|
| - if options.from_logs:
|
| - # Uses logs as description and message as subject.
|
| - subject = options.message
|
| - change_desc = subject + '\n\n' + log_desc
|
| - else:
|
| - initial_text = """# Enter a description of the change.
|
| -# This will displayed on the codereview site.
|
| -# The first line will also be used as the subject of the review.
|
| -"""
|
| - if 'BUG=' not in log_desc:
|
| - log_desc += '\nBUG='
|
| - if 'TEST=' not in log_desc:
|
| - log_desc += '\nTEST='
|
| - change_desc = UserEditedLog(initial_text + log_desc)
|
| - subject = ''
|
| - if change_desc:
|
| - subject = change_desc.splitlines()[0]
|
| - if not change_desc:
|
| + change_desc = ChangeDescription(options.message, log_desc,
|
| + options.reviewers)
|
| + if not options.from_logs:
|
| + change_desc.Update()
|
| +
|
| + if change_desc.IsEmpty():
|
| print "Description is empty; aborting."
|
| return 1
|
| - upload_args.extend(['--message', subject])
|
| - upload_args.extend(['--description', change_desc])
|
| +
|
| + upload_args.extend(['--message', change_desc.subject])
|
| + upload_args.extend(['--description', change_desc.description])
|
| + if change_desc.reviewers:
|
| + upload_args.extend(['--reviewers', change_desc.reviewers])
|
| cc = ','.join(filter(None, (settings.GetCCList(), options.cc)))
|
| if cc:
|
| upload_args.extend(['--cc', cc])
|
| @@ -831,7 +918,7 @@ def CMDupload(parser, args):
|
| print '\nGot exception while uploading -- saving description to %s\n' \
|
| % backup_path
|
| backup_file = open(backup_path, 'w')
|
| - backup_file.write(change_desc)
|
| + backup_file.write(change_desc.description)
|
| backup_file.close()
|
| raise
|
|
|
| @@ -899,8 +986,10 @@ def SendUpstream(parser, args, cmd):
|
| 'before attempting to %s.' % (base_branch, cmd))
|
| return 1
|
|
|
| - if not options.force and not options.bypass_hooks:
|
| - RunHook(PREDCOMMIT_HOOK, upstream_branch=base_branch, error_ok=False)
|
| + if not options.bypass_hooks:
|
| + RunHook(PREDCOMMIT_HOOK, upstream_branch=base_branch,
|
| + rietveld_server=cl.GetRietveldServer(), tbr=options.tbr,
|
| + error_ok=False)
|
|
|
| if cmd == 'dcommit':
|
| # Check the tree status if the tree status URL is set.
|
| @@ -940,6 +1029,7 @@ def SendUpstream(parser, args, cmd):
|
| if cl.GetIssue():
|
| description = cl.GetDescription()
|
|
|
| + # TODO(dpranke): Update to use ChangeDescription object.
|
| if not description:
|
| description = """# Enter a description of the change.
|
| # This will be used as the change log for the commit.
|
| @@ -1031,8 +1121,9 @@ def SendUpstream(parser, args, cmd):
|
| if retcode == 0:
|
| hook = POSTUPSTREAM_HOOK_PATTERN % cmd
|
| if os.path.isfile(hook):
|
| - RunHook(hook, upstream_branch=base_branch, error_ok=True)
|
| -
|
| + RunHook(hook, upstream_branch=base_branch,
|
| + rietveld_server=cl.GetRietveldServer(), tbr=options.tbr,
|
| + error_ok=True)
|
| return 0
|
|
|
|
|
|
|