Commit 6dadfbfc authored by Aaron Gable's avatar Aaron Gable Committed by Commit Bot

git-cl-upload: Set all reviewers and ccs in a single batch api call

The old system had two faults:
* It set reviewers and ccs via different mechanisms, which is confusing
* It set CCs with a single call for each, resulting in N separate emails,
  with each email going to the uploader and all reviewers and all previous
  CCs.

This new system just collects all reviewers and CCs, and sets them
in a single call. That call will fail if *any* of the individual
reviewers or ccs fail, so it also parses the response and retries with
only the ones which would have succeeded on their own. If that second
call fails, or the first fails in an unexpected way, it raises an
exception like normal

Bug: 710028
Change-Id: I1be508487a41f0b68f9c41908229b8f5342830a3
Reviewed-on: https://chromium-review.googlesource.com/479712
Commit-Queue: Aaron Gable <agable@chromium.org>
Reviewed-by: 's avatarAndrii Shyshkalov <tandrii@chromium.org>
parent 4e8058a4
...@@ -653,32 +653,44 @@ def GetReview(host, change, revision): ...@@ -653,32 +653,44 @@ def GetReview(host, change, revision):
return ReadHttpJsonResponse(CreateHttpConn(host, path)) return ReadHttpJsonResponse(CreateHttpConn(host, path))
def AddReviewers(host, change, add=None, is_reviewer=True, notify=True): def AddReviewers(host, change, reviewers=None, ccs=None, notify=True,
accept_statuses=frozenset([200, 400, 422])):
"""Add reviewers to a change.""" """Add reviewers to a change."""
errors = None if not reviewers and not ccs:
if not add:
return None return None
if isinstance(add, basestring): reviewers = frozenset(reviewers or [])
add = (add,) ccs = frozenset(ccs or [])
path = 'changes/%s/reviewers' % change path = 'changes/%s/revisions/current/review' % change
for r in add:
state = 'REVIEWER' if is_reviewer else 'CC' body = {
notify = 'ALL' if notify else 'NONE' 'reviewers': [],
body = { 'notify': 'ALL' if notify else 'NONE',
'reviewer': r, }
'state': state, for r in sorted(reviewers | ccs):
'notify': notify, state = 'REVIEWER' if r in reviewers else 'CC'
} body['reviewers'].append({
try: 'reviewer': r,
conn = CreateHttpConn(host, path, reqtype='POST', body=body) 'state': state,
_ = ReadHttpJsonResponse(conn) 'notify': 'NONE', # We handled `notify` argument above.
except GerritError as e: })
if e.http_status == 422: # "Unprocessable Entity"
LOGGER.warn('Note: "%s" not added as a %s' % (r, state.lower())) conn = CreateHttpConn(host, path, reqtype='POST', body=body)
errors = True # Gerrit will return 400 if one or more of the requested reviewers are
else: # unprocessable. We read the response object to see which were rejected,
raise # warn about them, and retry with the remainder.
return errors resp = ReadHttpJsonResponse(conn, accept_statuses=accept_statuses)
errored = set()
for result in resp.get('reviewers', {}).itervalues():
r = result.get('input')
state = 'REVIEWER' if r in reviewers else 'CC'
if result.get('error'):
errored.add(r)
LOGGER.warn('Note: "%s" not added as a %s' % (r, state.lower()))
if errored:
# Try again, adding only those that didn't fail, and only accepting 200.
AddReviewers(host, change, reviewers=(reviewers-errored),
ccs=(ccs-errored), notify=notify, accept_statuses=[200])
def RemoveReviewers(host, change, remove=None): def RemoveReviewers(host, change, remove=None):
......
...@@ -2937,6 +2937,14 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase): ...@@ -2937,6 +2937,14 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
'single commit.') 'single commit.')
confirm_or_exit(action='upload') confirm_or_exit(action='upload')
if options.reviewers or options.tbrs or options.add_owners_to:
change_desc.update_reviewers(options.reviewers, options.tbrs,
options.add_owners_to, change)
if options.send_mail:
if not change_desc.get_reviewers():
DieWithError('Must specify reviewers to send email.', change_desc)
# Extra options that can be specified at push time. Doc: # Extra options that can be specified at push time. Doc:
# https://gerrit-review.googlesource.com/Documentation/user-upload.html # https://gerrit-review.googlesource.com/Documentation/user-upload.html
refspec_opts = [] refspec_opts = []
...@@ -2960,16 +2968,9 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase): ...@@ -2960,16 +2968,9 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
# reverse on its side. # reverse on its side.
refspec_opts.append('m=' + title.replace(' ', '_')) refspec_opts.append('m=' + title.replace(' ', '_'))
if options.send_mail: # Never notify now because no one is on the review. Notify when we add
if not change_desc.get_reviewers(): # reviewers and CCs below.
DieWithError('Must specify reviewers to send email.', change_desc) refspec_opts.append('notify=NONE')
refspec_opts.append('notify=ALL')
else:
refspec_opts.append('notify=NONE')
reviewers = change_desc.get_reviewers()
if reviewers:
refspec_opts.extend('r=' + email.strip() for email in reviewers)
if options.private: if options.private:
refspec_opts.append('draft') refspec_opts.append('draft')
...@@ -3013,6 +3014,8 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase): ...@@ -3013,6 +3014,8 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
self.SetIssue(change_numbers[0]) self.SetIssue(change_numbers[0])
self._GitSetBranchConfigValue('gerritsquashhash', ref_to_push) self._GitSetBranchConfigValue('gerritsquashhash', ref_to_push)
reviewers = sorted(change_desc.get_reviewers())
# Add cc's from the CC_LIST and --cc flag (if any). # Add cc's from the CC_LIST and --cc flag (if any).
cc = self.GetCCList().split(',') cc = self.GetCCList().split(',')
if options.cc: if options.cc:
...@@ -3020,10 +3023,11 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase): ...@@ -3020,10 +3023,11 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase):
cc = filter(None, [email.strip() for email in cc]) cc = filter(None, [email.strip() for email in cc])
if change_desc.get_cced(): if change_desc.get_cced():
cc.extend(change_desc.get_cced()) cc.extend(change_desc.get_cced())
if cc:
gerrit_util.AddReviewers( gerrit_util.AddReviewers(
self._GetGerritHost(), self.GetIssue(), cc, self._GetGerritHost(), self.GetIssue(), reviewers, cc,
is_reviewer=False, notify=bool(options.send_mail)) notify=bool(options.send_mail))
return 0 return 0
def _ComputeParent(self, remote, upstream_branch, custom_cl_base, force, def _ComputeParent(self, remote, upstream_branch, custom_cl_base, force,
......
...@@ -594,8 +594,8 @@ class TestGitCl(TestCase): ...@@ -594,8 +594,8 @@ class TestGitCl(TestCase):
lambda *args, **kwargs: self._mocked_call( lambda *args, **kwargs: self._mocked_call(
'GetChangeDetail', *args, **kwargs)) 'GetChangeDetail', *args, **kwargs))
self.mock(git_cl.gerrit_util, 'AddReviewers', self.mock(git_cl.gerrit_util, 'AddReviewers',
lambda h, i, add, is_reviewer, notify: self._mocked_call( lambda h, i, reviewers, ccs, notify: self._mocked_call(
'AddReviewers', h, i, add, is_reviewer, notify)) 'AddReviewers', h, i, reviewers, ccs, notify))
self.mock(git_cl.gerrit_util.GceAuthenticator, 'is_gce', self.mock(git_cl.gerrit_util.GceAuthenticator, 'is_gce',
classmethod(lambda _: False)) classmethod(lambda _: False))
self.mock(git_cl, 'DieWithError', self.mock(git_cl, 'DieWithError',
...@@ -1515,16 +1515,12 @@ class TestGitCl(TestCase): ...@@ -1515,16 +1515,12 @@ class TestGitCl(TestCase):
else: else:
ref_suffix = '%m=' + title ref_suffix = '%m=' + title
notify_suffix = 'notify=%s' % ('ALL' if notify else 'NONE') notify_suffix = 'notify=NONE'
if ref_suffix: if ref_suffix:
ref_suffix += ',' + notify_suffix ref_suffix += ',' + notify_suffix
else: else:
ref_suffix = '%' + notify_suffix ref_suffix = '%' + notify_suffix
if reviewers:
ref_suffix += ',' + ','.join('r=%s' % email
for email in sorted(reviewers))
if git_mirror is None: if git_mirror is None:
calls += [ calls += [
((['git', 'config', 'remote.origin.url'],), ((['git', 'config', 'remote.origin.url'],),
...@@ -1571,8 +1567,8 @@ class TestGitCl(TestCase): ...@@ -1571,8 +1567,8 @@ class TestGitCl(TestCase):
calls += [ calls += [
((['git', 'config', 'rietveld.cc'],), ''), ((['git', 'config', 'rietveld.cc'],), ''),
(('AddReviewers', 'chromium-review.googlesource.com', (('AddReviewers', 'chromium-review.googlesource.com',
123456 if squash else None, 123456 if squash else None, sorted(reviewers),
['joe@example.com'] + cc, False, notify), ''), ['joe@example.com'] + cc, notify), ''),
] ]
calls += cls._git_post_upload_calls() calls += cls._git_post_upload_calls()
return calls return calls
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment