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
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
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, }
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, }
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, }