def main(self, *args): """Add the review request URL to a commit message.""" self.cmd_args = list(args) repository_info, self.tool = self.initialize_scm_tool( client_name=self.options.repository_type) server_url = self.get_server_url(repository_info, self.tool) api_client, api_root = self.get_api(server_url) self.setup_tool(self.tool, api_root=api_root) if not self.tool.can_amend_commit: raise NotImplementedError('rbt stamp is not supported with %s.' % self.tool.name) try: if self.tool.has_pending_changes(): raise CommandError('Working directory is not clean.') except NotImplementedError: pass revisions = get_revisions(self.tool, self.cmd_args) # Use the ID from the command line options if present. if self.options.rid: review_request_id = self.options.rid try: review_request = api_root.get_review_request( review_request_id=review_request_id) except APIError as e: raise CommandError('Error getting review request %s: %s' % (review_request_id, e)) review_request_url = review_request.absolute_url else: review_request_id, review_request_url = \ self. determine_review_request( api_client, api_root, repository_info, self.options.repository_name, revisions) if not review_request_url: raise CommandError('Could not determine the existing review ' 'request URL to stamp with.') stamp_commit_with_review_url(revisions, review_request_url, self.tool) print('Successfully stamped change with the URL:') print(review_request_url)
def post_request(self, repository_info, repository, server_url, api_root, review_request_id=None, changenum=None, diff_content=None, parent_diff_content=None, commit_id=None, base_commit_id=None, submit_as=None, retries=3, base_dir=None): """Creates or updates a review request, and uploads a diff. On success the review request id and url are returned. """ supports_posting_commit_ids = \ self.tool.capabilities.has_capability('review_requests', 'commit_ids') if review_request_id: review_request = get_review_request( review_request_id, api_root, only_fields='absolute_url,bugs_closed,id,status', only_links='diffs,draft') if review_request.status == 'submitted': raise CommandError( 'Review request %s is marked as %s. In order to update ' 'it, please reopen the review request and try again.' % (review_request_id, review_request.status)) else: # No review_request_id, so we will create a new review request. try: request_data = { 'repository': repository } if changenum: request_data['changenum'] = changenum elif commit_id and supports_posting_commit_ids: request_data['commit_id'] = commit_id if submit_as: request_data['submit_as'] = submit_as review_requests = api_root.get_review_requests( only_fields='', only_links='create') review_request = review_requests.create(**request_data) except APIError as e: if e.error_code == 204 and changenum: # The change number is already in use. Get the review # request for that change and update it instead. rid = e.rsp['review_request']['id'] review_request = api_root.get_review_request( review_request_id=rid, only_fields='absolute_url,bugs_closed,id,status', only_links='diffs,draft') if not self.options.diff_only: review_request = review_request.update( changenum=changenum) else: raise CommandError('Error creating review request: %s' % e) if (not repository_info.supports_changesets or not self.options.change_only): try: diff_kwargs = { 'parent_diff': parent_diff_content, 'base_dir': base_dir, } if (base_commit_id and self.tool.capabilities.has_capability('diffs', 'base_commit_ids')): # Both the Review Board server and SCMClient support # base commit IDs, so pass that along when creating # the diff. diff_kwargs['base_commit_id'] = base_commit_id review_request.get_diffs(only_fields='').upload_diff( diff_content, **diff_kwargs) except APIError as e: error_msg = [ u'Error uploading diff\n\n', ] if e.error_code == 101 and e.http_status == 403: error_msg.append( u'You do not have permissions to modify ' u'this review request\n') elif e.error_code == 219: error_msg.append( u'The generated diff file was empty. This ' u'usually means no files were\n' u'modified in this change.\n') else: error_msg.append(str(e).decode('utf-8') + u'\n') error_msg.append( u'Your review request still exists, but the diff is ' u'not attached.\n') error_msg.append(u'%s\n' % review_request.absolute_url) raise CommandError(u'\n'.join(error_msg)) try: draft = review_request.get_draft(only_fields='commit_id') except APIError as e: raise CommandError('Error retrieving review request draft: %s' % e) # Stamp the commit message with the review request URL before posting # the review, so that we can use the stamped commit message when # guessing the description. This enables the stamped message to be # present on the review if the user has chosen to publish immediately # upon posting. if self.options.stamp_when_posting: if not self.tool.can_amend_commit: print('Cannot stamp review URL onto the commit message; ' 'stamping is not supported with %s.' % self.tool.name) else: try: stamp_commit_with_review_url(self.revisions, review_request.absolute_url, self.tool) print('Stamped review URL onto the commit message.') except AlreadyStampedError: print('Commit message has already been stamped') except Exception as e: logging.debug('Caught exception while stamping the ' 'commit message. Proceeding to post ' 'without stamping.', exc_info=True) print('Could not stamp review URL onto the commit ' 'message.') # If the user has requested to guess the summary or description, # get the commit message and override the summary and description # options. The guessing takes place after stamping so that the # guessed description matches the commit when rbt exits. if not self.options.diff_filename: self.check_guess_fields() # Update the review request draft fields based on options set # by the user, or configuration. update_fields = {} if self.options.target_groups: update_fields['target_groups'] = self.options.target_groups if self.options.target_people: update_fields['target_people'] = self.options.target_people if self.options.depends_on: update_fields['depends_on'] = self.options.depends_on if self.options.summary: update_fields['summary'] = self.options.summary if self.options.branch: update_fields['branch'] = self.options.branch if self.options.bugs_closed: # Append to the existing list of bugs. self.options.bugs_closed = self.options.bugs_closed.strip(', ') bug_set = (set(re.split('[, ]+', self.options.bugs_closed)) | set(review_request.bugs_closed)) self.options.bugs_closed = ','.join(bug_set) update_fields['bugs_closed'] = self.options.bugs_closed if self.options.description: update_fields['description'] = self.options.description if self.options.testing_done: update_fields['testing_done'] = self.options.testing_done if ((self.options.description or self.options.testing_done) and self.options.markdown and self.tool.capabilities.has_capability('text', 'markdown')): # The user specified that their Description/Testing Done are # valid Markdown, so tell the server so it won't escape the text. update_fields['text_type'] = 'markdown' if self.options.change_description: update_fields['changedescription'] = \ self.options.change_description if self.options.publish: update_fields['public'] = True if supports_posting_commit_ids and commit_id != draft.commit_id: update_fields['commit_id'] = commit_id or '' if update_fields: try: draft = draft.update(**update_fields) except APIError as e: raise CommandError(u'\n'.join([ u'Error updating review request draft: %s\n' % e, u'Your review request still exists, but the diff is ' u'not attached.\n', u'%s\n' % review_request.absolute_url, ])) return review_request.id, review_request.absolute_url
def post_request(self, repository_info, repository, server_url, api_root, review_request_id=None, changenum=None, diff_content=None, parent_diff_content=None, commit_id=None, base_commit_id=None, submit_as=None, retries=3, base_dir=None): """Creates or updates a review request, and uploads a diff. On success the review request id and url are returned. """ supports_posting_commit_ids = \ self.tool.capabilities.has_capability('review_requests', 'commit_ids') if review_request_id: review_request = get_review_request( review_request_id, api_root, only_fields='absolute_url,bugs_closed,id,status,public', only_links='diffs,draft') if review_request.status == 'submitted': raise CommandError( 'Review request %s is marked as %s. In order to update ' 'it, please reopen the review request and try again.' % (review_request_id, review_request.status)) else: # No review_request_id, so we will create a new review request. try: # Until we are Python 2.7+ only, the keys in request_data have # to be bytes. See bug 3753 for details. request_data = {b'repository': repository} if changenum: request_data[b'changenum'] = changenum elif commit_id and supports_posting_commit_ids: request_data[b'commit_id'] = commit_id if submit_as: request_data[b'submit_as'] = submit_as if self.tool.can_bookmark: bookmark = self.tool.get_current_bookmark() request_data[b'extra_data__local_bookmark'] = bookmark elif self.tool.can_branch: branch = self.tool.get_current_branch() request_data[b'extra_data__local_branch'] = branch review_requests = api_root.get_review_requests( only_fields='', only_links='create') review_request = review_requests.create(**request_data) except APIError as e: if e.error_code == 204 and changenum: # The change number is already in use. Get the review # request for that change and update it instead. rid = e.rsp['review_request']['id'] review_request = api_root.get_review_request( review_request_id=rid, only_fields='absolute_url,bugs_closed,id,status', only_links='diffs,draft') else: raise CommandError('Error creating review request: %s' % e) if (not repository_info.supports_changesets or not self.options.change_only): try: diff_kwargs = { 'parent_diff': parent_diff_content, 'base_dir': base_dir, } if (base_commit_id and self.tool.capabilities.has_capability( 'diffs', 'base_commit_ids')): # Both the Review Board server and SCMClient support # base commit IDs, so pass that along when creating # the diff. diff_kwargs['base_commit_id'] = base_commit_id review_request.get_diffs(only_fields='').upload_diff( diff_content, **diff_kwargs) except APIError as e: error_msg = [ u'Error uploading diff\n\n', ] if e.error_code == 101 and e.http_status == 403: error_msg.append(u'You do not have permissions to modify ' u'this review request\n') elif e.error_code == 219: error_msg.append( u'The generated diff file was empty. This ' u'usually means no files were\n' u'modified in this change.\n') else: error_msg.append(str(e).decode('utf-8') + u'\n') error_msg.append( u'Your review request still exists, but the diff is ' u'not attached.\n') error_msg.append(u'%s\n' % review_request.absolute_url) raise CommandError(u'\n'.join(error_msg)) try: draft = review_request.get_draft(only_fields='commit_id') except APIError as e: raise CommandError('Error retrieving review request draft: %s' % e) # Stamp the commit message with the review request URL before posting # the review, so that we can use the stamped commit message when # guessing the description. This enables the stamped message to be # present on the review if the user has chosen to publish immediately # upon posting. if self.options.stamp_when_posting: if not self.tool.can_amend_commit: print('Cannot stamp review URL onto the commit message; ' 'stamping is not supported with %s.' % self.tool.name) else: try: stamp_commit_with_review_url(self.revisions, review_request.absolute_url, self.tool) print('Stamped review URL onto the commit message.') except AlreadyStampedError: print('Commit message has already been stamped') except Exception as e: logging.debug( 'Caught exception while stamping the ' 'commit message. Proceeding to post ' 'without stamping.', exc_info=True) print('Could not stamp review URL onto the commit ' 'message.') # If the user has requested to guess the summary or description, # get the commit message and override the summary and description # options. The guessing takes place after stamping so that the # guessed description matches the commit when rbt exits. if not self.options.diff_filename: self.check_guess_fields() # Update the review request draft fields based on options set # by the user, or configuration. update_fields = {} if self.options.target_groups: update_fields['target_groups'] = self.options.target_groups if self.options.target_people: update_fields['target_people'] = self.options.target_people if self.options.depends_on: update_fields['depends_on'] = self.options.depends_on if self.options.summary: update_fields['summary'] = self.options.summary if self.options.branch: update_fields['branch'] = self.options.branch if self.options.bugs_closed: # Append to the existing list of bugs. self.options.bugs_closed = self.options.bugs_closed.strip(', ') bug_set = (set(re.split('[, ]+', self.options.bugs_closed)) | set(review_request.bugs_closed)) self.options.bugs_closed = ','.join(bug_set) update_fields['bugs_closed'] = self.options.bugs_closed if self.options.description: update_fields['description'] = self.options.description if self.options.testing_done: update_fields['testing_done'] = self.options.testing_done if ((self.options.description or self.options.testing_done) and self.options.markdown and self.tool.capabilities.has_capability('text', 'markdown')): # The user specified that their Description/Testing Done are # valid Markdown, so tell the server so it won't escape the text. update_fields['text_type'] = 'markdown' if self.options.publish: update_fields['public'] = True if (self.options.trivial_publish and self.tool.capabilities.has_capability( 'review_requests', 'trivial_publish')): update_fields['trivial'] = True if self.options.change_description is not None: if review_request.public: update_fields['changedescription'] = \ self.options.change_description if (self.options.markdown and self.tool.capabilities.has_capability( 'text', 'markdown')): update_fields['changedescription_text_type'] = 'markdown' else: update_fields['changedescription_text_type'] = 'plain' else: logging.error( 'The change description field can only be set when ' 'publishing an update. Use --description instead.') if supports_posting_commit_ids and commit_id != draft.commit_id: update_fields['commit_id'] = commit_id or '' if update_fields: try: draft = draft.update(**update_fields) except APIError as e: raise CommandError( 'Error updating review request draft: %s\n\n' 'Your review request still exists, but the diff is not ' 'attached.\n\n' '%s\n' % (e, review_request.absolute_url)) return review_request.id, review_request.absolute_url