Example #1
0
    def as_html(self):
        commit_id = self.commit_data.extra_data.get(COMMIT_ID_KEY)

        if is_parent(self.review_request_details, self.commit_data):
            user = self.request.user
            parent = get_parent_rr(
                self.review_request_details.get_review_request(),
                self.commit_data)
            parent_details = parent.get_draft() or parent
            children = [
                child for child in gen_child_rrs(parent_details, user=user)
                if child.is_accessible_by(user)]

            commit_data = fetch_commit_data(children[-1])
            commit_id = commit_data.extra_data.get(COMMIT_ID_KEY)

        review_request = self.review_request_details.get_review_request()
        repo_path = review_request.repository.path

        if not commit_id:
            logger.error('No commit_id for review request: %d' % (
                review_request.id))
            return ''

        return get_template('mozreview/hg-pull.html').render(Context({
                'commit_id': commit_id,
                'repo_path': repo_path,
        }))
Example #2
0
def _close_child_review_requests(user,
                                 review_request,
                                 status,
                                 child_close_description,
                                 commit_data=None):
    """Closes all child review requests for a squashed review request."""
    commit_data = fetch_commit_data(review_request, commit_data)
    # At the point of closing, it's possible that if this review
    # request was never published, that most of the fields are empty
    # (See https://code.google.com/p/reviewboard/issues/detail?id=3465).
    # Luckily, the extra_data is still around, and more luckily, it's
    # not exposed in the UI for user-meddling. We can find all of the
    # child review requests via extra_data.p2rb.commits.
    for child in gen_child_rrs(review_request, commit_data=commit_data):
        child.close(status, user=user, description=child_close_description)

    # We want to discard any review requests that this squashed review
    # request never got to publish, so were never part of its "commits"
    # list.
    for child in gen_rrs_by_extra_data_key(review_request,
                                           UNPUBLISHED_KEY,
                                           commit_data=commit_data):
        child.close(ReviewRequest.DISCARDED,
                    user=user,
                    description=NEVER_USED_DESCRIPTION)

    commit_data.extra_data[UNPUBLISHED_KEY] = '[]'
    commit_data.extra_data[DISCARD_ON_PUBLISH_KEY] = '[]'
    commit_data.save(update_fields=['extra_data'])
    def get(self, request, *args, **kwargs):
        parent_rrid = kwargs[self.uri_object_key]
        try:
            parent_review_request = ReviewRequest.objects.get(id=parent_rrid)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        if not self.has_access_permissions(request, parent_review_request,
                                           *args, **kwargs):
            return self.get_no_access_error(request, parent_review_request,
                                            *args, **kwargs)

        if not is_parent(parent_review_request):
            return NOT_PARENT

        if not parent_review_request.public:
            # Review request has never been published.
            return DOES_NOT_EXIST

        families = self._sort_families(request, [parent_review_request])
        self._sort_families(request, gen_child_rrs(parent_review_request),
                            families=families)

        # FIXME: The returned data should actually be a dict, with keys
        # 'stat' and self.item_result_key mapped to 'ok' and the
        # family-summary dict, respectively, to match the standard Review
        # Board web API format.
        # However, the Bugzilla extension uses the existing, nonstandard
        # return value, so we have to wait until it is fetching review
        # requests by bug before fixing this.

        return 200, self._summarize_families(request, families)[0]
    def as_html(self):
        user = self.request.user
        parent = get_parent_rr(self.review_request_details.get_review_request())
        parent_details = parent.get_draft(user) or parent

        # If a user can view the parent draft they should also have
        # permission to view every child. We check if the child is
        # accessible anyways in case it has been restricted for other
        # reasons.
        children_details = [
            child for child in gen_child_rrs(parent_details, user=user)
            if child.is_accessible_by(user)]

        autoland_requests = AutolandRequest.objects.filter(
            review_request_id=parent.id).order_by('-autoland_id')

        repo_urls = set()
        latest_autoland_requests = []


        # We would like to fetch the latest AutolandRequest for each
        # different repository.
        for request in autoland_requests:
            if request.repository_url in repo_urls:
                continue

            repo_urls.add(request.repository_url)
            latest_autoland_requests.append(request)

        return get_template('mozreview/commits.html').render(Context({
            'review_request_details': self.review_request_details,
            'parent_details': parent_details,
            'children_details': children_details,
            'latest_autoland_requests': latest_autoland_requests,
        }))
    def as_html(self):
        commit_id = self.commit_data.extra_data.get(COMMIT_ID_KEY)

        if is_parent(self.review_request_details, self.commit_data):
            user = self.request.user
            parent = get_parent_rr(
                self.review_request_details.get_review_request(),
                self.commit_data)
            parent_details = parent.get_draft() or parent
            children = [
                child for child in gen_child_rrs(parent_details, user=user)
                if child.is_accessible_by(user)]

            commit_data = fetch_commit_data(children[-1])
            commit_id = commit_data.extra_data.get(COMMIT_ID_KEY)

        review_request = self.review_request_details.get_review_request()
        repo_path = review_request.repository.path

        if not commit_id:
            logger.error('No commit_id for review request: %d' % (
                review_request.id))
            return ''

        return get_template('mozreview/hg-pull.html').render(Context({
                'commit_id': commit_id,
                'repo_path': repo_path,
        }))
Example #6
0
    def get(self, request, *args, **kwargs):
        parent_rrid = kwargs[self.uri_object_key]
        try:
            parent_review_request = ReviewRequest.objects.get(id=parent_rrid)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        if not self.has_access_permissions(request, parent_review_request,
                                           *args, **kwargs):
            return self.get_no_access_error(request, parent_review_request,
                                            *args, **kwargs)

        if not is_parent(parent_review_request):
            return NOT_PARENT

        if not parent_review_request.public:
            # Review request has never been published.
            return DOES_NOT_EXIST

        families = self._sort_families(request, [parent_review_request])
        self._sort_families(request, gen_child_rrs(parent_review_request),
                            families=families)

        # FIXME: The returned data should actually be a dict, with keys
        # 'stat' and self.item_result_key mapped to 'ok' and the
        # family-summary dict, respectively, to match the standard Review
        # Board web API format.
        # However, the Bugzilla extension uses the existing, nonstandard
        # return value, so we have to wait until it is fetching review
        # requests by bug before fixing this.

        return 200, self._summarize_families(request, families)[0]
def close_child_review_requests(user, review_request, status,
                                  child_close_description):
    """Closes all child review requests for a squashed review request."""
    # At the point of closing, it's possible that if this review
    # request was never published, that most of the fields are empty
    # (See https://code.google.com/p/reviewboard/issues/detail?id=3465).
    # Luckily, the extra_data is still around, and more luckily, it's
    # not exposed in the UI for user-meddling. We can find all of the
    # child review requests via extra_data.p2rb.commits.
    for child in gen_child_rrs(review_request):
        child.close(status,
                    user=user,
                    description=child_close_description)

    # We want to discard any review requests that this squashed review
    # request never got to publish, so were never part of its "commits"
    # list.
    for child in gen_rrs_by_extra_data_key(review_request,
                                           UNPUBLISHED_RRIDS_KEY):
        child.close(ReviewRequest.DISCARDED,
                    user=user,
                    description=NEVER_USED_DESCRIPTION)

    review_request.extra_data['p2rb.unpublished_rids'] = '[]'
    review_request.extra_data['p2rb.discard_on_publish_rids'] = '[]'
    review_request.save()
def get_commit_table_context(request, review_request_details):
    """Get the data needed to display the commits table.

    Information provided includes the parent and child review requests,
    as well as autoland information.
    """
    commit_data = fetch_commit_data(review_request_details)

    user = request.user
    parent = get_parent_rr(review_request_details.get_review_request(), commit_data=commit_data)
    parent_details = parent.get_draft(user) or parent

    # If a user can view the parent draft they should also have
    # permission to view every child. We check if the child is
    # accessible anyways in case it has been restricted for other
    # reasons.
    children_details = [
        child for child in gen_child_rrs(parent_details, user=user)
        if child.is_accessible_by(user)]
    n_children = len(children_details)
    current_child_num = prev_child = next_child = None

    if not is_parent(review_request_details, commit_data=commit_data):
        cur_index = children_details.index(review_request_details)
        current_child_num = cur_index + 1
        next_child = (children_details[cur_index + 1]
                      if cur_index + 1 < n_children else None)
        prev_child = (children_details[cur_index - 1]
                      if cur_index - 1 >= 0 else None)

    latest_autoland_requests = []
    try_syntax = ''
    repo_urls = set()
    autoland_requests = AutolandRequest.objects.filter(
        review_request_id=parent.id).order_by('-autoland_id')

    # We would like to fetch the latest AutolandRequest for each
    # different repository.
    for land_request in autoland_requests:
        if land_request.repository_url in repo_urls:
            continue

        repo_urls.add(land_request.repository_url)
        latest_autoland_requests.append(land_request)
        try_syntax = try_syntax or land_request.extra_data.get('try_syntax', '')

    return {
        'review_request_details': review_request_details,
        'parent_details': parent_details,
        'children_details': children_details,
        'num_children': n_children,
        'current_child_num': current_child_num,
        'next_child': next_child,
        'prev_child': prev_child,
        'latest_autoland_requests': latest_autoland_requests,
        'user': user,
        'try_syntax': try_syntax,
    }
Example #9
0
    def as_html(self):
        user = self.request.user

        commit_data = fetch_commit_data(self.review_request_details)
        commit_id = commit_data.get_for(self.review_request_details,
                                        COMMIT_ID_KEY)

        review_request = self.review_request_details.get_review_request()
        parent = get_parent_rr(review_request)
        parent_details = parent.get_draft(user) or parent

        author = commit_data.extra_data.get(AUTHOR_KEY, None)

        # If a user can view the parent draft they should also have
        # permission to view every child. We check if the child is
        # accessible anyways in case it has been restricted for other
        # reasons.
        children_details = [
            child for child in gen_child_rrs(parent_details, user=user)
            if child.is_accessible_by(user)
        ]

        # Generate the import and pull input field contents
        import_text = pull_text = ""
        repo_path = review_request.repository.path

        if commit_id:
            import_text = "hg import %s/rev/%s" % (repo_path, commit_id)

        last_child_commit_id = commit_id
        if is_parent(self.review_request_details, commit_data=commit_data):
            last_child_commit_data = fetch_commit_data(children_details[-1])
            last_child_commit_id = (
                last_child_commit_data.extra_data.get(COMMIT_ID_KEY))

        pull_text = "hg pull -r %s %s" % (last_child_commit_id, repo_path)

        # Get just the extended commit message details for display
        commit_message_detail = "\n".join(
            self.review_request_details.description.splitlines()[1:]).strip()

        return get_template('mozreview/commit-main.html').render(
            Context({
                'review_request_details': self.review_request_details,
                'parent_details': parent_details,
                'user': user,
                'author': author,
                'pull_text': pull_text,
                'import_text': import_text,
                'commit_message_detail': commit_message_detail,
            }))
def on_review_request_reopened(user, review_request, **kwargs):
    if not is_parent(review_request):
        return

    commit_data = fetch_commit_data(review_request)
    identifier = commit_data.extra_data[IDENTIFIER_KEY]

    # If we're reviving a squashed review request that was discarded, it means
    # we're going to want to restore the commit ID field back, since we remove
    # it on discarding. This might be a problem if there's already a review
    # request with the same commit ID somewhere on Review Board, since commit
    # IDs are unique.
    #
    # When this signal fires, the state of the review request has already
    # changed, so we query for a review request with the same commit ID that is
    # not equal to the revived review request.
    try:
        preexisting_review_request = ReviewRequest.objects.get(
            commit_id=identifier, repository=review_request.repository)
        if preexisting_review_request != review_request:
            logger.error(
                'Could not revive review request with ID %s because its '
                'commit id (%s) is already being used by a review request '
                'with ID %s.' % (
                    review_request.id,
                    identifier,
                    preexisting_review_request.id))
            # TODO: We need Review Board to recognize exceptions in these
            # signal handlers so that the UI can print out a useful message.
            raise Exception(
                'Revive failed because a review request with commit ID %s '
                'already exists.' % identifier)
    except ReviewRequest.DoesNotExist:
        # Great! This is a success case.
        pass

    for child in gen_child_rrs(review_request):
        child.reopen(user=user)

    # If the review request had been discarded, then the commit ID would
    # have been cleared out. If the review request had been submitted,
    # this is a no-op, since the commit ID would have been there already.
    review_request.commit = identifier
    review_request.save()

    # If the review request has a draft, we have to set the commit ID there as
    # well, otherwise it'll get overwritten on publish.
    draft = review_request.get_draft(user)
    if draft:
        draft.commit = identifier
        draft.save()
Example #11
0
def commits_summary_table_fragment(request, parent_id=None, child_id=None):
    """Return the #mozreview-child-requests table."""

    # Load the parent.

    try:
        parent_request = ReviewRequest.objects.get(id=parent_id)
    except ReviewRequest.DoesNotExist:
        return HttpResponseNotFound('Parent Not Found')
    if not parent_request.is_accessible_by(request.user):
        return HttpResponseNotAllowed('Permission denied')

    commit_data = fetch_commit_data(parent_request)

    # Sanity check parent.

    if not is_parent(parent_request, commit_data):
        return HttpResponseNotAllowed('Invalid parent')
    if COMMITS_KEY not in commit_data.extra_data:
        logging.error('Parent review request %s missing COMMITS_KEY' %
                      parent_request.id)
        return HttpResponseNotAllowed('Invalid parent')

    # Load the current child.

    try:
        child_request = ReviewRequest.objects.get(id=child_id)
    except ReviewRequest.DoesNotExist:
        return HttpResponseNotFound('Child Not Found')

    # Sanity check child.

    if is_parent(child_request):
        return HttpResponseNotAllowed('Invalid child')

    # Load all other children and ensure requested child matches parent.

    children_details = list(gen_child_rrs(parent_request, user=request.user))
    if not any(r for r in children_details if r.id == child_request.id):
        return HttpResponseNotAllowed('Invalid child')

    # Return rendered template.

    return render(
        request, 'mozreview/commits-requests.html', {
            'user': request.user,
            'review_request_details': child_request,
            'children_details': children_details,
        })
Example #12
0
def on_review_request_reopened(user, review_request, **kwargs):
    if not is_parent(review_request):
        return

    commit_data = fetch_commit_data(review_request)
    identifier = commit_data.extra_data[IDENTIFIER_KEY]

    # If we're reviving a squashed review request that was discarded, it means
    # we're going to want to restore the commit ID field back, since we remove
    # it on discarding. This might be a problem if there's already a review
    # request with the same commit ID somewhere on Review Board, since commit
    # IDs are unique.
    #
    # When this signal fires, the state of the review request has already
    # changed, so we query for a review request with the same commit ID that is
    # not equal to the revived review request.
    try:
        preexisting_review_request = ReviewRequest.objects.get(
            commit_id=identifier, repository=review_request.repository)
        if preexisting_review_request != review_request:
            logger.error(
                'Could not revive review request with ID %s because its '
                'commit id (%s) is already being used by a review request '
                'with ID %s.' %
                (review_request.id, identifier, preexisting_review_request.id))
            # TODO: We need Review Board to recognize exceptions in these
            # signal handlers so that the UI can print out a useful message.
            raise Exception(
                'Revive failed because a review request with commit ID %s '
                'already exists.' % identifier)
    except ReviewRequest.DoesNotExist:
        # Great! This is a success case.
        pass

    for child in gen_child_rrs(review_request):
        child.reopen(user=user)

    # If the review request had been discarded, then the commit ID would
    # have been cleared out. If the review request had been submitted,
    # this is a no-op, since the commit ID would have been there already.
    review_request.commit = identifier
    review_request.save()

    # If the review request has a draft, we have to set the commit ID there as
    # well, otherwise it'll get overwritten on publish.
    draft = review_request.get_draft(user)
    if draft:
        draft.commit = identifier
        draft.save()
Example #13
0
    def as_html(self):
        user = self.request.user
        parent = get_parent_rr(self.review_request_details.get_review_request())
        parent_details = parent.get_draft() or parent

        # If a user can view the parent draft they should also have
        # permission to view every child. We check if the child is
        # accessible anyways in case it has been restricted for other
        # reasons.
        children_details = [
            child for child in gen_child_rrs(parent_details, user)
            if child.is_accessible_by(user)]

        return get_template('mozreview/commits.html').render(Context({
            'review_request_details': self.review_request_details,
            'parent_details': parent_details,
            'children_details': children_details,
        }))
Example #14
0
    def is_approved_parent(self, review_request):
        """Check approval for a parent review request"""
        for rr in gen_child_rrs(review_request):
            if not rr.approved:
                commit_id = rr.extra_data.get(COMMIT_ID_KEY, None)

                if commit_id is None:
                    logging.error('Review request %s missing commit_id' % rr.id)
                    return False, 'A Commit is not approved.'

                return False, 'Commit %s is not approved.' % commit_id
        else:
            # This parent review request had no children, so it's either
            # private or something has gone seriously wrong.
            logging.error('Review request %s has no children' %
                          review_request.id)
            return False, 'Review request has no children.'

        return True
Example #15
0
    def create(self, request, parent_request_id, *args, **kwargs):
        try:
            parent_rr = ReviewRequest.objects.get(pk=parent_request_id)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        if not (parent_rr.is_accessible_by(request.user)
                or parent_rr.is_mutable_by(request.user)):
            return PERMISSION_DENIED

        if not is_parent(parent_rr):
            return NOT_PARENT

        with transaction.atomic():
            for child_rr in gen_child_rrs(parent_rr):
                if child_rr.get_draft() is None:
                    ReviewRequestDraft.create(child_rr)
            if parent_rr.get_draft() is None:
                ReviewRequestDraft.create(parent_rr)

        return 200, {}
Example #16
0
    def is_approved_parent(self, review_request):
        """Check approval for a parent review request"""
        children = list(gen_child_rrs(review_request))

        if not children:
            # This parent review request had no children, so it's either
            # private or something has gone seriously wrong.
            logger.error('Review request %s has no children' %
                         review_request.id)
            return False, 'Review request has no children.'

        for rr in children:
            if not rr.approved:
                commit_data = fetch_commit_data(rr)
                commit_id = commit_data.extra_data.get(COMMIT_ID_KEY, None)

                if commit_id is None:
                    logger.error('Review request %s missing commit_id' % rr.id)
                    return False, 'A Commit is not approved.'

                return False, 'Commit %s is not approved.' % commit_id

        return True
Example #17
0
    def as_html(self):
        user = self.request.user
        parent = get_parent_rr(self.review_request_details.get_review_request())
        parent_details = parent.get_draft(user) or parent

        # If a user can view the parent draft they should also have
        # permission to view every child. We check if the child is
        # accessible anyways in case it has been restricted for other
        # reasons.
        children_details = [
            child for child in gen_child_rrs(parent_details, user=user)
            if child.is_accessible_by(user)]

        autoland_requests = AutolandRequest.objects.filter(
            review_request_id=parent.id).order_by('-autoland_id')

        repo_urls = set()
        latest_autoland_requests = []


        # We would like to fetch the latest AutolandRequest for each
        # different repository.
        for request in autoland_requests:
            if request.repository_url in repo_urls:
                continue

            repo_urls.add(request.repository_url)
            latest_autoland_requests.append(request)

        return get_template('mozreview/commits.html').render(Context({
            'review_request_details': self.review_request_details,
            'parent_details': parent_details,
            'children_details': children_details,
            'latest_autoland_requests': latest_autoland_requests,
            'user': user
        }))
def on_draft_pre_delete(sender, instance, using, **kwargs):
    """ Handle draft discards.

    There are no handy signals built into Review Board (yet) for us to detect
    when a squashed Review Request Draft is discarded. Instead, we monitor for
    deletions of models, and handle cases where the models being deleted are
    ReviewRequestDrafts. We then do some processing to ensure that the draft
    is indeed a draft of a squashed review request that we want to handle,
    and then propagate the discard down to the child review requests.
    """
    if not sender == ReviewRequestDraft:
        return

    # Drafts can get deleted for a number of reasons. They get deleted when
    # drafts are discarded, obviously, but also whenever review requests are
    # published, because the data gets copied over to the review request, and
    # then the draft is blown away. Unfortunately, on_pre_delete doesn't give
    # us too many clues about which scenario we're in, so we have to infer it
    # based on other things attached to the model. This is a temporary fix until
    # we get more comprehensive draft deletion signals built into Review Board.
    #
    # In the case where the review request is NOT public yet, the draft will
    # not have a change description. In this case, we do not need to
    # differentiate between publish and discard because discards of non-public
    # review request's drafts will always cause the review request to be closed
    # as discarded, and this case is handled by on_review_request_closed().
    #
    # In the case where the review request has a change description, but it's
    # set to public, we must have just published this draft before deleting it,
    # so there's nothing to do here.
    if (instance.changedesc is None or instance.changedesc.public):
        return

    review_request = instance.review_request

    if not review_request:
        return

    if not is_review_request_squashed(review_request):
        return

    # If the review request is marked as discarded, then we must be closing
    # it, and so the on_review_request_closed() handler will take care of it.
    if review_request.status == ReviewRequest.DISCARDED:
        return

    user = review_request.submitter

    for child in gen_child_rrs(review_request):
        draft = child.get_draft()
        if draft:
            draft.delete()

    for child in gen_rrs_by_extra_data_key(review_request,
                                           UNPUBLISHED_RRIDS_KEY):
        child.close(ReviewRequest.DISCARDED,
                    user=user,
                    description=NEVER_USED_DESCRIPTION)

    review_request.extra_data['p2rb.discard_on_publish_rids'] = '[]'
    review_request.extra_data['p2rb.unpublished_rids'] = '[]'
    review_request.save()
    def create(self, request, parent_request_id, reviewers, *args, **kwargs):
        try:
            parent_rr = ReviewRequest.objects.get(pk=parent_request_id)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        if not (parent_rr.is_accessible_by(request.user)
                or parent_rr.is_mutable_by(request.user)):
            return PERMISSION_DENIED

        if not is_parent(parent_rr):
            return NOT_PARENT

        # Validate and expand the new reviewer list.

        bugzilla = Bugzilla(get_bugzilla_api_key(request.user))
        child_reviewers = json.loads(reviewers)
        invalid_reviewers = []
        for child_rrid in child_reviewers:
            users = []
            for username in child_reviewers[child_rrid]:
                try:
                    users.append(bugzilla.get_user_from_irc_nick(username))
                except User.DoesNotExist:
                    invalid_reviewers.append(username)
            child_reviewers[child_rrid] = users

        if invalid_reviewers:
            # Because this isn't called through Review Board's built-in
            # backbone system, it's dramatically simpler to return just the
            # intended error message instead of categorising the errors by
            # field.
            if len(invalid_reviewers) == 1:
                return INVALID_FORM_DATA.with_message(
                    "The reviewer '%s' was not found" % invalid_reviewers[0])
            else:
                return INVALID_FORM_DATA.with_message(
                    "The reviewers '%s' were not found" %
                    "', '".join(invalid_reviewers))

        # Review Board only supports the submitter updating a review
        # request.  In order for this to work, we publish these changes
        # in Review Board under the review submitter's account, and
        # set an extra_data field which instructs our bugzilla
        # connector to use this request's user when adjusting flags.
        #
        # Updating the review request requires creating a draft and
        # publishing it, so we have to be careful to not overwrite
        # existing drafts.

        try:
            with transaction.atomic():
                for rr in itertools.chain([parent_rr],
                                          gen_child_rrs(parent_rr)):
                    if rr.get_draft() is not None:
                        return REVIEW_REQUEST_UPDATE_NOT_ALLOWED.with_message(
                            "Unable to update reviewers as the review "
                            "request has pending changes (the patch author "
                            "has a draft)")

                try:
                    for child_rr in gen_child_rrs(parent_rr):
                        if str(child_rr.id) in child_reviewers:
                            if not child_rr.is_accessible_by(request.user):
                                return PERMISSION_DENIED.with_message(
                                    "You do not have permission to update "
                                    "reviewers on review request %s" %
                                    child_rr.id)

                            draft = ReviewRequestDraft.create(child_rr)
                            draft.target_people.clear()
                            for user in child_reviewers[str(child_rr.id)]:
                                draft.target_people.add(user)

                    set_publish_as(parent_rr, request.user)
                    parent_rr_draft = ReviewRequestDraft.create(parent_rr)
                    update_parent_rr_reviewers(parent_rr_draft)
                    parent_rr.publish(user=parent_rr.submitter)
                finally:
                    clear_publish_as(parent_rr)

        except PublishError as e:
            logging.error("failed to update reviewers on %s: %s" %
                          (parent_rr.id, str(e)))
            return PUBLISH_ERROR.with_message(str(e))

        return 200, {}
Example #20
0
def on_review_publishing(user, review, **kwargs):
    """Comment in the bug and potentially r+ or clear a review flag.

    Note that a reviewer *must* have editbugs to set an attachment flag on
    someone else's attachment (i.e. the standard BMO review process).

    TODO: Report lack-of-editbugs properly; see bug 1119065.
    """
    review_request = review.review_request
    logger.info('Publishing review for user: %s review id: %s '
                'review request id: %s' % (user, review.id, review_request.id))

    # skip review requests that were not pushed
    if not is_pushed(review_request):
        logger.info('Did not publish review: %s: for user: %d: review not '
                    'pushed.' % (user, review.id))
        return

    site = Site.objects.get_current()
    siteconfig = SiteConfiguration.objects.get_current()
    comment = build_plaintext_review(review,
                                     get_obj_url(review, site, siteconfig),
                                     {"user": user})
    b = Bugzilla(get_bugzilla_api_key(user))

    if is_parent(review_request):
        # We only support raw comments on parent review requests to prevent
        # confusion.  If the ship-it flag or the review flag was set, throw
        # an error.
        # Otherwise, mirror the comment over, associating it with the first
        # commit.
        if review.ship_it or review.extra_data.get(REVIEW_FLAG_KEY):
            raise ParentShipItError

        # TODO: If we ever allow multiple bugs in a single series, and we want
        # to continue to allow comments on parents, we'll have to pick one
        # child for each unique bug.
        first_child = list(gen_child_rrs(review_request))[0]
        b.post_comment(int(first_child.get_bug_list()[0]), comment,
                       get_diff_url(first_child), False)
    else:
        diff_url = get_diff_url(review_request)
        bug_id = int(review_request.get_bug_list()[0])

        commented = False
        flag = review.extra_data.get(REVIEW_FLAG_KEY)

        if flag is not None:
            commented = b.set_review_flag(bug_id, flag, review.user.email,
                                          diff_url, comment)
        else:
            # If for some reasons we don't have the flag set in extra_data,
            # fall back to ship_it
            logger.warning('Review flag not set on review %s, '
                           'updating attachment based on ship_it' % review.id)
            if review.ship_it:
                commented = b.r_plus_attachment(bug_id, review.user.email,
                                                diff_url, comment)
            else:
                commented = b.cancel_review_request(bug_id, review.user.email,
                                                    diff_url, comment)

        if comment and not commented:
            b.post_comment(bug_id, comment, diff_url, False)
def on_review_request_publishing(user, review_request_draft, **kwargs):
    # There have been strange cases (all local, and during development), where
    # when attempting to publish a review request, this handler will fail
    # because the draft does not exist. This is a really strange case, and not
    # one we expect to happen in production. However, since we've seen it
    # locally, we handle it here, and log.
    if not review_request_draft:
        logger.error('Strangely, there was no review request draft on the '
                     'review request we were attempting to publish.')
        return

    # If the review request draft has a new DiffSet we will only allow
    # publishing if that DiffSet has been verified. It is important to
    # do this for every review request, not just pushed ones, because
    # we can't trust the storage mechanism which indicates it was pushed.
    # TODO: This will be fixed when we transition away from extra_data.
    if review_request_draft.diffset:
        try:
            DiffSetVerification.objects.get(
                diffset=review_request_draft.diffset)
        except DiffSetVerification.DoesNotExist:
            logger.error(
                'An attempt was made by User %s to publish an unverified '
                'DiffSet with id %s',
                user.id,
                review_request_draft.diffset.id)

            raise PublishError(
                'This review request draft contained a manually uploaded '
                'diff, which is prohibited. Please push to the review server '
                'to create review requests. If you believe you received this '
                'message in error, please file a bug.')

    review_request = review_request_draft.get_review_request()
    commit_data = fetch_commit_data(review_request)

    # skip review requests that were not pushed
    if not is_pushed(review_request, commit_data=commit_data):
        return

    if not is_parent(review_request, commit_data):
        # Send a signal asking for approval to publish this review request.
        # We only want to publish this commit request if we are in the middle
        # of publishing the parent. If the parent is publishing it will be
        # listening for this signal to approve it.
        approvals = commit_request_publishing.send_robust(
            sender=review_request,
            user=user,
            review_request_draft=review_request_draft)

        for receiver, approved in approvals:
            if approved:
                break
        else:
            # This publish is not approved by the parent review request.
            raise CommitPublishProhibited()

    # The reviewid passed through p2rb is, for Mozilla's instance anyway,
    # bz://<bug id>/<irc nick>.
    reviewid = commit_data.draft_extra_data.get(IDENTIFIER_KEY, None)
    m = REVIEWID_RE.match(reviewid)

    if not m:
        raise InvalidBugIdError('<unknown>')

    bug_id = m.group(1)

    try:
        bug_id = int(bug_id)
    except (TypeError, ValueError):
        raise InvalidBugIdError(bug_id)

    siteconfig = SiteConfiguration.objects.get_current()
    using_bugzilla = (
        siteconfig.settings.get("auth_backend", "builtin") == "bugzilla")

    if using_bugzilla:
        b = Bugzilla(get_bugzilla_api_key(user))

        try:
            if b.is_bug_confidential(bug_id):
                raise ConfidentialBugError
        except BugzillaError as e:
            # Special cases:
            #   100: Invalid Bug Alias
            #   101: Bug does not exist
            if e.fault_code and (e.fault_code == 100 or e.fault_code == 101):
                raise InvalidBugIdError(bug_id)
            raise

    # Note that the bug ID has already been set when the review was created.

    # If this is a squashed/parent review request, automatically publish all
    # relevant children.
    if is_parent(review_request, commit_data):
        unpublished_rids = map(int, json.loads(
            commit_data.extra_data[UNPUBLISHED_KEY]))
        discard_on_publish_rids = map(int, json.loads(
            commit_data.extra_data[DISCARD_ON_PUBLISH_KEY]))
        child_rrs = list(gen_child_rrs(review_request_draft))

        # Create or update Bugzilla attachments for each draft commit.  This
        # is done before the children are published to ensure that MozReview
        # doesn't get into a strange state if communication with Bugzilla is
        # broken or attachment creation otherwise fails.  The Bugzilla
        # attachments will then, of course, be in a weird state, but that
        # should be fixed by the next successful publish.
        if using_bugzilla:
            for child in child_rrs:
                child_draft = child.get_draft(user=user)

                if child_draft:
                    if child.id in discard_on_publish_rids:
                        b.obsolete_review_attachments(
                            bug_id, get_obj_url(child))
                    post_bugzilla_attachment(b, bug_id, child_draft, child)

        # Publish draft commits. This will already include items that are in
        # unpublished_rids, so we'll remove anything we publish out of
        # unpublished_rids.
        for child in child_rrs:
            if child.get_draft(user=user) or not child.public:
                def approve_publish(sender, user, review_request_draft,
                                    **kwargs):
                    return child is sender

                # Setup the parent signal handler to approve the publish
                # and then publish the child.
                commit_request_publishing.connect(approve_publish,
                                                  sender=child,
                                                  weak=False)
                try:
                    child.publish(user=user)
                finally:
                    commit_request_publishing.disconnect(
                        receiver=approve_publish,
                        sender=child,
                        weak=False)

                if child.id in unpublished_rids:
                    unpublished_rids.remove(child.id)

        # The remaining unpubished_rids need to be closed as discarded because
        # they have never been published, and they will appear in the user's
        # dashboard unless closed.
        for child in gen_rrs_by_rids(unpublished_rids):
            child.close(ReviewRequest.DISCARDED,
                        user=user,
                        description=NEVER_USED_DESCRIPTION)

        # We also close the discard_on_publish review requests because, well,
        # we don't need them anymore. We use a slightly different message
        # though.
        for child in gen_rrs_by_rids(discard_on_publish_rids):
            child.close(ReviewRequest.DISCARDED,
                        user=user,
                        description=OBSOLETE_DESCRIPTION)

        commit_data.extra_data[UNPUBLISHED_KEY] = '[]'
        commit_data.extra_data[DISCARD_ON_PUBLISH_KEY] = '[]'

    # Copy any drafted CommitData from draft_extra_data to extra_data.
    for key in DRAFTED_COMMIT_DATA_KEYS:
        if key in commit_data.draft_extra_data:
            commit_data.extra_data[key] = commit_data.draft_extra_data[key]

    commit_data.save(update_fields=['extra_data'])

    review_request.save()
Example #22
0
def on_draft_pre_delete(sender, instance, using, **kwargs):
    """ Handle draft discards.

    There are no handy signals built into Review Board (yet) for us to detect
    when a squashed Review Request Draft is discarded. Instead, we monitor for
    deletions of models, and handle cases where the models being deleted are
    ReviewRequestDrafts. We then do some processing to ensure that the draft
    is indeed a draft of a squashed review request that we want to handle,
    and then propagate the discard down to the child review requests.
    """
    if not sender == ReviewRequestDraft:
        return

    # Drafts can get deleted for a number of reasons. They get deleted when
    # drafts are discarded, obviously, but also whenever review requests are
    # published, because the data gets copied over to the review request, and
    # then the draft is blown away. Unfortunately, on_pre_delete doesn't give
    # us too many clues about which scenario we're in, so we have to infer it
    # based on other things attached to the model. This is a temporary fix
    # until we get more comprehensive draft deletion signals built into Review
    # Board.
    #
    # In the case where the review request is NOT public yet, the draft will
    # not have a change description. In this case, we do not need to
    # differentiate between publish and discard because discards of non-public
    # review request's drafts will always cause the review request to be closed
    # as discarded, and this case is handled by on_review_request_closed().
    #
    # In the case where the review request has a change description, but it's
    # set to public, we must have just published this draft before deleting it,
    # so there's nothing to do here.
    if (instance.changedesc is None or instance.changedesc.public):
        return

    review_request = instance.review_request

    if not review_request:
        return

    commit_data = fetch_commit_data(review_request)

    if not is_parent(review_request, commit_data):
        return

    # If the review request is marked as discarded, then we must be closing
    # it, and so the on_review_request_closed() handler will take care of it.
    if review_request.status == ReviewRequest.DISCARDED:
        return

    user = review_request.submitter

    for child in gen_child_rrs(review_request, commit_data=commit_data):
        draft = child.get_draft()
        if draft:
            draft.delete()

    for child in gen_rrs_by_extra_data_key(review_request,
                                           UNPUBLISHED_KEY,
                                           commit_data=commit_data):
        child.close(ReviewRequest.DISCARDED,
                    user=user,
                    description=NEVER_USED_DESCRIPTION)

    commit_data.extra_data[DISCARD_ON_PUBLISH_KEY] = '[]'
    commit_data.extra_data[UNPUBLISHED_KEY] = '[]'
    commit_data.save(update_fields=['extra_data'])
def on_review_request_publishing(user, review_request_draft, **kwargs):
    # There have been strange cases (all local, and during development), where
    # when attempting to publish a review request, this handler will fail
    # because the draft does not exist. This is a really strange case, and not
    # one we expect to happen in production. However, since we've seen it
    # locally, we handle it here, and log.
    if not review_request_draft:
        logging.error('Strangely, there was no review request draft on the '
                      'review request we were attempting to publish.')
        return

    review_request = review_request_draft.get_review_request()

    # skip review requests that were not pushed
    if not is_review_request_pushed(review_request):
        return

    if not is_parent(review_request):
        # Send a signal asking for approval to publish this review request.
        # We only want to publish this commit request if we are in the middle
        # of publishing the parent. If the parent is publishing it will be
        # listening for this signal to approve it.
        approvals = commit_request_publishing.send_robust(
            sender=review_request,
            user=user,
            review_request_draft=review_request_draft)

        for receiver, approved in approvals:
            if approved:
                break
        else:
            # This publish is not approved by the parent review request.
            raise CommitPublishProhibited()

    # The reviewid passed through p2rb is, for Mozilla's instance anyway,
    # bz://<bug id>/<irc nick>.
    reviewid = review_request_draft.extra_data.get('p2rb.identifier', None)
    m = REVIEWID_RE.match(reviewid)

    if not m:
        raise InvalidBugIdError('<unknown>')

    bug_id = m.group(1)
    using_bugzilla = we_are_using_bugzilla()
    try:
        bug_id = int(bug_id)
    except (TypeError, ValueError):
        raise InvalidBugIdError(bug_id)

    if using_bugzilla:
        b = Bugzilla(get_bugzilla_api_key(user))

        try:
            if b.is_bug_confidential(bug_id):
                raise ConfidentialBugError
        except BugzillaError as e:
            # Special cases:
            #   100: Invalid Bug Alias
            #   101: Bug does not exist
            if e.fault_code and (e.fault_code == 100 or e.fault_code == 101):
                raise InvalidBugIdError(bug_id)
            raise

    # Note that the bug ID has already been set when the review was created.

    # If this is a squashed/parent review request, automatically publish all
    # relevant children.
    if is_review_request_squashed(review_request):
        unpublished_rids = map(int, json.loads(
            review_request.extra_data['p2rb.unpublished_rids']))
        discard_on_publish_rids = map(int, json.loads(
            review_request.extra_data['p2rb.discard_on_publish_rids']))
        child_rrs = list(gen_child_rrs(review_request_draft))

        # Create or update Bugzilla attachments for each draft commit.  This
        # is done before the children are published to ensure that MozReview
        # doesn't get into a strange state if communication with Bugzilla is
        # broken or attachment creation otherwise fails.  The Bugzilla
        # attachments will then, of course, be in a weird state, but that
        # should be fixed by the next successful publish.
        if using_bugzilla:
            for child in child_rrs:
                child_draft = child.get_draft(user=user)

                if child_draft:
                    if child.id in discard_on_publish_rids:
                        b.obsolete_review_attachments(
                            bug_id, get_obj_url(child))
                    post_bugzilla_attachment(b, bug_id, child_draft, child)

        # Publish draft commits. This will already include items that are in
        # unpublished_rids, so we'll remove anything we publish out of
        # unpublished_rids.
        for child in child_rrs:
            if child.get_draft(user=user) or not child.public:
                def approve_publish(sender, user, review_request_draft,
                                    **kwargs):
                    return child is sender

                # Setup the parent signal handler to approve the publish
                # and then publish the child.
                commit_request_publishing.connect(approve_publish, sender=child,
                                                  weak=False)
                try:
                    child.publish(user=user)
                finally:
                    commit_request_publishing.disconnect(
                        receiver=approve_publish,
                        sender=child,
                        weak=False)

                if child.id in unpublished_rids:
                    unpublished_rids.remove(child.id)

        # The remaining unpubished_rids need to be closed as discarded because
        # they have never been published, and they will appear in the user's
        # dashboard unless closed.
        for child in gen_rrs_by_rids(unpublished_rids):
            child.close(ReviewRequest.DISCARDED,
                        user=user,
                        description=NEVER_USED_DESCRIPTION)

        # We also close the discard_on_publish review requests because, well,
        # we don't need them anymore. We use a slightly different message
        # though.
        for child in gen_rrs_by_rids(discard_on_publish_rids):
            child.close(ReviewRequest.DISCARDED,
                        user=user,
                        description=OBSOLETE_DESCRIPTION)

        review_request.extra_data['p2rb.unpublished_rids'] = '[]'
        review_request.extra_data['p2rb.discard_on_publish_rids'] = '[]'

    # Copy p2rb extra data from the draft, overwriting the current
    # values on the review request.
    draft_extra_data = review_request_draft.extra_data

    for key in DRAFTED_EXTRA_DATA_KEYS:
        if key in draft_extra_data:
            review_request.extra_data[key] = draft_extra_data[key]

    review_request.save()
Example #24
0
def on_review_request_publishing(user, review_request_draft, **kwargs):
    # There have been strange cases (all local, and during development), where
    # when attempting to publish a review request, this handler will fail
    # because the draft does not exist. This is a really strange case, and not
    # one we expect to happen in production. However, since we've seen it
    # locally, we handle it here, and log.
    if not review_request_draft:
        logger.error('Strangely, there was no review request draft on the '
                     'review request we were attempting to publish.')
        return

    # If the review request draft has a new DiffSet we will only allow
    # publishing if that DiffSet has been verified. It is important to
    # do this for every review request, not just pushed ones, because
    # we can't trust the storage mechanism which indicates it was pushed.
    # TODO: This will be fixed when we transition away from extra_data.
    if review_request_draft.diffset:
        try:
            DiffSetVerification.objects.get(
                diffset=review_request_draft.diffset)
        except DiffSetVerification.DoesNotExist:
            logger.error(
                'An attempt was made by User %s to publish an unverified '
                'DiffSet with id %s', user.id, review_request_draft.diffset.id)

            raise PublishError(
                'This review request draft contained a manually uploaded '
                'diff, which is prohibited. Please push to the review server '
                'to create review requests. If you believe you received this '
                'message in error, please file a bug.')

    review_request = review_request_draft.get_review_request()
    commit_data = fetch_commit_data(review_request)

    # skip review requests that were not pushed
    if not is_pushed(review_request, commit_data=commit_data):
        return

    if not is_parent(review_request, commit_data):
        # Send a signal asking for approval to publish this review request.
        # We only want to publish this commit request if we are in the middle
        # of publishing the parent. If the parent is publishing it will be
        # listening for this signal to approve it.
        approvals = commit_request_publishing.send_robust(
            sender=review_request,
            user=user,
            review_request_draft=review_request_draft)

        for receiver, approved in approvals:
            if approved:
                break
        else:
            # This publish is not approved by the parent review request.
            raise CommitPublishProhibited()

    # The reviewid passed through p2rb is, for Mozilla's instance anyway,
    # bz://<bug id>/<irc nick>.
    reviewid = commit_data.draft_extra_data.get(IDENTIFIER_KEY, None)
    m = REVIEWID_RE.match(reviewid)

    if not m:
        raise InvalidBugIdError('<unknown>')

    bug_id = m.group(1)

    try:
        bug_id = int(bug_id)
    except (TypeError, ValueError):
        raise InvalidBugIdError(bug_id)

    siteconfig = SiteConfiguration.objects.get_current()
    using_bugzilla = (siteconfig.settings.get("auth_backend",
                                              "builtin") == "bugzilla")

    if using_bugzilla:
        commit_data = fetch_commit_data(review_request_draft)
        publish_as_id = commit_data.draft_extra_data.get(PUBLISH_AS_KEY)
        if publish_as_id:
            u = User.objects.get(id=publish_as_id)
            b = Bugzilla(get_bugzilla_api_key(u))
        else:
            b = Bugzilla(get_bugzilla_api_key(user))

        try:
            if b.is_bug_confidential(bug_id):
                raise ConfidentialBugError
        except BugzillaError as e:
            # Special cases:
            #   100: Invalid Bug Alias
            #   101: Bug does not exist
            if e.fault_code and (e.fault_code == 100 or e.fault_code == 101):
                raise InvalidBugIdError(bug_id)
            raise

    # Note that the bug ID has already been set when the review was created.

    # If this is a squashed/parent review request, automatically publish all
    # relevant children.
    if is_parent(review_request, commit_data):
        unpublished_rids = map(
            int, json.loads(commit_data.extra_data[UNPUBLISHED_KEY]))
        discard_on_publish_rids = map(
            int, json.loads(commit_data.extra_data[DISCARD_ON_PUBLISH_KEY]))
        child_rrs = list(gen_child_rrs(review_request_draft))

        # Create or update Bugzilla attachments for each draft commit.  This
        # is done before the children are published to ensure that MozReview
        # doesn't get into a strange state if communication with Bugzilla is
        # broken or attachment creation otherwise fails.  The Bugzilla
        # attachments will then, of course, be in a weird state, but that
        # should be fixed by the next successful publish.
        if using_bugzilla:
            children_to_post = []
            children_to_obsolete = []

            for child in child_rrs:
                child_draft = child.get_draft(user=user)

                if child_draft:
                    if child.id in discard_on_publish_rids:
                        children_to_obsolete.append(child)

                    children_to_post.append((child_draft, child))

            if children_to_post or children_to_obsolete:
                update_bugzilla_attachments(b, bug_id, children_to_post,
                                            children_to_obsolete)

        # Publish draft commits. This will already include items that are in
        # unpublished_rids, so we'll remove anything we publish out of
        # unpublished_rids.
        for child in child_rrs:
            if child.get_draft(user=user) or not child.public:

                def approve_publish(sender, user, review_request_draft,
                                    **kwargs):
                    return child is sender

                # Setup the parent signal handler to approve the publish
                # and then publish the child.
                commit_request_publishing.connect(approve_publish,
                                                  sender=child,
                                                  weak=False)
                try:
                    child.publish(user=user)
                except NotModifiedError:
                    # As we create empty drafts as part of allowing reviewer
                    # delegation, delete these empty drafts instead of
                    # throwing an error.
                    child.get_draft(user=user).delete()
                finally:
                    commit_request_publishing.disconnect(
                        receiver=approve_publish, sender=child, weak=False)

                if child.id in unpublished_rids:
                    unpublished_rids.remove(child.id)

        # The remaining unpubished_rids need to be closed as discarded because
        # they have never been published, and they will appear in the user's
        # dashboard unless closed.
        for child in gen_rrs_by_rids(unpublished_rids):
            child.close(ReviewRequest.DISCARDED,
                        user=user,
                        description=NEVER_USED_DESCRIPTION)

        # We also close the discard_on_publish review requests because, well,
        # we don't need them anymore. We use a slightly different message
        # though.
        for child in gen_rrs_by_rids(discard_on_publish_rids):
            child.close(ReviewRequest.DISCARDED,
                        user=user,
                        description=OBSOLETE_DESCRIPTION)

        commit_data.extra_data[UNPUBLISHED_KEY] = '[]'
        commit_data.extra_data[DISCARD_ON_PUBLISH_KEY] = '[]'

    # Copy any drafted CommitData from draft_extra_data to extra_data.
    for key in DRAFTED_COMMIT_DATA_KEYS:
        if key in commit_data.draft_extra_data:
            commit_data.extra_data[key] = commit_data.draft_extra_data[key]

    commit_data.save(update_fields=['extra_data'])

    review_request.save()
    def create(self, request, parent_request_id, reviewers, *args, **kwargs):
        try:
            parent_rr = ReviewRequest.objects.get(pk=parent_request_id)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        if not (parent_rr.is_accessible_by(request.user)
                or parent_rr.is_mutable_by(request.user)):
            return PERMISSION_DENIED

        if not is_parent(parent_rr):
            return NOT_PARENT

        # Validate and expand the new reviewer list.

        bugzilla = Bugzilla(get_bugzilla_api_key(request.user))
        child_reviewers = json.loads(reviewers)
        invalid_reviewers = []
        for child_rrid in child_reviewers:
            users = []
            for username in child_reviewers[child_rrid]:
                try:
                    users.append(bugzilla.get_user_from_irc_nick(username))
                except User.DoesNotExist:
                    invalid_reviewers.append(username)
            child_reviewers[child_rrid] = users

        if invalid_reviewers:
            # Because this isn't called through Review Board's built-in
            # backbone system, it's dramatically simpler to return just the
            # intended error message instead of categorising the errors by
            # field.
            if len(invalid_reviewers) == 1:
                return INVALID_FORM_DATA.with_message(
                    "The reviewer '%s' was not found" % invalid_reviewers[0])
            else:
                return INVALID_FORM_DATA.with_message(
                    "The reviewers '%s' were not found"
                    % "', '".join(invalid_reviewers))

        # Review Board only supports the submitter updating a review
        # request.  In order for this to work, we publish these changes
        # in Review Board under the review submitter's account, and
        # set an extra_data field which instructs our bugzilla
        # connector to use this request's user when adjusting flags.
        #
        # Updating the review request requires creating a draft and
        # publishing it, so we have to be careful to not overwrite
        # existing drafts.

        try:
            with transaction.atomic():
                for rr in itertools.chain([parent_rr],
                                          gen_child_rrs(parent_rr)):
                    if rr.get_draft() is not None:
                        return REVIEW_REQUEST_UPDATE_NOT_ALLOWED.with_message(
                            "Unable to update reviewers as the review "
                            "request has pending changes (the patch author "
                            "has a draft)")

                try:
                    for child_rr in gen_child_rrs(parent_rr):
                        if str(child_rr.id) in child_reviewers:
                            if not child_rr.is_accessible_by(request.user):
                                return PERMISSION_DENIED.with_message(
                                    "You do not have permission to update "
                                    "reviewers on review request %s"
                                    % child_rr.id)

                            draft = ReviewRequestDraft.create(child_rr)
                            draft.target_people.clear()
                            for user in child_reviewers[str(child_rr.id)]:
                                draft.target_people.add(user)

                    set_publish_as(parent_rr, request.user)
                    parent_rr_draft = ReviewRequestDraft.create(parent_rr)
                    update_parent_rr_reviewers(parent_rr_draft)
                    parent_rr.publish(user=parent_rr.submitter)
                finally:
                    clear_publish_as(parent_rr)

        except PublishError as e:
                logging.error("failed to update reviewers on %s: %s"
                              % (parent_rr.id, str(e)))
                return PUBLISH_ERROR.with_message(str(e))

        return 200, {}