Esempio n. 1
0
    def create(self, request, extra_fields={}, local_site=None, *args,
               **kwargs):
        """Creates a new diff by parsing an uploaded diff file.

        This will implicitly create the new Review Request draft, which can
        be updated separately and then published.

        This accepts a unified diff file, validates it, and stores it along
        with the draft of a review request. The new diff will have a revision
        of 0.

        A parent diff can be uploaded along with the main diff. A parent diff
        is a diff based on an existing commit in the repository, which will
        be applied before the main diff. The parent diff will not be included
        in the diff viewer. It's useful when developing a change based on a
        branch that is not yet committed. In this case, a parent diff of the
        parent branch would be provided along with the diff of the new commit,
        and only the new commit will be shown.

        It is expected that the client will send the data as part of a
        :mimetype:`multipart/form-data` mimetype. The main diff's name and
        content would be stored in the ``path`` field. If a parent diff is
        provided, its name and content would be stored in the
        ``parent_diff_path`` field.

        An example of this would be::

            -- SoMe BoUnDaRy
            Content-Disposition: form-data; name=path; filename="foo.diff"

            <Unified Diff Content Here>
            -- SoMe BoUnDaRy --

        Extra data can be stored later lookup. See
        :ref:`webapi2.0-extra-data` for more information.
        """
        # Prevent a circular dependency, as ReviewRequestDraftResource
        # needs DraftDiffResource, which needs DiffResource.
        from reviewboard.webapi.resources.review_request_draft import \
            ReviewRequestDraftResource

        try:
            review_request = \
                resources.review_request.get_object(request, *args, **kwargs)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        if not review_request.is_mutable_by(request.user):
            return self.get_no_access_error(request)

        if review_request.repository is None:
            return INVALID_ATTRIBUTE, {
                'reason': 'This review request was created as attachments-'
                          'only, with no repository.',
            }
        elif review_request.created_with_history:
            assert dvcs_feature.is_enabled(request=request)

            if 'path' in request.FILES:
                return INVALID_FORM_DATA, {
                    'reason': (
                        'This review request was created with support for '
                        'multiple commits.\n'
                        '\n'
                        'Create an empty diff revision and upload commits to '
                        'that instead.'
                    ),
                }

            diffset = DiffSet.objects.create_empty(
                repository=review_request.repository,
                base_commit_id=request.POST.get('base_commit_id'))
            diffset.update_revision_from_history(
                review_request.diffset_history)
            diffset.save(update_fields=('revision',))
        else:
            form_data = request.POST.copy()
            form = UploadDiffForm(review_request,
                                  data=form_data,
                                  files=request.FILES,
                                  request=request)

            if not form.is_valid():
                return INVALID_FORM_DATA, {
                    'fields': self._get_form_errors(form),
                }

            try:
                diffset = form.create()
            except FileNotFoundError as e:
                return REPO_FILE_NOT_FOUND, {
                    'file': e.path,
                    'revision': six.text_type(e.revision)
                }
            except EmptyDiffError as e:
                return DIFF_EMPTY
            except DiffTooBigError as e:
                return DIFF_TOO_BIG, {
                    'reason': six.text_type(e),
                    'max_size': e.max_diff_size,
                }
            except Exception as e:
                # This could be very wrong, but at least they'll see the error.
                # We probably want a new error type for this.
                logging.error("Error uploading new diff: %s", e, exc_info=1,
                              request=request)

                return INVALID_FORM_DATA, {
                    'fields': {
                        'path': [six.text_type(e)]
                    }
                }

        discarded_diffset = None

        try:
            draft = review_request.draft.get()

            if draft.diffset and draft.diffset != diffset:
                discarded_diffset = draft.diffset
        except ReviewRequestDraft.DoesNotExist:
            try:
                draft = ReviewRequestDraftResource.prepare_draft(
                    request, review_request)
            except PermissionDenied:
                return self.get_no_access_error(request)

        draft.diffset = diffset

        # We only want to add default reviewers the first time.  Was bug 318.
        if review_request.can_add_default_reviewers():
            draft.add_default_reviewers()

        draft.save()

        if extra_fields:
            try:
                self.import_extra_data(diffset, diffset.extra_data,
                                       extra_fields)
            except ImportExtraDataError as e:
                return e.error_payload

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

        if discarded_diffset:
            discarded_diffset.delete()

        # E-mail gets sent when the draft is saved.

        return 201, {
            self.item_result_key: diffset,
        }
Esempio n. 2
0
    def update(self, request, status=None, changenum=None, commit_id=None,
               description=None, *args, **kwargs):
        """Updates the status of the review request.

        The only supported update to a review request's resource is to change
        the status, the associated server-side, change number, or to update
        information from the existing change number.

        The status can be set in order to close the review request as
        discarded or submitted, or to reopen as pending.

        The change number can either be changed to a new number, or the
        current change number can be passed. In either case, a new draft will
        be created or an existing one updated to include information from
        the server based on the change number.

        Changes to a review request's fields, such as the summary or the
        list of reviewers, is made on the Review Request Draft resource.
        This can be accessed through the ``draft`` link. Only when that
        draft is published will the changes end up back in this resource.
        """
        try:
            review_request = \
                resources.review_request.get_object(request, *args, **kwargs)
        except ObjectDoesNotExist:
            return DOES_NOT_EXIST

        if not self.has_modify_permissions(request, review_request):
            return self._no_access_error(request.user)

        if (status is not None and
            (review_request.status != string_to_status(status) or
             review_request.status != ReviewRequest.PENDING_REVIEW)):
            try:
                if status in self._close_type_map:
                    review_request.close(self._close_type_map[status],
                                         request.user, description)
                elif status == 'pending':
                    review_request.reopen(request.user)
                else:
                    raise AssertionError("Code path for invalid status '%s' "
                                         "should never be reached." % status)
            except PermissionError:
                return self._no_access_error(request.user)

        if changenum is not None and commit_id is None:
            commit_id = str(changenum)

        if commit_id is not None:
            if commit_id != review_request.commit:
                review_request.update_commit_id(commit_id, request.user)

            try:
                draft = ReviewRequestDraftResource.prepare_draft(
                    request, review_request)
            except PermissionDenied:
                return PERMISSION_DENIED

            try:
                draft.update_from_commit_id(commit_id)
            except InvalidChangeNumberError:
                return INVALID_CHANGE_NUMBER

            draft.save()
            review_request.reopen()

        return 200, {
            self.item_result_key: review_request,
        }
Esempio n. 3
0
    def update(self, request, status=None, changenum=None, description=None,
               extra_fields={}, *args, **kwargs):
        """Updates the status of the review request.

        The only supported update to a review request's resource is to change
        the status, the associated server-side, change number, or to update
        information from the existing change number.

        The status can be set in order to close the review request as
        discarded or submitted, or to reopen as pending.

        For Perforce, a change number can either be changed to a new number, or
        the current change number can be passed. In either case, a new draft
        will be created or an existing one updated to include information from
        the server based on the change number. This behavior is deprecated,
        and instead, the commit_id field should be set on the draft.

        Changes to a review request's fields, such as the summary or the
        list of reviewers, is made on the Review Request Draft resource.
        This can be accessed through the ``draft`` link. Only when that
        draft is published will the changes end up back in this resource.

        Extra data can be stored on the review request for later lookup by
        passing ``extra_data.key_name=value``. The ``key_name`` and ``value``
        can be any valid strings. Passing a blank ``value`` will remove the
        key. The ``extra_data.`` prefix is required.
        """
        try:
            review_request = \
                resources.review_request.get_object(request, *args, **kwargs)
        except ObjectDoesNotExist:
            return DOES_NOT_EXIST

        is_mutating_field = (
            changenum is not None or
            extra_fields
        )

        if ((is_mutating_field and
             not self.has_modify_permissions(request, review_request)) or
            (status is not None and
             not review_request.is_status_mutable_by(request.user))):
            return self._no_access_error(request.user)

        if (status is not None and
            (review_request.status != string_to_status(status) or
             review_request.status != ReviewRequest.PENDING_REVIEW)):
            try:
                if status in self._close_type_map:
                    review_request.close(self._close_type_map[status],
                                         request.user, description)
                elif status == 'pending':
                    review_request.reopen(request.user)
                else:
                    raise AssertionError("Code path for invalid status '%s' "
                                         "should never be reached." % status)
            except PermissionError:
                return self._no_access_error(request.user)

        # Preserve the old changenum behavior.
        if changenum is not None:
            if changenum != review_request.changenum:
                review_request.commit = changenum

            try:
                draft = ReviewRequestDraftResource.prepare_draft(
                    request, review_request)
            except PermissionDenied:
                return PERMISSION_DENIED

            try:
                draft.update_from_commit_id(six.text_type(changenum))
            except InvalidChangeNumberError:
                return INVALID_CHANGE_NUMBER
            except EmptyChangeSetError:
                return EMPTY_CHANGESET

            draft.save()
            review_request.reopen()

        if extra_fields:
            self._import_extra_data(review_request.extra_data, extra_fields)
            review_request.save(update_fields=['extra_data'])

        return 200, {
            self.item_result_key: review_request,
        }
Esempio n. 4
0
    def create(self,
               request,
               extra_fields={},
               local_site=None,
               *args,
               **kwargs):
        """Creates a new diff by parsing an uploaded diff file.

        This will implicitly create the new Review Request draft, which can
        be updated separately and then published.

        This accepts a unified diff file, validates it, and stores it along
        with the draft of a review request. The new diff will have a revision
        of 0.

        A parent diff can be uploaded along with the main diff. A parent diff
        is a diff based on an existing commit in the repository, which will
        be applied before the main diff. The parent diff will not be included
        in the diff viewer. It's useful when developing a change based on a
        branch that is not yet committed. In this case, a parent diff of the
        parent branch would be provided along with the diff of the new commit,
        and only the new commit will be shown.

        It is expected that the client will send the data as part of a
        :mimetype:`multipart/form-data` mimetype. The main diff's name and
        content would be stored in the ``path`` field. If a parent diff is
        provided, its name and content would be stored in the
        ``parent_diff_path`` field.

        An example of this would be::

            -- SoMe BoUnDaRy
            Content-Disposition: form-data; name=path; filename="foo.diff"

            <Unified Diff Content Here>
            -- SoMe BoUnDaRy --

        Extra data can be stored later lookup. See
        :ref:`webapi2.0-extra-data` for more information.
        """
        # Prevent a circular dependency, as ReviewRequestDraftResource
        # needs DraftDiffResource, which needs DiffResource.
        from reviewboard.webapi.resources.review_request_draft import \
            ReviewRequestDraftResource

        try:
            review_request = \
                resources.review_request.get_object(request, *args, **kwargs)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        if not review_request.is_mutable_by(request.user):
            return self.get_no_access_error(request)

        if review_request.repository is None:
            return INVALID_ATTRIBUTE, {
                'reason':
                'This review request was created as attachments-'
                'only, with no repository.',
            }
        elif review_request.created_with_history:
            assert dvcs_feature.is_enabled(request=request)

            if 'path' in request.FILES:
                return INVALID_FORM_DATA, {
                    'reason':
                    ('This review request was created with support for '
                     'multiple commits.\n'
                     '\n'
                     'Create an empty diff revision and upload commits to '
                     'that instead.'),
                }

            diffset = DiffSet.objects.create_empty(
                repository=review_request.repository,
                base_commit_id=request.POST.get('base_commit_id'))
            diffset.update_revision_from_history(
                review_request.diffset_history)
            diffset.save(update_fields=('revision', ))
        else:
            form_data = request.POST.copy()
            form = UploadDiffForm(review_request,
                                  data=form_data,
                                  files=request.FILES,
                                  request=request)

            if not form.is_valid():
                return INVALID_FORM_DATA, {
                    'fields': self._get_form_errors(form),
                }

            try:
                diffset = form.create()
            except FileNotFoundError as e:
                return REPO_FILE_NOT_FOUND, {
                    'file': e.path,
                    'revision': six.text_type(e.revision)
                }
            except EmptyDiffError as e:
                return DIFF_EMPTY
            except DiffTooBigError as e:
                return DIFF_TOO_BIG, {
                    'reason': six.text_type(e),
                    'max_size': e.max_diff_size,
                }
            except Exception as e:
                # This could be very wrong, but at least they'll see the error.
                # We probably want a new error type for this.
                logger.error("Error uploading new diff: %s",
                             e,
                             exc_info=1,
                             request=request)

                return INVALID_FORM_DATA, {
                    'fields': {
                        'path': [six.text_type(e)]
                    }
                }

        discarded_diffset = None

        try:
            draft = review_request.draft.get()

            if draft.diffset and draft.diffset != diffset:
                discarded_diffset = draft.diffset
        except ReviewRequestDraft.DoesNotExist:
            try:
                draft = ReviewRequestDraftResource.prepare_draft(
                    request, review_request)
            except PermissionDenied:
                return self.get_no_access_error(request)

        draft.diffset = diffset

        # We only want to add default reviewers the first time.  Was bug 318.
        if review_request.can_add_default_reviewers():
            draft.add_default_reviewers()

        draft.save()

        if extra_fields:
            try:
                self.import_extra_data(diffset, diffset.extra_data,
                                       extra_fields)
            except ImportExtraDataError as e:
                return e.error_payload

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

        if discarded_diffset:
            discarded_diffset.delete()

        # E-mail gets sent when the draft is saved.

        return 201, {
            self.item_result_key: diffset,
        }
Esempio n. 5
0
    def update(self,
               request,
               status=None,
               changenum=None,
               description=None,
               extra_fields={},
               *args,
               **kwargs):
        """Updates the status of the review request.

        The only supported update to a review request's resource is to change
        the status, the associated server-side, change number, or to update
        information from the existing change number.

        The status can be set in order to close the review request as
        discarded or submitted, or to reopen as pending.

        For Perforce, a change number can either be changed to a new number, or
        the current change number can be passed. In either case, a new draft
        will be created or an existing one updated to include information from
        the server based on the change number. This behavior is deprecated,
        and instead, the commit_id field should be set on the draft.

        Changes to a review request's fields, such as the summary or the
        list of reviewers, is made on the Review Request Draft resource.
        This can be accessed through the ``draft`` link. Only when that
        draft is published will the changes end up back in this resource.

        Extra data can be stored on the review request for later lookup by
        passing ``extra_data.key_name=value``. The ``key_name`` and ``value``
        can be any valid strings. Passing a blank ``value`` will remove the
        key. The ``extra_data.`` prefix is required.
        """
        try:
            review_request = \
                resources.review_request.get_object(request, *args, **kwargs)
        except ObjectDoesNotExist:
            return DOES_NOT_EXIST

        is_mutating_field = (changenum is not None or extra_fields)

        if ((is_mutating_field
             and not self.has_modify_permissions(request, review_request)) or
            (status is not None
             and not review_request.is_status_mutable_by(request.user))):
            return self._no_access_error(request.user)

        if (status is not None and
            (review_request.status != string_to_status(status)
             or review_request.status != ReviewRequest.PENDING_REVIEW)):
            try:
                if status in self._close_type_map:
                    review_request.close(self._close_type_map[status],
                                         request.user, description)
                elif status == 'pending':
                    review_request.reopen(request.user)
                else:
                    raise AssertionError("Code path for invalid status '%s' "
                                         "should never be reached." % status)
            except PermissionError:
                return self._no_access_error(request.user)

        # Preserve the old changenum behavior.
        if changenum is not None:
            if changenum != review_request.changenum:
                review_request.commit = changenum

            try:
                draft = ReviewRequestDraftResource.prepare_draft(
                    request, review_request)
            except PermissionDenied:
                return PERMISSION_DENIED

            try:
                draft.update_from_commit_id(six.text_type(changenum))
            except InvalidChangeNumberError:
                return INVALID_CHANGE_NUMBER

            draft.save()
            review_request.reopen()

        if extra_fields:
            self._import_extra_data(review_request.extra_data, extra_fields)
            review_request.save(update_fields=['extra_data'])

        return 200, {
            self.item_result_key: review_request,
        }
Esempio n. 6
0
    def update(self,
               request,
               status=None,
               changenum=None,
               commit_id=None,
               description=None,
               *args,
               **kwargs):
        """Updates the status of the review request.

        The only supported update to a review request's resource is to change
        the status, the associated server-side, change number, or to update
        information from the existing change number.

        The status can be set in order to close the review request as
        discarded or submitted, or to reopen as pending.

        The change number can either be changed to a new number, or the
        current change number can be passed. In either case, a new draft will
        be created or an existing one updated to include information from
        the server based on the change number.

        Changes to a review request's fields, such as the summary or the
        list of reviewers, is made on the Review Request Draft resource.
        This can be accessed through the ``draft`` link. Only when that
        draft is published will the changes end up back in this resource.
        """
        try:
            review_request = \
                resources.review_request.get_object(request, *args, **kwargs)
        except ObjectDoesNotExist:
            return DOES_NOT_EXIST

        if not self.has_modify_permissions(request, review_request):
            return self._no_access_error(request.user)

        if (status is not None and
            (review_request.status != string_to_status(status)
             or review_request.status != ReviewRequest.PENDING_REVIEW)):
            try:
                if status in self._close_type_map:
                    review_request.close(self._close_type_map[status],
                                         request.user, description)
                elif status == 'pending':
                    review_request.reopen(request.user)
                else:
                    raise AssertionError("Code path for invalid status '%s' "
                                         "should never be reached." % status)
            except PermissionError:
                return self._no_access_error(request.user)

        if changenum is not None and commit_id is None:
            commit_id = str(changenum)

        if commit_id is not None:
            if commit_id != review_request.commit:
                review_request.update_commit_id(commit_id, request.user)

            try:
                draft = ReviewRequestDraftResource.prepare_draft(
                    request, review_request)
            except PermissionDenied:
                return PERMISSION_DENIED

            try:
                draft.update_from_commit_id(commit_id)
            except InvalidChangeNumberError:
                return INVALID_CHANGE_NUMBER

            draft.save()
            review_request.reopen()

        return 200, {
            self.item_result_key: review_request,
        }
Esempio n. 7
0
            return INVALID_FORM_DATA, {
                'fields': {
                    'path': [str(e)]
                }
            }

        discarded_diffset = None

        try:
            draft = review_request.draft.get()

            if draft.diffset and draft.diffset != diffset:
                discarded_diffset = draft.diffset
        except ReviewRequestDraft.DoesNotExist:
            try:
                draft = ReviewRequestDraftResource.prepare_draft(
                    request, review_request)
            except PermissionDenied:
                return self._no_access_error(request.user)

        draft.diffset = diffset

        # We only want to add default reviewers the first time.  Was bug 318.
        if review_request.diffset_history.diffsets.count() == 0:
            draft.add_default_reviewers()

        draft.save()

        if discarded_diffset:
            discarded_diffset.delete()

        # E-mail gets sent when the draft is saved.
Esempio n. 8
0
                          e,
                          exc_info=1,
                          request=request)

            return INVALID_FORM_DATA, {'fields': {'path': [str(e)]}}

        discarded_diffset = None

        try:
            draft = review_request.draft.get()

            if draft.diffset and draft.diffset != diffset:
                discarded_diffset = draft.diffset
        except ReviewRequestDraft.DoesNotExist:
            try:
                draft = ReviewRequestDraftResource.prepare_draft(
                    request, review_request)
            except PermissionDenied:
                return self._no_access_error(request.user)

        draft.diffset = diffset

        # We only want to add default reviewers the first time.  Was bug 318.
        if review_request.diffset_history.diffsets.count() == 0:
            draft.add_default_reviewers()

        draft.save()

        if discarded_diffset:
            discarded_diffset.delete()

        # E-mail gets sent when the draft is saved.
Esempio n. 9
0
    def create(self, request, *args, **kwargs):
        """Creates a new diff by parsing an uploaded diff file.

        This will implicitly create the new Review Request draft, which can
        be updated separately and then published.

        This accepts a unified diff file, validates it, and stores it along
        with the draft of a review request. The new diff will have a revision
        of 0.

        A parent diff can be uploaded along with the main diff. A parent diff
        is a diff based on an existing commit in the repository, which will
        be applied before the main diff. The parent diff will not be included
        in the diff viewer. It's useful when developing a change based on a
        branch that is not yet committed. In this case, a parent diff of the
        parent branch would be provided along with the diff of the new commit,
        and only the new commit will be shown.

        It is expected that the client will send the data as part of a
        :mimetype:`multipart/form-data` mimetype. The main diff's name and
        content would be stored in the ``path`` field. If a parent diff is
        provided, its name and content would be stored in the
        ``parent_diff_path`` field.

        An example of this would be::

            -- SoMe BoUnDaRy
            Content-Disposition: form-data; name=path; filename="foo.diff"

            <Unified Diff Content Here>
            -- SoMe BoUnDaRy --
        """
        # Prevent a circular dependency, as ReviewRequestDraftResource
        # needs DraftDiffResource, which needs DiffResource.
        from reviewboard.webapi.resources.review_request_draft import \
            ReviewRequestDraftResource

        try:
            review_request = \
                resources.review_request.get_object(request, *args, **kwargs)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        if not review_request.is_mutable_by(request.user):
            return self._no_access_error(request.user)

        form_data = request.POST.copy()
        form = UploadDiffForm(review_request,
                              form_data,
                              request.FILES,
                              request=request)

        if not form.is_valid():
            return INVALID_FORM_DATA, {
                'fields': self._get_form_errors(form),
            }

        try:
            diffset = form.create(request.FILES['path'],
                                  request.FILES.get('parent_diff_path'))
        except FileNotFoundError as e:
            return REPO_FILE_NOT_FOUND, {
                'file': e.path,
                'revision': six.text_type(e.revision)
            }
        except EmptyDiffError as e:
            return DIFF_EMPTY
        except DiffTooBigError as e:
            return DIFF_TOO_BIG, {
                'reason': six.text_type(e),
                'max_size': e.max_diff_size,
            }
        except Exception as e:
            # This could be very wrong, but at least they'll see the error.
            # We probably want a new error type for this.
            logging.error("Error uploading new diff: %s",
                          e,
                          exc_info=1,
                          request=request)

            return INVALID_FORM_DATA, {'fields': {'path': [six.text_type(e)]}}

        discarded_diffset = None

        try:
            draft = review_request.draft.get()

            if draft.diffset and draft.diffset != diffset:
                discarded_diffset = draft.diffset
        except ReviewRequestDraft.DoesNotExist:
            try:
                draft = ReviewRequestDraftResource.prepare_draft(
                    request, review_request)
            except PermissionDenied:
                return self._no_access_error(request.user)

        draft.diffset = diffset

        # We only want to add default reviewers the first time.  Was bug 318.
        if review_request.diffset_history.diffsets.count() == 0:
            draft.add_default_reviewers()

        draft.save()

        if discarded_diffset:
            discarded_diffset.delete()

        # E-mail gets sent when the draft is saved.

        return 201, {
            self.item_result_key: diffset,
        }
Esempio n. 10
0
    def create(self, request, extra_fields={}, *args, **kwargs):
        """Creates a new diff by parsing an uploaded diff file.

        This will implicitly create the new Review Request draft, which can
        be updated separately and then published.

        This accepts a unified diff file, validates it, and stores it along
        with the draft of a review request. The new diff will have a revision
        of 0.

        A parent diff can be uploaded along with the main diff. A parent diff
        is a diff based on an existing commit in the repository, which will
        be applied before the main diff. The parent diff will not be included
        in the diff viewer. It's useful when developing a change based on a
        branch that is not yet committed. In this case, a parent diff of the
        parent branch would be provided along with the diff of the new commit,
        and only the new commit will be shown.

        It is expected that the client will send the data as part of a
        :mimetype:`multipart/form-data` mimetype. The main diff's name and
        content would be stored in the ``path`` field. If a parent diff is
        provided, its name and content would be stored in the
        ``parent_diff_path`` field.

        An example of this would be::

            -- SoMe BoUnDaRy
            Content-Disposition: form-data; name=path; filename="foo.diff"

            <Unified Diff Content Here>
            -- SoMe BoUnDaRy --

        Extra data can be stored on the diff for later lookup by passing
        ``extra_data.key_name=value``. The ``key_name`` and ``value`` can
        be any valid strings. Passing a blank ``value`` will remove the key.
        The ``extra_data.`` prefix is required.
        """
        # Prevent a circular dependency, as ReviewRequestDraftResource
        # needs DraftDiffResource, which needs DiffResource.
        from reviewboard.webapi.resources.review_request_draft import \
            ReviewRequestDraftResource

        try:
            review_request = \
                resources.review_request.get_object(request, *args, **kwargs)
        except ReviewRequest.DoesNotExist:
            return DOES_NOT_EXIST

        if not review_request.is_mutable_by(request.user):
            return self._no_access_error(request.user)

        form_data = request.POST.copy()
        form = UploadDiffForm(review_request, form_data, request.FILES,
                              request=request)

        if not form.is_valid():
            return INVALID_FORM_DATA, {
                'fields': self._get_form_errors(form),
            }

        try:
            diffset = form.create(request.FILES['path'],
                                  request.FILES.get('parent_diff_path'))
        except FileNotFoundError as e:
            return REPO_FILE_NOT_FOUND, {
                'file': e.path,
                'revision': six.text_type(e.revision)
            }
        except EmptyDiffError as e:
            return DIFF_EMPTY
        except DiffTooBigError as e:
            return DIFF_TOO_BIG, {
                'reason': six.text_type(e),
                'max_size': e.max_diff_size,
            }
        except Exception as e:
            # This could be very wrong, but at least they'll see the error.
            # We probably want a new error type for this.
            logging.error("Error uploading new diff: %s", e, exc_info=1,
                          request=request)

            return INVALID_FORM_DATA, {
                'fields': {
                    'path': [six.text_type(e)]
                }
            }

        discarded_diffset = None

        try:
            draft = review_request.draft.get()

            if draft.diffset and draft.diffset != diffset:
                discarded_diffset = draft.diffset
        except ReviewRequestDraft.DoesNotExist:
            try:
                draft = ReviewRequestDraftResource.prepare_draft(
                    request, review_request)
            except PermissionDenied:
                return self._no_access_error(request.user)

        draft.diffset = diffset

        # We only want to add default reviewers the first time.  Was bug 318.
        if review_request.diffset_history.diffsets.count() == 0:
            draft.add_default_reviewers()

        draft.save()

        if extra_fields:
            self._import_extra_data(diffset.extra_data, extra_fields)
            diffset.save(update_fields=['extra_data'])

        if discarded_diffset:
            discarded_diffset.delete()

        # E-mail gets sent when the draft is saved.

        return 201, {
            self.item_result_key: diffset,
        }