예제 #1
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,
        })
예제 #2
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]
예제 #3
0
def pre_save_review(sender, *args, **kwargs):
    """Handle pre_save for a Review.

    This is needed to give a default value to the REVIEW_FLAG_KEY
    extra_data key. It tries to retrieve the last known review status,
    falling back to r? if no status is found.
    """
    review = kwargs["instance"]
    if review.pk:
        # The review create endpoint calls save twice: the first time it
        # gets or creates the review and the second time it updates the
        # object retrieved/created. This condition let us execute the code
        # below only once.

        if not is_parent(review.review_request):

            if REVIEW_FLAG_KEY not in review.extra_data:
                # TODO: we should use a different query than going through
                # all the reviews, which is what get_reviewers_status does.
                reviewers_status = get_reviewers_status(review.review_request,
                                                        reviewers=[review.user])
                user = review.user.username
                flag = reviewers_status.get(user, {}).get('review_flag', ' ')
                review.extra_data[REVIEW_FLAG_KEY] = flag

            review.ship_it = (review.extra_data[REVIEW_FLAG_KEY] == 'r+')
예제 #4
0
    def is_approved(self, review_request, prev_approved, prev_failure):
        """Check if a review request is approved to land

        We will completely override the checks done by Review Board and
        provide our own (to keep approval simpler and explicit).

        If True is returned by this function it will indicate that
        review request may be autolanded - care should be taken
        when modifying the logic.
        """
        # TODO: We should consider rejecting review requests which
        # currently have a draft (to prevent autolanding incorrect
        # things)
        try:
            if not is_pushed(review_request):
                return False, 'Manually uploaded requests cannot be approved.'

            if not review_request.public:
                return False, 'The review request is not public.'

            if is_parent(review_request):
                return self.is_approved_parent(review_request)

            return self.is_approved_child(review_request)
        except Exception as e:
            # We catch all exceptions because any error will make
            # Review Board revert to it's default behaviour which
            # is much more relaxed than ours.
            logger.error('Failed to calculate approval for review '
                         'request %s: %s' % (review_request.id, e))
            return False, "Error when calculating approval."
예제 #5
0
def on_review_request_closed_discarded(user, review_request, type, **kwargs):
    if type != ReviewRequest.DISCARDED:
        return

    commit_data = fetch_commit_data(review_request)

    if is_parent(review_request, commit_data):
        # close_child_review_requests will call save on this review request, so
        # we don't have to worry about it.
        review_request.commit = None

        _close_child_review_requests(user,
                                     review_request,
                                     ReviewRequest.DISCARDED,
                                     AUTO_CLOSE_DESCRIPTION,
                                     commit_data=commit_data)
    else:
        # TODO: Remove this once we properly prevent users from closing
        # commit review requests.
        b = Bugzilla(get_bugzilla_api_key(user))
        bug = int(review_request.get_bug_list()[0])
        attachment_updates = BugzillaAttachmentUpdates(b, bug)
        attachment_updates.obsolete_review_attachments(
            get_diff_url(review_request))
        attachment_updates.do_updates()
예제 #6
0
def pre_save_review(sender, *args, **kwargs):
    """Handle pre_save for a Review.

    This is needed to give a default value to the REVIEW_FLAG_KEY
    extra_data key. It tries to retrieve the last known review status,
    falling back to r? if no status is found.
    """
    review = kwargs["instance"]
    if review.pk:
        # The review create endpoint calls save twice: the first time it
        # gets or creates the review and the second time it updates the
        # object retrieved/created. This condition let us execute the code
        # below only once.

        if not is_parent(review.review_request):

            if REVIEW_FLAG_KEY not in review.extra_data:
                # TODO: we should use a different query than going through
                # all the reviews, which is what get_reviewers_status does.
                reviewers_status = get_reviewers_status(
                    review.review_request, reviewers=[review.user])
                user = review.user.username
                flag = reviewers_status.get(user, {}).get('review_flag', ' ')
                review.extra_data[REVIEW_FLAG_KEY] = flag

            review.ship_it = (review.extra_data[REVIEW_FLAG_KEY] == 'r+')
예제 #7
0
    def is_approved(self, review_request, prev_approved, prev_failure):
        """Check if a review request is approved to land

        We will completely override the checks done by Review Board and
        provide our own (to keep approval simpler and explicit).

        If True is returned by this function it will indicate that
        review request may be autolanded - care should be taken
        when modifying the logic.
        """
        # TODO: We should consider rejecting review requests which
        # currently have a draft (to prevent autolanding incorrect
        # things)
        try:
            if not is_pushed(review_request):
                return False, 'Manually uploaded requests cannot be approved.'

            if not review_request.public:
                return False, 'The review request is not public.'

            if is_parent(review_request):
                return self.is_approved_parent(review_request)

            return self.is_approved_child(review_request)
        except Exception as e:
            # We catch all exceptions because any error will make
            # Review Board revert to it's default behaviour which
            # is much more relaxed than ours.
            logger.error('Failed to calculate approval for review '
                         'request %s: %s' % (review_request.id, e))
            return False, "Error when calculating approval."
예제 #8
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,
        }))
    def _sort_families(self, request, rrs, families=None):
        """Sort ReviewRequest objects into families.

        'families' is a dict with parent ReviewRequest ids as keys.  Each
        value is another dict, with 'parent' mapped to the parent
        ReviewRequest and 'children' mapped to a list of child ReviewRequests
        of that parent.  If 'families' is not None, it is updated in place;
        if 'families' is not given, it is first initialized.  In both cases
        'families' is also returned.

        For each ReviewRequest in rrs, 'families' is updated appropriately
        to assemble a full set of families.
        """
        if families is None:
            families = defaultdict(lambda: dict(parent=None, children={}))

        for rr in rrs:
            if rr.status == ReviewRequest.DISCARDED:
                continue

            if not self.has_access_permissions(request, rr):
                continue

            if is_parent(rr):
                families[rr.id]['parent'] = rr
            else:
                # Some early review requests were orphaned; ignore them.
                try:
                    parent_rr = get_parent_rr(rr)
                except ReviewRequest.DoesNotExist:
                    continue

                families[parent_rr.id]['children'][rr.id] = rr

        return families
    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]
예제 #11
0
    def _sort_families(self, request, rrs, families=None):
        """Sort ReviewRequest objects into families.

        'families' is a dict with parent ReviewRequest ids as keys.  Each
        value is another dict, with 'parent' mapped to the parent
        ReviewRequest and 'children' mapped to a list of child ReviewRequests
        of that parent.  If 'families' is not None, it is updated in place;
        if 'families' is not given, it is first initialized.  In both cases
        'families' is also returned.

        For each ReviewRequest in rrs, 'families' is updated appropriately
        to assemble a full set of families.
        """
        if families is None:
            families = defaultdict(lambda: dict(parent=None, children={}))

        for rr in rrs:
            if rr.status == ReviewRequest.DISCARDED:
                continue

            if not self.has_access_permissions(request, rr):
                continue

            if is_parent(rr):
                families[rr.id]['parent'] = rr
            else:
                # Some early review requests were orphaned; ignore them.
                try:
                    parent_rr = get_parent_rr(rr)
                except ReviewRequest.DoesNotExist:
                    continue

                families[parent_rr.id]['children'][rr.id] = rr

        return families
예제 #12
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,
        }))
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,
    }
    def get(self, request, *args, **kwargs):
        try:
            parent_request = get_parent_rr(ReviewRequest.objects.get(
                id=kwargs[self.uri_object_key]))
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST
        if parent_request is None:
            return DOES_NOT_EXIST
        if not is_parent(parent_request):
            return NOT_PARENT
        if not parent_request.is_accessible_by(request.user):
            return PERMISSION_DENIED
        if COMMITS_KEY not in parent_request.extra_data:
            logging.error('Parent review request %s missing COMMIT_KEY'
                          % parent_request.id)
            return NOT_PARENT

        result = []
        children = json.loads(parent_request.extra_data[COMMITS_KEY])
        for child in children:
            try:
                child_request = ReviewRequest.objects.get(id=child[1])
            except ReviewRequest.DoesNotExist:
                return DOES_NOT_EXIST
            if not child_request.approved:
                return AUTOLAND_REVIEW_NOT_APPROVED

            reviewers = [
                r.user.username for r in gen_latest_reviews(child_request) if
                r.ship_it and
                r.user != child_request.submitter
            ]

            if not reviewers and child_request.approved:
                # This review request is approved (the repeated check is
                # to ensure this is guaranteed if other parts of the code
                # change) but we have an empty list of reviewers. We'll
                # assume the author has just approved this themself and
                # set r=me
                reviewers.append('me')

            result.append({
                'commit': child[0],
                'id': child[1],
                'reviewers': reviewers,
                'summary': replace_reviewers(child_request.description,
                                             reviewers)
            })

        return 200, {
            'commits': result,
            'total_results': len(result),
            'links': self.get_links(request=request),
        }
예제 #15
0
    def on_draft_changed(self, sender, **kwargs):
        instance = kwargs["instance"]
        rr = instance.get_review_request()

        if is_pushed(instance) and not is_parent(rr):
            parent_rr = get_parent_rr(rr)
            parent_rr_draft = parent_rr.get_draft()

            if parent_rr_draft is None:
                parent_rr_draft = ReviewRequestDraft.create(parent_rr)

            update_parent_rr_reviewers(parent_rr_draft)
def on_review_request_closed_submitted(user, review_request, type, **kwargs):
    if type != ReviewRequest.SUBMITTED:
        return

    commit_data = fetch_commit_data(review_request)

    if not is_parent(review_request, commit_data):
        return

    _close_child_review_requests(user, review_request, ReviewRequest.SUBMITTED,
                                 AUTO_SUBMITTED_DESCRIPTION,
                                 commit_data=commit_data)
예제 #17
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()
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))

    # TODO: Update all attachments in one call.  This is not possible right
    # now because we have to potentially mix changing and creating flags.

    if is_parent(review_request):
        # Mirror the comment to the bug, unless it's a ship-it, in which
        # case throw an error.  Ship-its are allowed only on child commits.
        if review.ship_it:
            raise ParentShipItError

        [b.post_comment(int(bug_id), comment) for bug_id in
         review_request.get_bug_list()]
    else:
        diff_url = '%sdiff/#index_header' % get_obj_url(review_request)
        bug_id = int(review_request.get_bug_list()[0])

        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)
예제 #20
0
def on_review_request_closed_submitted(user, review_request, type, **kwargs):
    if type != ReviewRequest.SUBMITTED:
        return

    commit_data = fetch_commit_data(review_request)

    if not is_parent(review_request, commit_data):
        return

    _close_child_review_requests(user,
                                 review_request,
                                 ReviewRequest.SUBMITTED,
                                 AUTO_SUBMITTED_DESCRIPTION,
                                 commit_data=commit_data)
예제 #21
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()
    def get(self, request, *args, **kwargs):
        try:
            parent_request = get_parent_rr(ReviewRequest.objects.get(
                id=kwargs[self.uri_object_key]))
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST
        if parent_request is None:
            return DOES_NOT_EXIST
        if not is_parent(parent_request):
            return NOT_PARENT
        if not parent_request.is_accessible_by(request.user):
            return PERMISSION_DENIED
        if COMMITS_KEY not in parent_request.extra_data:
            logging.error('Parent review request %s missing COMMIT_KEY'
                          % parent_request.id)
            return NOT_PARENT

        result = []
        children = json.loads(parent_request.extra_data[COMMITS_KEY])
        for child in children:
            try:
                child_request = ReviewRequest.objects.get(id=child[1])
            except ReviewRequest.DoesNotExist:
                return DOES_NOT_EXIST
            if not child_request.approved:
                return AUTOLAND_REVIEW_NOT_APPROVED

            reviewers = map(lambda review: review.user.username,
                            gen_latest_reviews(child_request))
            result.append({
                'commit': child[0],
                'id': child[1],
                'reviewers': reviewers,
                'summary': replace_reviewers(child_request.description,
                                             reviewers)
            })

        return 200, {
            'commits': result,
            'total_results': len(result),
            'links': self.get_links(request=request),
        }
예제 #23
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, {}
def on_review_request_closed_discarded(user, review_request, type, **kwargs):
    if type != ReviewRequest.DISCARDED:
        return

    commit_data = fetch_commit_data(review_request)

    if is_parent(review_request, commit_data):
        # close_child_review_requests will call save on this review request, so
        # we don't have to worry about it.
        review_request.commit = None

        _close_child_review_requests(user, review_request,
                                     ReviewRequest.DISCARDED,
                                     AUTO_CLOSE_DESCRIPTION,
                                     commit_data=commit_data)
    else:
        # TODO: Remove this once we properly prevent users from closing
        # commit review requests.
        b = Bugzilla(get_bugzilla_api_key(user))
        bug = int(review_request.get_bug_list()[0])
        diff_url = '%sdiff/#index_header' % get_obj_url(review_request)
        b.obsolete_review_attachments(bug, diff_url)
def ensure_parent_draft(draft):
    """Ensure parent draft exists when child has a draft.

    This is intended to be called in the post_save signal for the
    ReviewRequestDraft model and ensure the parent review request
    has a draft if a child draft is saved. We need to do this so
    that the parent may always be published when a child requires
    publishing.

    Particularly we update our own reviewer information in the
    parent to make sure that a reviewer change on a child request
    will create a parent draft - even if the reviewer change does
    not alter the overall set of reviewers for the series.
    """
    rr = draft.get_review_request()

    if is_pushed(draft) and not is_parent(rr):
        parent_rr = get_parent_rr(rr)
        parent_rr_draft = parent_rr.get_draft()

        if parent_rr_draft is None:
            parent_rr_draft = ReviewRequestDraft.create(parent_rr)

        update_parent_rr_reviewers(parent_rr_draft)
예제 #26
0
def ensure_parent_draft(draft):
    """Ensure parent draft exists when child has a draft.

    This is intended to be called in the post_save signal for the
    ReviewRequestDraft model and ensure the parent review request
    has a draft if a child draft is saved. We need to do this so
    that the parent may always be published when a child requires
    publishing.

    Particularly we update our own reviewer information in the
    parent to make sure that a reviewer change on a child request
    will create a parent draft - even if the reviewer change does
    not alter the overall set of reviewers for the series.
    """
    rr = draft.get_review_request()

    if is_pushed(draft) and not is_parent(rr):
        parent_rr = get_parent_rr(rr)
        parent_rr_draft = parent_rr.get_draft()

        if parent_rr_draft is None:
            parent_rr_draft = ReviewRequestDraft.create(parent_rr)

        update_parent_rr_reviewers(parent_rr_draft)
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()
예제 #28
0
    def get(self, request, *args, **kwargs):
        try:
            parent_request = get_parent_rr(
                ReviewRequest.objects.get(id=kwargs[self.uri_object_key]))
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST
        if parent_request is None:
            return DOES_NOT_EXIST

        commit_data = fetch_commit_data(parent_request)

        if not is_parent(parent_request, commit_data):
            return NOT_PARENT
        if not parent_request.is_accessible_by(request.user):
            return PERMISSION_DENIED
        if COMMITS_KEY not in commit_data.extra_data:
            logging.error('Parent review request %s missing COMMIT_KEY' %
                          parent_request.id)
            return NOT_PARENT

        result = []
        children = json.loads(commit_data.extra_data[COMMITS_KEY])
        for child in children:
            try:
                child_request = ReviewRequest.objects.get(id=child[1])
            except ReviewRequest.DoesNotExist:
                return DOES_NOT_EXIST
            if not child_request.approved:
                return AUTOLAND_REVIEW_NOT_APPROVED

            reviewers = [
                r.user.username for r in gen_latest_reviews(child_request)
                if r.ship_it and r.user != child_request.submitter
            ]

            if not reviewers and child_request.approved:
                # This review request is approved (the repeated check is
                # to ensure this is guaranteed if other parts of the code
                # change) but we have an empty list of reviewers. We'll
                # assume the author has just approved this themself.
                reviewers.append(child_request.submitter.username)

            # Detect if the commit has been changed since the last review.
            shipit_carryforward = has_shipit_carryforward(child_request)

            result.append({
                'commit':
                child[0],
                'id':
                child[1],
                'reviewers':
                reviewers,
                'shipit_carryforward':
                shipit_carryforward,
                'summary':
                replace_reviewers(child_request.description, reviewers)
            })

        return 200, {
            'commits': result,
            'total_results': len(result),
            'links': self.get_links(request=request),
        }
예제 #29
0
def isSquashed(review_request):
    return is_parent(review_request)
예제 #30
0
def handle_commits_published(extension=None, **kwargs):
    """Handle sending 'mozreview.commits.published'.

    This message is only sent when the parent review request, in a set of
    pushed review requests, is published with new commit information.

    This is a useful message for consumers who care about new or modified
    commits being published for review.
    """
    review_request = kwargs.get('review_request')

    if review_request is None:
        return

    commit_data = fetch_commit_data(review_request)

    if (not is_pushed(review_request, commit_data)
            or not is_parent(review_request, commit_data)):
        return

    # Check the change description and only continue if it contains a change
    # to the commit information. Currently change descriptions won't include
    # information about our extra data field, so we'll look for a change to
    # the diff which is mandatory if the commits changed. TODO: Properly use
    # the commit information once we start populating the change description
    # with it.
    #
    # A change description will not exist if this is the first publish of the
    # review request. In that case we know there must be commits since this
    # is a pushed request.
    cd = kwargs.get('changedesc')
    if (cd is not None and ('diff' not in cd.fields_changed
                            or 'added' not in cd.fields_changed['diff'])):
        return

    # We publish both the review repository url as well as the landing
    # ("inbound") repository url. This gives consumers which perform hg
    # operations the option to avoid cloning the review repository, which may
    # be large.
    repo = review_request.repository
    repo_url = repo.path
    landing_repo_url = repo.extra_data.get('landing_repository_url')

    child_rrids = []
    commits = []
    ext_commits = json.loads(commit_data.extra_data.get(COMMITS_KEY, '[]'))

    for rev, rrid in ext_commits:
        child_rrids.append(int(rrid))
        commits.append({
            'rev': rev,
            'review_request_id': int(rrid),
            'diffset_revision': None
        })

    # In order to retrieve the diff revision for each commit we need to fetch
    # their correpsonding child review request.
    review_requests = dict(
        (obj.id, obj)
        for obj in ReviewRequest.objects.filter(pk__in=child_rrids))

    for commit_info in commits:
        # TODO: Every call to get_latest_diffset() makes its own query to the
        # database. It is probably possible to retrieve the diffsets we care
        # about using a single query through Django's ORM, but it's not trivial.
        commit_info['diffset_revision'] = review_requests[
            commit_info['review_request_id']].get_latest_diffset().revision

    msg = base.GenericMessage()
    msg.routing_parts.append('mozreview.commits.published')
    msg.data['parent_review_request_id'] = review_request.id
    msg.data['parent_diffset_revision'] = review_request.get_latest_diffset(
    ).revision
    msg.data['commits'] = commits
    msg.data['repository_url'] = repo_url
    msg.data['landing_repository_url'] = landing_repo_url

    # TODO: Make work with RB localsites.
    msg.data['review_board_url'] = get_server_url()

    publish_message(extension, msg)
예제 #31
0
 def should_render(self, value):
     # Only show this for child review requests as for parent review
     # requests different constituent commits can have different authors.
     # Also, do not show it if it's empty, because review requests created
     # before the author field was introduced will not have this information.
     return not is_parent(self.review_request_details) and value
예제 #32
0
 def should_render(self, value):
     # Only show this for child review requests as for parent review
     # requests different constituent commits can have different authors.
     # Also, do not show it if it's empty, because review requests created
     # before the author field was introduced will not have this information.
     return not is_parent(self.review_request_details) and value
    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, {}
예제 #34
0
def isSquashed(review_request):
    return is_parent(review_request)
예제 #35
0
    def create(self, request, review_request_id, commit_descriptions, *args,
               **kwargs):
        try:
            rr = ReviewRequest.objects.get(pk=review_request_id)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        commit_data = fetch_commit_data(rr)

        if not is_pushed(rr, commit_data) or not is_parent(rr, commit_data):
            logger.error('Failed triggering Autoland because the review '
                         'request is not pushed, or not the parent review '
                         'request.')
            return NOT_PUSHED_PARENT_REVIEW_REQUEST

        enabled = rr.repository.extra_data.get('autolanding_enabled')

        if not enabled:
            return AUTOLAND_CONFIGURATION_ERROR.with_message(
                'Autolanding not enabled.')

        target_repository = rr.repository.extra_data.get(
            'landing_repository_url')
        push_bookmark = rr.repository.extra_data.get('landing_bookmark')

        if not target_repository:
            return AUTOLAND_CONFIGURATION_ERROR.with_message(
                'Autoland has not been configured with a proper landing URL.')

        last_revision = json.loads(
            commit_data.extra_data.get(COMMITS_KEY))[-1][0]
        ext = get_extension_manager().get_enabled_extension(
            'mozreview.extension.MozReviewExtension')

        logger.info('Submitting a request to Autoland for review request '
                    'ID %s for revision %s destination %s' %
                    (review_request_id, last_revision, target_repository))

        autoland_url = ext.get_settings('autoland_url')

        if not autoland_url:
            return AUTOLAND_CONFIGURATION_ERROR

        autoland_user = ext.get_settings('autoland_user')
        autoland_password = ext.get_settings('autoland_password')

        if not autoland_user or not autoland_password:
            return AUTOLAND_CONFIGURATION_ERROR

        pingback_url = autoland_request_update_resource.get_uri(request)
        lock_id = get_autoland_lock_id(rr.id, target_repository, last_revision)

        if not acquire_lock(lock_id):
            return AUTOLAND_REQUEST_IN_PROGRESS

        try:
            response = requests.post(
                autoland_url + '/autoland',
                data=json.dumps({
                    'ldap_username':
                    request.mozreview_profile.ldap_username,
                    'tree':
                    rr.repository.name,
                    'pingback_url':
                    pingback_url,
                    'rev':
                    last_revision,
                    'destination':
                    target_repository,
                    'push_bookmark':
                    push_bookmark,
                    'commit_descriptions':
                    json.loads(commit_descriptions),
                }),
                headers={
                    'content-type': 'application/json',
                },
                timeout=AUTOLAND_REQUEST_TIMEOUT,
                auth=(autoland_user, autoland_password))
        except requests.exceptions.RequestException:
            logger.error('We hit a RequestException when submitting a '
                         'request to Autoland')
            release_lock(lock_id)
            return AUTOLAND_ERROR
        except requests.exceptions.Timeout:
            logger.error('We timed out when submitting a request to '
                         'Autoland')
            release_lock(lock_id)
            return AUTOLAND_TIMEOUT

        if response.status_code != 200:
            release_lock(lock_id)

            try:
                error_message = response.json().get('error')
            except ValueError:
                error_message = response.text

            return AUTOLAND_ERROR, {
                'status_code': response.status_code,
                'message': error_message,
            }

        # We succeeded in scheduling the job.
        try:
            autoland_request_id = int(response.json().get('request_id', 0))
        finally:
            if autoland_request_id is None:
                release_lock(lock_id)
                return AUTOLAND_ERROR, {
                    'status_code': response.status_code,
                    'request_id': None,
                }

        AutolandRequest.objects.create(
            autoland_id=autoland_request_id,
            push_revision=last_revision,
            repository_url=target_repository,
            review_request_id=rr.id,
            user_id=request.user.id,
        )

        AutolandEventLogEntry.objects.create(
            status=AutolandEventLogEntry.REQUESTED,
            autoland_request_id=autoland_request_id)

        self.save_autolandrequest_id('p2rb.autoland', rr, autoland_request_id)

        return 200, {}
예제 #36
0
 def should_render(self, value):
     return not is_parent(self.review_request_details, self.commit_data)
def handle_commits_published(extension=None, **kwargs):
    """Handle sending 'mozreview.commits.published'.

    This message is only sent when the parent review request, in a set of
    pushed review requests, is published with new commit information.

    This is a useful message for consumers who care about new or modified
    commits being published for review.
    """
    review_request = kwargs.get('review_request')

    if review_request is None:
        return

    commit_data = fetch_commit_data(review_request)

    if (not is_pushed(review_request, commit_data) or
            not is_parent(review_request, commit_data)):
        return

    # Check the change description and only continue if it contains a change
    # to the commit information. Currently change descriptions won't include
    # information about our extra data field, so we'll look for a change to
    # the diff which is mandatory if the commits changed. TODO: Properly use
    # the commit information once we start populating the change description
    # with it.
    #
    # A change description will not exist if this is the first publish of the
    # review request. In that case we know there must be commits since this
    # is a pushed request.
    cd = kwargs.get('changedesc')
    if (cd is not None and ('diff' not in cd.fields_changed or
                            'added' not in cd.fields_changed['diff'])):
        return

    # We publish both the review repository url as well as the landing
    # ("inbound") repository url. This gives consumers which perform hg
    # operations the option to avoid cloning the review repository, which may
    # be large.
    repo = review_request.repository
    repo_url = repo.path
    landing_repo_url = repo.extra_data.get('landing_repository_url')

    child_rrids = []
    commits = []
    ext_commits = json.loads(commit_data.extra_data.get(COMMITS_KEY, '[]'))

    for rev, rrid in ext_commits:
        child_rrids.append(int(rrid))
        commits.append({
            'rev': rev,
            'review_request_id': int(rrid),
            'diffset_revision': None
        })

    # In order to retrieve the diff revision for each commit we need to fetch
    # their correpsonding child review request.
    review_requests = dict(
        (obj.id, obj) for obj in
        ReviewRequest.objects.filter(pk__in=child_rrids))

    for commit_info in commits:
        # TODO: Every call to get_latest_diffset() makes its own query to the
        # database. It is probably possible to retrieve the diffsets we care
        # about using a single query through Django's ORM, but it's not trivial.
        commit_info['diffset_revision'] = review_requests[
            commit_info['review_request_id']
        ].get_latest_diffset().revision

    msg = base.GenericMessage()
    msg.routing_parts.append('mozreview.commits.published')
    msg.data['parent_review_request_id'] = review_request.id
    msg.data['parent_diffset_revision'] = review_request.get_latest_diffset().revision
    msg.data['commits'] = commits
    msg.data['repository_url'] = repo_url
    msg.data['landing_repository_url'] = landing_repo_url

    # TODO: Make work with RB localsites.
    msg.data['review_board_url'] = get_server_url()

    publish_message(extension, msg)
예제 #38
0
    def create(self, request, review_request_id, try_syntax, *args, **kwargs):
        try:
            rr = ReviewRequest.objects.get(pk=review_request_id)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        if not try_syntax.startswith('try: '):
            return INVALID_FORM_DATA, {
                'fields': {
                    'try_syntax': ['The provided try syntax was invalid']
                }
            }

        if not is_pushed(rr) or not is_parent(rr):
            logging.error('Failed triggering Autoland because the review '
                          'request is not pushed, or not the parent review '
                          'request.')
            return NOT_PUSHED_PARENT_REVIEW_REQUEST

        if not rr.is_mutable_by(request.user):
            return PERMISSION_DENIED

        target_repository = rr.repository.extra_data.get(
            'try_repository_url')

        if not target_repository:
            return AUTOLAND_CONFIGURATION_ERROR.with_message(
                'Autoland has not been configured with a proper try URL.')

        last_revision = json.loads(rr.extra_data.get('p2rb.commits'))[-1][0]

        ext = get_extension_manager().get_enabled_extension(
            'mozreview.extension.MozReviewExtension')

        logging.info('Submitting a request to Autoland for review request '
                     'ID %s for revision %s '
                     % (review_request_id, last_revision))

        autoland_url = ext.settings.get('autoland_url')
        if not autoland_url:
            return AUTOLAND_CONFIGURATION_ERROR

        autoland_user = ext.settings.get('autoland_user')
        autoland_password = ext.settings.get('autoland_password')

        if not autoland_user or not autoland_password:
            return AUTOLAND_CONFIGURATION_ERROR

        pingback_url = autoland_request_update_resource.get_uri(request)

        logging.info('Telling Autoland to give status updates to %s'
                     % pingback_url)

        lock_id = get_autoland_lock_id(rr.id, target_repository, last_revision)
        if not acquire_lock(lock_id):
            return AUTOLAND_REQUEST_IN_PROGRESS

        try:
            # We use a hard-coded destination here. If we ever open this up
            # to make the destination a parameter to this resource, we need to
            # verify that the destination is in fact an "scm_level_1"
            # repository to ensure that people don't try to land to inbound
            # using this resource.
            response = requests.post(autoland_url + '/autoland',
                data=json.dumps({
                'tree': rr.repository.name,
                'pingback_url': pingback_url,
                'rev': last_revision,
                'destination': TRY_AUTOLAND_DESTINATION,
                'trysyntax': try_syntax,
            }), headers={
                'content-type': 'application/json',
            },
                timeout=AUTOLAND_REQUEST_TIMEOUT,
                auth=(autoland_user, autoland_password))
        except requests.exceptions.RequestException:
            logging.error('We hit a RequestException when submitting a '
                          'request to Autoland')
            release_lock(lock_id)
            return AUTOLAND_ERROR
        except requests.exceptions.Timeout:
            logging.error('We timed out when submitting a request to '
                          'Autoland')
            release_lock(lock_id)
            return AUTOLAND_TIMEOUT

        if response.status_code != 200:
            release_lock(lock_id)
            return AUTOLAND_ERROR, {
                'status_code': response.status_code,
                'message': response.json().get('error'),
            }

        # We succeeded in scheduling the job.
        try:
            autoland_request_id = int(response.json().get('request_id', 0))
        finally:
            if autoland_request_id is None:
                release_lock(lock_id)
                return AUTOLAND_ERROR, {
                    'status_code': response.status_code,
                    'request_id': None,
                }

        autoland_request = AutolandRequest.objects.create(
            autoland_id=autoland_request_id,
            push_revision=last_revision,
            repository_url=target_repository,
            review_request_id=rr.id,
            user_id=request.user.id,
            extra_data=json.dumps({
                'try_syntax': try_syntax
            })
        )

        AutolandEventLogEntry.objects.create(
            status=AutolandEventLogEntry.REQUESTED,
            autoland_request_id=autoland_request_id)

        self.save_autolandrequest_id('p2rb.autoland_try', rr,
            autoland_request_id)

        return 200, {}
    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, {}
예제 #40
0
 def should_render(self, value):
     return (is_pushed(self.review_request_details) and
             is_parent(self.review_request_details) and
             False) # TODO: Remove and render hg web link to the base commit.
예제 #41
0
 def should_render(self, value):
     return not is_parent(self.review_request_details, self.commit_data)
예제 #42
0
    def create(self, request, review_request_id, try_syntax, *args, **kwargs):
        try:
            rr = ReviewRequest.objects.get(pk=review_request_id)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        if not try_syntax.startswith('try: '):
            return INVALID_FORM_DATA, {
                'fields': {
                    'try_syntax': ['The provided try syntax was invalid']
                }
            }

        commit_data = fetch_commit_data(rr)

        if not is_pushed(rr, commit_data) or not is_parent(rr, commit_data):
            logger.error('Failed triggering Autoland because the review '
                         'request is not pushed, or not the parent review '
                         'request.')
            return NOT_PUSHED_PARENT_REVIEW_REQUEST

        enabled = rr.repository.extra_data.get('autolanding_to_try_enabled')

        if not enabled:
            return AUTOLAND_CONFIGURATION_ERROR.with_message(
                'Autolanding to try not enabled.')

        target_repository = rr.repository.extra_data.get('try_repository_url')

        if not target_repository:
            return AUTOLAND_CONFIGURATION_ERROR.with_message(
                'Autoland has not been configured with a proper try URL.')

        last_revision = json.loads(
            commit_data.extra_data.get(COMMITS_KEY))[-1][0]
        ext = get_extension_manager().get_enabled_extension(
            'mozreview.extension.MozReviewExtension')

        logger.info('Submitting a request to Autoland for review request '
                    'ID %s for revision %s destination try' %
                    (review_request_id, last_revision))

        autoland_url = ext.get_settings('autoland_url')

        if not autoland_url:
            return AUTOLAND_CONFIGURATION_ERROR

        autoland_user = ext.get_settings('autoland_user')
        autoland_password = ext.get_settings('autoland_password')

        if not autoland_user or not autoland_password:
            return AUTOLAND_CONFIGURATION_ERROR

        pingback_url = autoland_request_update_resource.get_uri(request)

        lock_id = get_autoland_lock_id(rr.id, target_repository, last_revision)

        if not acquire_lock(lock_id):
            return AUTOLAND_REQUEST_IN_PROGRESS

        try:
            # We use a hard-coded destination here. If we ever open this up
            # to make the destination a parameter to this resource, we need to
            # verify that the destination is in fact an "scm_level_1"
            # repository to ensure that people don't try to land to inbound
            # using this resource.
            response = requests.post(
                autoland_url + '/autoland',
                data=json.dumps({
                    'ldap_username': request.mozreview_profile.ldap_username,
                    'tree': rr.repository.name,
                    'pingback_url': pingback_url,
                    'rev': last_revision,
                    'destination': TRY_AUTOLAND_DESTINATION,
                    'trysyntax': try_syntax,
                }),
                headers={
                    'content-type': 'application/json',
                },
                timeout=AUTOLAND_REQUEST_TIMEOUT,
                auth=(autoland_user, autoland_password))
        except requests.exceptions.RequestException:
            logger.error('We hit a RequestException when submitting a '
                         'request to Autoland')
            release_lock(lock_id)
            return AUTOLAND_ERROR
        except requests.exceptions.Timeout:
            logger.error('We timed out when submitting a request to '
                         'Autoland')
            release_lock(lock_id)
            return AUTOLAND_TIMEOUT

        if response.status_code != 200:
            release_lock(lock_id)
            return AUTOLAND_ERROR, {
                'status_code': response.status_code,
                'message': response.json().get('error'),
            }

        # We succeeded in scheduling the job.
        try:
            autoland_request_id = int(response.json().get('request_id', 0))
        finally:
            if autoland_request_id is None:
                release_lock(lock_id)
                return AUTOLAND_ERROR, {
                    'status_code': response.status_code,
                    'request_id': None,
                }

        AutolandRequest.objects.create(autoland_id=autoland_request_id,
                                       push_revision=last_revision,
                                       repository_url=target_repository,
                                       review_request_id=rr.id,
                                       user_id=request.user.id,
                                       extra_data=json.dumps(
                                           {'try_syntax': try_syntax}))

        AutolandEventLogEntry.objects.create(
            status=AutolandEventLogEntry.REQUESTED,
            autoland_request_id=autoland_request_id)

        self.save_autolandrequest_id('p2rb.autoland_try', rr,
                                     autoland_request_id)

        return 200, {}
예제 #43
0
    def create(self, request, review_request_id, *args, **kwargs):
        try:
            rr = ReviewRequest.objects.get(pk=review_request_id)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        if not is_pushed(rr) or not is_parent(rr):
            logging.error(
                "Failed triggering Autoland because the review "
                "request is not pushed, or not the parent review "
                "request."
            )
            return NOT_PUSHED_PARENT_REVIEW_REQUEST

        if not rr.is_mutable_by(request.user):
            return PERMISSION_DENIED

        last_revision = json.loads(rr.extra_data.get("p2rb.commits"))[-1][0]

        ext = get_extension_manager().get_enabled_extension("mozreview.extension.MozReviewExtension")

        logging.info(
            "Submitting a request to Autoland for review request "
            "ID %s for revision %s " % (review_request_id, last_revision)
        )

        autoland_url = ext.settings.get("autoland_url")
        if not autoland_url:
            return AUTOLAND_CONFIGURATION_ERROR

        autoland_user = ext.settings.get("autoland_user")
        autoland_password = ext.settings.get("autoland_password")

        if not autoland_user or not autoland_password:
            return AUTOLAND_CONFIGURATION_ERROR

        pingback_url = autoland_request_update_resource.get_uri(request)

        logging.info("Telling Autoland to give status updates to %s" % pingback_url)

        try:
            # Rather than hard coding the destination it would make sense
            # to extract it from metadata about the repository. That will have
            # to wait until we fix Bug 1168486.
            response = requests.post(
                autoland_url + "/autoland",
                data=json.dumps(
                    {
                        "tree": rr.repository.name,
                        "pingback_url": pingback_url,
                        "rev": last_revision,
                        "destination": INBOUND_AUTOLAND_DESTINATION,
                    }
                ),
                headers={"content-type": "application/json"},
                timeout=AUTOLAND_REQUEST_TIMEOUT,
                auth=(autoland_user, autoland_password),
            )
        except requests.exceptions.RequestException:
            logging.error("We hit a RequestException when submitting a " "request to Autoland")
            return AUTOLAND_ERROR
        except requests.exceptions.Timeout:
            logging.error("We timed out when submitting a request to " "Autoland")
            return AUTOLAND_TIMEOUT

        if response.status_code != 200:
            return AUTOLAND_ERROR, {"status_code": response.status_code, "message": response.json().get("error")}

        # We succeeded in scheduling the job.
        try:
            autoland_request_id = int(response.json().get("request_id", 0))
        finally:
            if autoland_request_id is None:
                return AUTOLAND_ERROR, {"status_code": response.status_code, "request_id": None}

        autoland_request = AutolandRequest.objects.create(
            autoland_id=autoland_request_id,
            push_revision=last_revision,
            review_request_id=rr.id,
            user_id=request.user.id,
        )

        AutolandEventLogEntry.objects.create(
            status=AutolandEventLogEntry.REQUESTED, autoland_request_id=autoland_request_id
        )

        self.save_autolandrequest_id("p2rb.autoland_inbound", rr, autoland_request_id)

        return 200, {}
예제 #44
0
    def create(self, request, review_request_id, commit_descriptions, *args, **kwargs):
        try:
            rr = ReviewRequest.objects.get(pk=review_request_id)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        commit_data = fetch_commit_data(rr)

        if not is_pushed(rr, commit_data) or not is_parent(rr, commit_data):
            logger.error(
                "Failed triggering Autoland because the review "
                "request is not pushed, or not the parent review "
                "request."
            )
            return NOT_PUSHED_PARENT_REVIEW_REQUEST

        target_repository = rr.repository.extra_data.get("landing_repository_url")
        push_bookmark = rr.repository.extra_data.get("landing_bookmark")

        if not target_repository:
            return AUTOLAND_CONFIGURATION_ERROR.with_message(
                "Autoland has not been configured with a proper landing URL."
            )

        last_revision = json.loads(commit_data.extra_data.get(COMMITS_KEY))[-1][0]

        ext = get_extension_manager().get_enabled_extension("mozreview.extension.MozReviewExtension")

        logger.info(
            "Submitting a request to Autoland for review request "
            "ID %s for revision %s destination %s" % (review_request_id, last_revision, target_repository)
        )

        autoland_url = ext.get_settings("autoland_url")
        if not autoland_url:
            return AUTOLAND_CONFIGURATION_ERROR

        autoland_user = ext.get_settings("autoland_user")
        autoland_password = ext.get_settings("autoland_password")

        if not autoland_user or not autoland_password:
            return AUTOLAND_CONFIGURATION_ERROR

        pingback_url = autoland_request_update_resource.get_uri(request)

        lock_id = get_autoland_lock_id(rr.id, target_repository, last_revision)
        if not acquire_lock(lock_id):
            return AUTOLAND_REQUEST_IN_PROGRESS
        try:
            response = requests.post(
                autoland_url + "/autoland",
                data=json.dumps(
                    {
                        "ldap_username": request.mozreview_profile.ldap_username,
                        "tree": rr.repository.name,
                        "pingback_url": pingback_url,
                        "rev": last_revision,
                        "destination": target_repository,
                        "push_bookmark": push_bookmark,
                        "commit_descriptions": json.loads(commit_descriptions),
                    }
                ),
                headers={"content-type": "application/json"},
                timeout=AUTOLAND_REQUEST_TIMEOUT,
                auth=(autoland_user, autoland_password),
            )
        except requests.exceptions.RequestException:
            logger.error("We hit a RequestException when submitting a " "request to Autoland")
            release_lock(lock_id)
            return AUTOLAND_ERROR
        except requests.exceptions.Timeout:
            logger.error("We timed out when submitting a request to " "Autoland")
            release_lock(lock_id)
            return AUTOLAND_TIMEOUT

        if response.status_code != 200:
            release_lock(lock_id)
            return AUTOLAND_ERROR, {"status_code": response.status_code, "message": response.json().get("error")}

        # We succeeded in scheduling the job.
        try:
            autoland_request_id = int(response.json().get("request_id", 0))
        finally:
            if autoland_request_id is None:
                release_lock(lock_id)
                return AUTOLAND_ERROR, {"status_code": response.status_code, "request_id": None}

        autoland_request = AutolandRequest.objects.create(
            autoland_id=autoland_request_id,
            push_revision=last_revision,
            repository_url=target_repository,
            review_request_id=rr.id,
            user_id=request.user.id,
        )

        AutolandEventLogEntry.objects.create(
            status=AutolandEventLogEntry.REQUESTED, autoland_request_id=autoland_request_id
        )

        self.save_autolandrequest_id("p2rb.autoland", rr, autoland_request_id)

        return 200, {}
예제 #45
0
 def should_render(self, value):
     return (is_pushed(self.review_request_details) and
             is_parent(self.review_request_details) and
             False) # TODO: Remove and render hg web link to the base commit.
예제 #46
0
    def create(self, request, review_request_id, try_syntax, *args, **kwargs):
        try:
            rr = ReviewRequest.objects.get(pk=review_request_id)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        if not try_syntax.startswith("try: "):
            return INVALID_FORM_DATA, {"fields": {"try_syntax": ["The provided try syntax was invalid"]}}

        commit_data = fetch_commit_data(rr)

        if not is_pushed(rr, commit_data) or not is_parent(rr, commit_data):
            logger.error(
                "Failed triggering Autoland because the review "
                "request is not pushed, or not the parent review "
                "request."
            )
            return NOT_PUSHED_PARENT_REVIEW_REQUEST

        target_repository = rr.repository.extra_data.get("try_repository_url")

        if not target_repository:
            return AUTOLAND_CONFIGURATION_ERROR.with_message("Autoland has not been configured with a proper try URL.")

        last_revision = json.loads(commit_data.extra_data.get(COMMITS_KEY))[-1][0]

        ext = get_extension_manager().get_enabled_extension("mozreview.extension.MozReviewExtension")

        logger.info(
            "Submitting a request to Autoland for review request "
            "ID %s for revision %s destination try" % (review_request_id, last_revision)
        )

        autoland_url = ext.get_settings("autoland_url")
        if not autoland_url:
            return AUTOLAND_CONFIGURATION_ERROR

        autoland_user = ext.get_settings("autoland_user")
        autoland_password = ext.get_settings("autoland_password")

        if not autoland_user or not autoland_password:
            return AUTOLAND_CONFIGURATION_ERROR

        pingback_url = autoland_request_update_resource.get_uri(request)

        lock_id = get_autoland_lock_id(rr.id, target_repository, last_revision)
        if not acquire_lock(lock_id):
            return AUTOLAND_REQUEST_IN_PROGRESS

        try:
            # We use a hard-coded destination here. If we ever open this up
            # to make the destination a parameter to this resource, we need to
            # verify that the destination is in fact an "scm_level_1"
            # repository to ensure that people don't try to land to inbound
            # using this resource.
            response = requests.post(
                autoland_url + "/autoland",
                data=json.dumps(
                    {
                        "ldap_username": request.mozreview_profile.ldap_username,
                        "tree": rr.repository.name,
                        "pingback_url": pingback_url,
                        "rev": last_revision,
                        "destination": TRY_AUTOLAND_DESTINATION,
                        "trysyntax": try_syntax,
                    }
                ),
                headers={"content-type": "application/json"},
                timeout=AUTOLAND_REQUEST_TIMEOUT,
                auth=(autoland_user, autoland_password),
            )
        except requests.exceptions.RequestException:
            logger.error("We hit a RequestException when submitting a " "request to Autoland")
            release_lock(lock_id)
            return AUTOLAND_ERROR
        except requests.exceptions.Timeout:
            logger.error("We timed out when submitting a request to " "Autoland")
            release_lock(lock_id)
            return AUTOLAND_TIMEOUT

        if response.status_code != 200:
            release_lock(lock_id)
            return AUTOLAND_ERROR, {"status_code": response.status_code, "message": response.json().get("error")}

        # We succeeded in scheduling the job.
        try:
            autoland_request_id = int(response.json().get("request_id", 0))
        finally:
            if autoland_request_id is None:
                release_lock(lock_id)
                return AUTOLAND_ERROR, {"status_code": response.status_code, "request_id": None}

        autoland_request = AutolandRequest.objects.create(
            autoland_id=autoland_request_id,
            push_revision=last_revision,
            repository_url=target_repository,
            review_request_id=rr.id,
            user_id=request.user.id,
            extra_data=json.dumps({"try_syntax": try_syntax}),
        )

        AutolandEventLogEntry.objects.create(
            status=AutolandEventLogEntry.REQUESTED, autoland_request_id=autoland_request_id
        )

        self.save_autolandrequest_id("p2rb.autoland_try", rr, autoland_request_id)

        return 200, {}
예제 #47
0
    def create(self, request, review_request_id, *args, **kwargs):
        try:
            rr = ReviewRequest.objects.get(pk=review_request_id)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        if not is_pushed(rr) or not is_parent(rr):
            logging.error('Failed triggering Autoland because the review '
                          'request is not pushed, or not the parent review '
                          'request.')
            return NOT_PUSHED_PARENT_REVIEW_REQUEST

        if not rr.is_mutable_by(request.user):
            return PERMISSION_DENIED

        target_repository = rr.repository.extra_data.get(
            'landing_repository_url')
        push_bookmark = rr.repository.extra_data.get('landing_bookmark')

        if not target_repository:
            return AUTOLAND_CONFIGURATION_ERROR.with_message(
                'Autoland has not been configured with a proper landing URL.')

        last_revision = json.loads(rr.extra_data.get('p2rb.commits'))[-1][0]

        ext = get_extension_manager().get_enabled_extension(
            'mozreview.extension.MozReviewExtension')

        logging.info('Submitting a request to Autoland for review request '
                     'ID %s for revision %s '
                     % (review_request_id, last_revision))

        autoland_url = ext.settings.get('autoland_url')
        if not autoland_url:
            return AUTOLAND_CONFIGURATION_ERROR

        autoland_user = ext.settings.get('autoland_user')
        autoland_password = ext.settings.get('autoland_password')

        if not autoland_user or not autoland_password:
            return AUTOLAND_CONFIGURATION_ERROR

        pingback_url = autoland_request_update_resource.get_uri(request)

        logging.info('Telling Autoland to give status updates to %s'
                     % pingback_url)

        try:
            # Rather than hard coding the destination it would make sense
            # to extract it from metadata about the repository. That will have
            # to wait until we fix Bug 1168486.
            response = requests.post(autoland_url + '/autoland',
                data=json.dumps({
                'tree': rr.repository.name,
                'pingback_url': pingback_url,
                'rev': last_revision,
                'destination': target_repository,
                'push_bookmark': push_bookmark,
            }), headers={
                'content-type': 'application/json',
            },
                timeout=AUTOLAND_REQUEST_TIMEOUT,
                auth=(autoland_user, autoland_password))
        except requests.exceptions.RequestException:
            logging.error('We hit a RequestException when submitting a '
                          'request to Autoland')
            return AUTOLAND_ERROR
        except requests.exceptions.Timeout:
            logging.error('We timed out when submitting a request to '
                          'Autoland')
            return AUTOLAND_TIMEOUT

        if response.status_code != 200:
            return AUTOLAND_ERROR, {
                'status_code': response.status_code,
                'message': response.json().get('error'),
            }

        # We succeeded in scheduling the job.
        try:
            autoland_request_id = int(response.json().get('request_id', 0))
        finally:
            if autoland_request_id is None:
                return AUTOLAND_ERROR, {
                    'status_code': response.status_code,
                    'request_id': None,
                }

        autoland_request = AutolandRequest.objects.create(
            autoland_id=autoland_request_id,
            push_revision=last_revision,
            repository_url=target_repository,
            review_request_id=rr.id,
            user_id=request.user.id,
        )

        AutolandEventLogEntry.objects.create(
            status=AutolandEventLogEntry.REQUESTED,
            autoland_request_id=autoland_request_id)

        self.save_autolandrequest_id('p2rb.autoland', rr,
            autoland_request_id)

        return 200, {}
예제 #48
0
    def initialize(self):
        initialize_pulse_handlers(self)

        URLHook(self,
                patterns('', url(r'^mozreview/', include('mozreview.urls'))))

        HeaderDropdownActionHook(self, actions=[{
            'label': 'MozReview',
            'items': [
                {
                    'label': 'User Guide',
                    'url': 'https://mozilla-version-control-tools.readthedocs.org/en/latest/mozreview-user.html',
                },
                {
                    'label': 'Mercurial for Mozillians',
                    'url': 'https://mozilla-version-control-tools.readthedocs.org/en/latest/hgmozilla/index.html',
                },
                {
                    'label': 'Hacking MozReview',
                    'url': 'https://mozilla-version-control-tools.readthedocs.org/en/latest/hacking-mozreview.html',
                },
                {
                    'label': 'File a Bug',
                    'url': 'https://bugzilla.mozilla.org/enter_bug.cgi?product=Developer%20Services&component=MozReview',
                },
            ],
        }])

        ReviewRequestDropdownActionHook(self, actions=[
        {
            'label': 'Automation',
            'id': 'automation-menu',
            'items': [
                {
                    'id': 'autoland-try-trigger',
                    'label': 'Trigger a Try Build',
                    'url': '#',
                },
                {
                    'id': 'autoland-trigger',
                    'label': 'Land Commits',
                    'url': '#',
                },
            ],
        },
        ])

        # Hide fields from all review requests that are not used by Mozilla
        # developers.
        main_fieldset = get_review_request_fieldset('main')
        testing_done_field = get_review_request_field('testing_done')
        if testing_done_field:
            main_fieldset.remove_field(testing_done_field)

        info_fieldset = get_review_request_fieldset('info')
        for field_name in ('branch', 'depends_on', 'blocks'):
            field = get_review_request_field(field_name)
            if field:
                info_fieldset.remove_field(field)

        # We "monkey patch" (yes, I feel dirty) the should_render method on
        # the description field so that it is not rendered for parent review
        # requests.
        description_field = get_review_request_field('description')
        if description_field:
            description_field.should_render = (lambda self, value:
                not is_parent(self.review_request_details))

        # All of our review request styling is injected via
        # review-stylings-css, which in turn loads the review.css static
        # bundle.
        TemplateHook(self, 'base-css', 'mozreview/review-stylings-css.html',
                     apply_to=review_request_url_names)
        TemplateHook(self, 'base-css', 'mozreview/viewdiff-stylings-css.html',
                     apply_to=diffviewer_url_names)
        TemplateHook(self, 'base-scripts-post',
                     'mozreview/review-scripts-js.html',
                     apply_to=review_request_url_names)
        TemplateHook(self, 'base-extrahead',
                     'mozreview/base-extrahead-login-form.html',
                     apply_to=['login'])
        TemplateHook(self, 'before-login-form',
                     'mozreview/before-login-form.html', apply_to=['login'])
        TemplateHook(self, 'after-login-form',
                     'mozreview/after-login-form.html', apply_to=['login'])
        TemplateHook(self, 'base-after-content',
                     'mozreview/scm_level.html')
        TemplateHook(self, 'base-after-content',
                     'mozreview/repository.html')

        ReviewRequestFieldsHook(self, 'main', [CommitsListField])
        # This forces the Commits field to be the top item.
        main_fieldset.field_classes.insert(0,
                                           main_fieldset.field_classes.pop())

        # The above hack forced Commits at the top, but the rest of these
        # fields are fine below the Description.
        ReviewRequestFieldsHook(self, 'main', [CombinedReviewersField])
        ReviewRequestFieldsHook(self, 'main', [TryField])
        ReviewRequestFieldsHook(self, 'main', [BaseCommitField])
        ReviewRequestFieldsHook(self, 'main', [FileDiffReviewerField])

        # We want pull to appear first as it is the more robust way of
        # retrieving changesets.
        ReviewRequestFieldsHook(self, 'info', [PullCommitField])
        ReviewRequestFieldsHook(self, 'info', [ImportCommitField])

        # Use a custom method to calculate a review approval state.
        MozReviewApprovalHook(self)

        SignalHook(self, post_save, self.on_draft_changed,
                   sender=ReviewRequestDraft)

        HostingServiceHook(self, HMORepository)

        URLHook(self, patterns('',
            url(r'^import-pullrequest/(?P<user>.+)/(?P<repo>.+)/(?P<pullrequest>\d+)/$',
            import_pullrequest, name='import_pullrequest')))
예제 #49
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_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'])
예제 #51
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:
        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()
예제 #52
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()
예제 #53
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'])
예제 #54
0
    def initialize(self):
        AuthBackendHook(self, BugzillaBackend)

        self.original_opcode_generator = get_diff_opcode_generator_class()
        set_diff_opcode_generator_class(NoFilterDiffOpcodeGenerator)

        initialize_pulse_handlers(self)

        URLHook(self,
                patterns('', url(r'^mozreview/', include('mozreview.urls'))))

        HeaderDropdownActionHook(
            self,
            actions=[{
                'id':
                'nav-mozreview-menu',
                'label':
                'MozReview',
                'items': [
                    {
                        'label':
                        'User Guide',
                        'url':
                        'https://mozilla-version-control-tools.readthedocs.io/en/latest/mozreview-user.html',
                    },
                    {
                        'label':
                        'Mercurial for Mozillians',
                        'url':
                        'https://mozilla-version-control-tools.readthedocs.io/en/latest/hgmozilla/index.html',
                    },
                    {
                        'label':
                        'Hacking MozReview',
                        'url':
                        'https://mozilla-version-control-tools.readthedocs.io/en/latest/hacking-mozreview.html',
                    },
                    {
                        'label':
                        'File a Bug',
                        'url':
                        'https://bugzilla.mozilla.org/enter_bug.cgi?product=MozReview&component=General',
                    },
                ],
            }])

        review_request_dropdown_actions = [
            {
                'label':
                'Automation',
                'id':
                'automation-menu',
                'items': [
                    {
                        'id': 'autoland-try-trigger',
                        'label': 'Trigger a Try Build',
                        'url': '#',
                    },
                    {
                        'id': 'autoland-trigger',
                        'label': 'Land Commits',
                        'url': '#',
                    },
                ],
            },
        ]

        ReviewRequestDropdownActionHook(
            self, actions=review_request_dropdown_actions)
        DiffViewerDropdownActionHook(self,
                                     actions=review_request_dropdown_actions)

        # Hide fields from all review requests that are not used by Mozilla
        # developers.
        main_fieldset = get_review_request_fieldset('main')
        testing_done_field = get_review_request_field('testing_done')
        if testing_done_field:
            main_fieldset.remove_field(testing_done_field)

        info_fieldset = get_review_request_fieldset('info')
        for field_name in ('branch', 'depends_on', 'blocks'):
            field = get_review_request_field(field_name)
            if field:
                info_fieldset.remove_field(field)

        # We "monkey patch" (yes, I feel dirty) the should_render method on
        # the description field so that it is not rendered for parent review
        # requests.
        description_field = get_review_request_field('description')
        if description_field:
            description_field.should_render = (
                lambda self, value: not is_parent(self.review_request_details))

        # All of our review request styling is injected via
        # review-stylings-css, which in turn loads the review.css static
        # bundle.
        TemplateHook(self,
                     'base-css',
                     'mozreview/review-stylings-css.html',
                     apply_to=review_request_url_names)
        TemplateHook(self,
                     'base-css',
                     'mozreview/viewdiff-stylings-css.html',
                     apply_to=diffviewer_url_names)
        TemplateHook(
            self,
            'base-css',
            'mozreview/admin-stylings-css.html',
            apply_to=['reviewboard.extensions.views.configure_extension'])
        TemplateHook(self,
                     'base-scripts-post',
                     'mozreview/review-scripts-js.html',
                     apply_to=review_request_url_names)
        TemplateHook(self,
                     'base-extrahead',
                     'mozreview/base-extrahead-login-form.html',
                     apply_to=['login'])
        TemplateHook(self,
                     'before-login-form',
                     'mozreview/before-login-form.html',
                     apply_to=['login'])
        TemplateHook(self,
                     'after-login-form',
                     'mozreview/after-login-form.html',
                     apply_to=['login'])
        TemplateHook(self, 'base-after-content', 'mozreview/user-data.html')
        TemplateHook(self, 'base-after-content', 'mozreview/repository.html')
        TemplateHook(self,
                     'base-after-content',
                     'mozreview/user_review_flag.html',
                     apply_to=review_request_url_names)

        ReviewRequestFieldsHook(self, 'main', [CommitsListField])
        # This forces the Commits field to be the top item.
        main_fieldset.field_classes.insert(0,
                                           main_fieldset.field_classes.pop())

        # The above hack forced Commits at the top, but the rest of these
        # fields are fine below the Description.
        ReviewRequestFieldsHook(self, 'main', [CombinedReviewersField])
        ReviewRequestFieldsHook(self, 'main', [TryField])
        ReviewRequestFieldsHook(self, 'main', [BaseCommitField])
        ReviewRequestFieldsHook(self, 'main', [FileDiffReviewerField])

        ReviewRequestFieldsHook(self, 'info', [CommitAuthorField])
        # We want pull to appear first as it is the more robust way of
        # retrieving changesets.
        ReviewRequestFieldsHook(self, 'info', [PullCommitField])
        ReviewRequestFieldsHook(self, 'info', [ImportCommitField])

        # Use a custom method to calculate a review approval state.
        MozReviewApprovalHook(self)

        # Instantiate the various signal handlers
        initialize_signal_handlers(self)

        HostingServiceHook(self, HMORepository)
        HostingServiceHook(self, BMOBugTracker)