Exemplo n.º 1
0
    def get(self, request, *args, **kwargs):
        """Retrieves an array of the branches in a repository."""
        try:
            repository = resources.repository.get_object(
                request, *args, **kwargs)
        except ObjectDoesNotExist:
            return DOES_NOT_EXIST

        try:
            branches = []
            for branch in repository.get_branches():
                branches.append({
                    'id': branch.id,
                    'name': branch.name,
                    'commit': branch.commit,
                    'default': branch.default,
                })

            return 200, {
                self.item_result_key: branches,
            }
        except (HostingServiceError, SCMError) as e:
            return REPO_INFO_ERROR.with_message(six.text_type(e))
        except NotImplementedError:
            return REPO_NOT_IMPLEMENTED
Exemplo n.º 2
0
    def get(self, request, *args, **kwargs):
        """Retrieves an array of the branches in a repository."""
        try:
            repository = resources.repository.get_object(request, *args,
                                                         **kwargs)
        except ObjectDoesNotExist:
            return DOES_NOT_EXIST

        try:
            branches = []
            for branch in repository.get_branches():
                branches.append({
                    'id': branch.id,
                    'name': branch.name,
                    'commit': branch.commit,
                    'default': branch.default,
                })

            return 200, {
                self.item_result_key: branches,
            }
        except (HostingServiceError, SCMError) as e:
            return REPO_INFO_ERROR.with_message(six.text_type(e))
        except NotImplementedError:
            return REPO_NOT_IMPLEMENTED
Exemplo n.º 3
0
    def get(self, request, branch=None, start=None, *args, **kwargs):
        """Retrieves a set of commits from a particular repository.

        The ``start`` parameter is a commit ID to use as a starting point. This
        allows both pagination and logging of different branches. Successive
        pages of commit history can be fetched by using the ``parent`` field of
        the last entry as the ``start`` parameter for another request.
        """
        try:
            repository = resources.repository.get_object(
                request, *args, **kwargs)
        except ObjectDoesNotExist:
            return DOES_NOT_EXIST

        try:
            items = repository.get_commits(branch=branch, start=start)
        except SCMError as e:
            return REPO_INFO_ERROR.with_message(six.text_type(e))
        except NotImplementedError:
            return REPO_NOT_IMPLEMENTED

        commits = []
        commit_ids = []
        for commit in items:
            commits.append({
                'author_name': commit.author_name,
                'id': commit.id,
                'date': commit.date,
                'message': commit.message,
                'parent': commit.parent,
            })
            commit_ids.append(commit.id)

        by_commit_id = {}
        for obj in ReviewRequest.objects.filter(repository=repository,
                                                commit_id__in=commit_ids):
            by_commit_id[obj.commit_id] = obj

        for commit in commits:
            try:
                review_request = by_commit_id[commit['id']]
                commit['review_request_url'] = \
                    review_request.get_absolute_url()
            except KeyError:
                commit['review_request_url'] = ''

        return 200, {
            self.item_result_key: commits,
        }
Exemplo n.º 4
0
    def get(self, request, branch=None, start=None, *args, **kwargs):
        """Retrieves a set of commits from a particular repository.

        The ``start`` parameter is a commit ID to use as a starting point. This
        allows both pagination and logging of different branches. Successive
        pages of commit history can be fetched by using the ``parent`` field of
        the last entry as the ``start`` parameter for another request.
        """
        try:
            repository = resources.repository.get_object(request, *args,
                                                         **kwargs)
        except ObjectDoesNotExist:
            return DOES_NOT_EXIST

        try:
            items = repository.get_commits(branch=branch, start=start)
        except SCMError as e:
            return REPO_INFO_ERROR.with_message(six.text_type(e))
        except NotImplementedError:
            return REPO_NOT_IMPLEMENTED

        commits = []
        commit_ids = []
        for commit in items:
            commits.append({
                'author_name': commit.author_name,
                'id': commit.id,
                'date': commit.date,
                'message': commit.message,
                'parent': commit.parent,
            })
            commit_ids.append(commit.id)

        by_commit_id = {}
        for obj in ReviewRequest.objects.filter(repository=repository,
                                                commit_id__in=commit_ids):
            by_commit_id[obj.commit_id] = obj

        for commit in commits:
            try:
                review_request = by_commit_id[commit['id']]
                commit['review_request_url'] = \
                    review_request.get_absolute_url()
            except KeyError:
                commit['review_request_url'] = ''

        return 200, {
            self.item_result_key: commits,
        }
Exemplo n.º 5
0
    def get(self, request, *args, **kwargs):
        """Returns repository-specific information from a server."""
        try:
            repository = resources.repository.get_object(
                request, *args, **kwargs)
        except ObjectDoesNotExist:
            return DOES_NOT_EXIST

        try:
            tool = repository.get_scmtool()

            return 200, {self.item_result_key: tool.get_repository_info()}
        except NotImplementedError:
            return REPO_NOT_IMPLEMENTED
        except AuthenticationError as e:
            return REPO_INFO_ERROR.with_message(str(e))
        except:
            return REPO_INFO_ERROR
    def get(self, request, *args, **kwargs):
        """Returns repository-specific information from a server."""
        try:
            repository = resources.repository.get_object(request, *args,
                                                         **kwargs)
        except ObjectDoesNotExist:
            return DOES_NOT_EXIST

        try:
            tool = repository.get_scmtool()

            return 200, {
                self.item_result_key: tool.get_repository_info()
            }
        except NotImplementedError:
            return REPO_NOT_IMPLEMENTED
        except AuthenticationError as e:
            return REPO_INFO_ERROR.with_message(six.text_type(e))
        except:
            return REPO_INFO_ERROR
    def update(self,
               request,
               local_site_name=None,
               branch=None,
               bugs_closed=None,
               changedescription=None,
               commit_id=None,
               depends_on=None,
               submitter=None,
               summary=None,
               target_groups=None,
               target_people=None,
               update_from_commit_id=False,
               trivial=None,
               publish_as_owner=False,
               extra_fields={},
               *args,
               **kwargs):
        """Updates a draft of a review request.

        This will update the draft with the newly provided data.

        Most of the fields correspond to fields in the review request, but
        there is one special one, ``public``. When ``public`` is set to true,
        the draft will be published, moving the new content to the
        review request itself, making it public, and sending out a notification
        (such as an e-mail) if configured on the server. The current draft will
        then be deleted.

        Extra data can be stored later lookup. See
        :ref:`webapi2.0-extra-data` for more information.
        """
        try:
            review_request = resources.review_request.get_object(
                request, local_site_name=local_site_name, *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)

        draft = review_request.get_draft()

        # Before we update anything, sanitize the commit ID, see if it
        # changed, and make sure that the the new value isn't owned by
        # another review request or draft.
        if commit_id == '':
            commit_id = None

        if (commit_id and
            commit_id != review_request.commit_id and
            (draft is None or commit_id != draft.commit_id)):
            # The commit ID has changed, so now we check for other usages of
            # this ID.
            repository = review_request.repository

            existing_review_request_ids = (
                ReviewRequest.objects
                .filter(commit_id=commit_id,
                        repository=repository)
                .values_list('pk', flat=True)
            )

            if (existing_review_request_ids and
                review_request.pk not in existing_review_request_ids):
                # Another review request is using this ID. Error out.
                return COMMIT_ID_ALREADY_EXISTS

            existing_draft_ids = (
                ReviewRequestDraft.objects
                .filter(commit_id=commit_id,
                        review_request__repository=repository)
                .values_list('pk', flat=True)
            )

            if (existing_draft_ids and
                (draft is None or draft.pk not in existing_draft_ids)):
                # Another review request draft is using this ID. Error out.
                return COMMIT_ID_ALREADY_EXISTS

        # Now that we've completed our initial accessibility and conflict
        # checks, we can start checking for changes to individual fields.
        #
        # We'll keep track of state pertaining to the fields we want to
        # set/save, and any errors we hit. For setting/saving, these's two
        # types of things we're tracking: The new field values (which will be
        # applied to the objects or Many-To-Many relations) and a list of
        # field names to set when calling `save(update_fields=...)`. The
        # former implies the latter. The latter only needs to be directly
        # set if the fields are modified directly by another function.
        new_draft_values = {}
        new_changedesc_values = {}
        draft_update_fields = set()
        changedesc_update_fields = set()
        invalid_fields = {}

        if draft is None:
            draft = ReviewRequestDraft.create(review_request=review_request)

        # Check for a new value for branch.
        if branch is not None:
            new_draft_values['branch'] = branch

        # Check for a new value for bugs_closed:
        if bugs_closed is not None:
            new_draft_values['bugs_closed'] = \
                ','.join(self._parse_bug_list(bugs_closed))

        # Check for a new value for changedescription.
        if changedescription is not None and draft.changedesc_id is None:
            invalid_fields['changedescription'] = [
                'Change descriptions cannot be used for drafts of '
                'new review requests'
            ]

        # Check for a new value for commit_id.
        if commit_id is not None:
            new_draft_values['commit_id'] = commit_id

            if update_from_commit_id:
                try:
                    draft_update_fields.update(
                        draft.update_from_commit_id(commit_id))
                except InvalidChangeNumberError:
                    return INVALID_CHANGE_NUMBER
                except HostingServiceError as e:
                    return REPO_INFO_ERROR.with_message(six.text_type(e))
                except SCMError as e:
                    return REPO_INFO_ERROR.with_message(six.text_type(e))

        # Check for a new value for depends_on.
        if depends_on is not None:
            found_deps, missing_dep_ids = self._find_depends_on(
                dep_ids=self._parse_value_list(depends_on),
                request=request)

            if missing_dep_ids:
                invalid_fields['depends_on'] = missing_dep_ids
            else:
                new_draft_values['depends_on'] = found_deps

        # Check for a new value for submitter.
        if submitter is not None:
            # While we only allow for one submitter, we'll try to parse a
            # possible list of values. This allows us to provide a suitable
            # error message if users try to set more than one submitter
            # (which people do try, in practice).
            found_users, missing_usernames = self._find_users(
                usernames=self._parse_value_list(submitter),
                request=request)

            if len(found_users) + len(missing_usernames) > 1:
                invalid_fields['submitter'] = [
                    'Only one user can be set as the owner of a review '
                    'request'
                ]
            elif missing_usernames:
                assert len(missing_usernames) == 1
                invalid_fields['submitter'] = missing_usernames
            elif found_users:
                assert len(found_users) == 1
                new_draft_values['owner'] = found_users[0]
            else:
                invalid_fields['submitter'] = [
                    'The owner of a review request cannot be blank'
                ]

        # Check for a new value for summary.
        if summary is not None:
            if '\n' in summary:
                invalid_fields['summary'] = [
                    "The summary can't contain a newline"
                ]
            else:
                new_draft_values['summary'] = summary

        # Check for a new value for target_groups.
        if target_groups is not None:
            found_groups, missing_group_names = self._find_review_groups(
                group_names=self._parse_value_list(target_groups),
                request=request)

            if missing_group_names:
                invalid_fields['target_groups'] = missing_group_names
            else:
                new_draft_values['target_groups'] = found_groups

        # Check for a new value for target_people.
        if target_people is not None:
            found_users, missing_usernames = self._find_users(
                usernames=self._parse_value_list(target_people),
                request=request)

            if missing_usernames:
                invalid_fields['target_people'] = missing_usernames
            else:
                new_draft_values['target_people'] = found_users

        # See if we've caught any invalid values. If so, we can error out
        # immediately before we update anything else.
        if invalid_fields:
            return INVALID_FORM_DATA, {
                'fields': invalid_fields,
                self.item_result_key: draft,
            }

        # Start applying any rich text processing to any text fields on the
        # ChangeDescription and draft. We'll track any fields that get set
        # for later saving.
        #
        # NOTE: If any text fields or text type fields are ever made
        #       parameters of this method, then their values will need to be
        #       passed directly to set_text_fields() calls below.
        if draft.changedesc_id:
            changedesc_update_fields.update(
                self.set_text_fields(obj=draft.changedesc,
                                     text_field='changedescription',
                                     text_model_field='text',
                                     rich_text_field_name='rich_text',
                                     changedescription=changedescription,
                                     **kwargs))

        for text_field in ('description', 'testing_done'):
            draft_update_fields.update(self.set_text_fields(
                obj=draft,
                text_field=text_field,
                **kwargs))

        # Go through the list of Markdown-enabled custom fields and apply
        # any rich text processing. These will go in extra_data, so we'll
        # want to make sure that's tracked for saving.
        for field_cls in get_review_request_fields():
            if (not issubclass(field_cls, BuiltinFieldMixin) and
                getattr(field_cls, 'enable_markdown', False)):
                modified_fields = self.set_extra_data_text_fields(
                    obj=draft,
                    text_field=field_cls.field_id,
                    extra_fields=extra_fields,
                    **kwargs)

                if modified_fields:
                    draft_update_fields.add('extra_data')

        # See if the caller has set or patched extra_data values. For
        # compatibility, we're going to do this after processing the rich
        # text fields ine extra_data above.
        try:
            if self.import_extra_data(draft, draft.extra_data, extra_fields):
                # Track extra_data for saving.
                draft_update_fields.add('extra_data')
        except ImportExtraDataError as e:
            return e.error_payload

        # Everything checks out. We can now begin the process of applying any
        # field changes and then save objects.
        #
        # We'll start by making an initial pass on the objects we need to
        # either care about. This optimistically lets us avoid a lookup on the
        # ChangeDescription, if it's not being modified.
        to_apply = []

        if draft_update_fields or new_draft_values:
            # If there's any changes made at all to the draft, make sure we
            # allow last_updated to be computed and saved.
            if draft_update_fields or new_draft_values:
                draft_update_fields.add('last_updated')

            to_apply.append((draft, draft_update_fields, new_draft_values))

        if changedesc_update_fields or new_changedesc_values:
            to_apply.append((draft.changedesc, changedesc_update_fields,
                             new_changedesc_values))

        for obj, update_fields, new_values in to_apply:
            new_m2m_values = {}

            # We may have a mixture of field values and Many-To-Many
            # relation values, which we want to set only after the object
            # is saved. Start by setting any field values, and store the
            # M2M values for after.
            for key, value in six.iteritems(new_values):
                field = obj._meta.get_field(key)

                if isinstance(field, ManyToManyField):
                    # Save this until after the object is saved.
                    new_m2m_values[key] = value
                else:
                    # We can set this one now, and mark it for saving.
                    setattr(obj, key, value)
                    update_fields.add(key)

            if update_fields:
                obj.save(update_fields=sorted(update_fields))

            # Now we can set any values on M2M fields.
            #
            # Each entry will have zero or more values. We'll be
            # setting to the list of values, which will fully replace
            # the stored entries in the database.
            for key, values in six.iteritems(new_m2m_values):
                setattr(obj, key, values)

        # Next, check if the draft is set to be published.
        if request.POST.get('public', False):
            if not review_request.public and not draft.changedesc_id:
                # This is a new review request. Publish this on behalf of the
                # owner of the review request, rather than the current user,
                # regardless of the original publish_as_owner in the request.
                # This allows a review request previously created with
                # submit-as= to be published by that user instead of the
                # logged in user.
                publish_as_owner = True

            if publish_as_owner:
                publish_user = review_request.owner
            else:
                # Default to posting as the logged in user.
                publish_user = request.user

            try:
                review_request.publish(user=publish_user, trivial=trivial)
            except NotModifiedError:
                return NOTHING_TO_PUBLISH
            except PublishError as e:
                return PUBLISH_ERROR.with_message(six.text_type(e))

        return 200, {
            self.item_result_key: draft,
        }