def main(self, request_id): """Run the command.""" repository_info, tool = self.initialize_scm_tool( client_name=self.options.repository_type) server_url = self.get_server_url(repository_info, tool) api_client, api_root = self.get_api(server_url) self.setup_tool(tool, api_root=api_root) # Check if repository info on reviewboard server match local ones. repository_info = repository_info.find_server_repository_info(api_root) # Get the patch, the used patch ID and base dir for the diff diff_body, diff_revision, base_dir = self.get_patch( request_id, api_root, self.options.diff_revision) if self.options.patch_stdout: print(diff_body) else: try: if tool.has_pending_changes(): message = 'Working directory is not clean.' if not self.options.commit: print('Warning: %s' % message) else: raise CommandError(message) except NotImplementedError: pass tmp_patch_file = make_tempfile(diff_body) success = self.apply_patch(repository_info, tool, request_id, diff_revision, tmp_patch_file, base_dir) if success and (self.options.commit or self.options.commit_no_edit): try: review_request = api_root.get_review_request( review_request_id=request_id, force_text_type='plain') except APIError as e: raise CommandError('Error getting review request %s: %s' % (request_id, e)) message = extract_commit_message(review_request) author = review_request.get_submitter() try: tool.create_commit(message, author, not self.options.commit_no_edit) print('Changes committed to current branch.') except NotImplementedError: raise CommandError('--commit is not supported with %s' % tool.name)
def land(self, destination_branch, review_request, source_branch=None, squash=False, edit=False, delete_branch=True, dry_run=False): """Land an individual review request.""" if source_branch: review_commit_message = extract_commit_message(review_request) author = review_request.get_submitter() if squash: print('Squashing branch "%s" into "%s".' % (source_branch, destination_branch)) else: print('Merging branch "%s" into "%s".' % (source_branch, destination_branch)) if not dry_run: try: self.tool.merge(source_branch, destination_branch, review_commit_message, author, squash, edit) except MergeError as e: raise CommandError(six.text_type(e)) if delete_branch: print('Deleting merged branch "%s".' % source_branch) if not dry_run: self.tool.delete_branch(source_branch, merged_only=False) else: print('Applying patch from review request %s.' % review_request.id) if not dry_run: self.patch(review_request.id) print('Review request %s has landed on "%s".' % (review_request.id, self.options.destination_branch))
def main(self, request_id): """Run the command.""" repository_info, tool = self.initialize_scm_tool( client_name=self.options.repository_type) if self.options.revert_patch and not tool.supports_patch_revert: raise CommandError('The %s backend does not support reverting ' 'patches.' % tool.name) server_url = self.get_server_url(repository_info, tool) api_client, api_root = self.get_api(server_url) self.setup_tool(tool, api_root=api_root) # Check if repository info on reviewboard server match local ones. repository_info = repository_info.find_server_repository_info(api_root) # Get the patch, the used patch ID and base dir for the diff diff_body, diff_revision, base_dir = self.get_patch( request_id, api_root, self.options.diff_revision) if self.options.patch_stdout: print(diff_body) else: try: if tool.has_pending_changes(): message = 'Working directory is not clean.' if not self.options.commit: print('Warning: %s' % message) else: raise CommandError(message) except NotImplementedError: pass tmp_patch_file = make_tempfile(diff_body) success = self.apply_patch(repository_info, tool, request_id, diff_revision, tmp_patch_file, base_dir, revert=self.options.revert_patch) if not success: raise CommandError('Could not apply patch') if self.options.commit or self.options.commit_no_edit: try: review_request = api_root.get_review_request( review_request_id=request_id, force_text_type='plain') except APIError as e: raise CommandError('Error getting review request %s: %s' % (request_id, e)) message = extract_commit_message(review_request) author = review_request.get_submitter() try: tool.create_commit(message, author, not self.options.commit_no_edit) print('Changes committed to current branch.') except NotImplementedError: raise CommandError('--commit is not supported with %s' % tool.name)
def land(self, destination_branch, review_request, source_branch=None, squash=False, edit=False, delete_branch=True, dry_run=False): """Land an individual review request. Args: destination_branch (unicode): The destination branch that the change will be committed or merged to. review_request (rbtools.api.resource.ReviewRequestResource): The review request containing the change to land. source_branch (unicode, optional): The source branch to land, if landing from a local branch. squash (bool, optional): Whether to squash the changes on the branch, for repositories that support it. edit (bool, optional): Whether to edit the commit message before landing. delete_branch (bool, optional): Whether to delete/close the branch, if landing from a local branch. dry_run (bool, optional): Whether to simulate landing without actually changing the repository. """ if source_branch: review_commit_message = extract_commit_message(review_request) author = review_request.get_submitter() if squash: print('Squashing branch "%s" into "%s".' % (source_branch, destination_branch)) else: print('Merging branch "%s" into "%s".' % (source_branch, destination_branch)) if not dry_run: try: self.tool.merge(target=source_branch, destination=destination_branch, message=review_commit_message, author=author, squash=squash, run_editor=edit, close_branch=delete_branch) except MergeError as e: raise CommandError(six.text_type(e)) else: print('Applying patch from review request %s.' % review_request.id) if not dry_run: self.patch(review_request.id) print('Review request %s has landed on "%s".' % (review_request.id, self.options.destination_branch))
def main(self, review_request_id): """Run the command.""" repository_info, tool = self.initialize_scm_tool( client_name=self.options.repository_type) if self.options.patch_stdout and self.options.server: server_url = self.options.server else: server_url = self.get_server_url(repository_info, tool) if self.options.revert_patch and not tool.supports_patch_revert: raise CommandError('The %s backend does not support reverting ' 'patches.' % tool.name) api_client, api_root = self.get_api(server_url) if not self.options.patch_stdout: self.setup_tool(tool, api_root=api_root) # Check if repository info on reviewboard server match local ones. repository_info = repository_info.find_server_repository_info( api_root) # Get the patch, the used patch ID and base dir for the diff patch_data = self.get_patch(tool, api_root, review_request_id, self.options.diff_revision, self.options.commit_id) diff_body = patch_data['diff'] diff_revision = patch_data['revision'] base_dir = patch_data['base_dir'] if self.options.patch_stdout: if isinstance(diff_body, bytes): print(diff_body.decode('utf-8')) else: print(diff_body) else: try: if tool.has_pending_changes(): message = 'Working directory is not clean.' if not self.options.commit: print('Warning: %s' % message) else: raise CommandError(message) except NotImplementedError: pass tmp_patch_file = make_tempfile(diff_body) success = self.apply_patch(repository_info, tool, review_request_id, diff_revision, tmp_patch_file, base_dir, revert=self.options.revert_patch) if not success: raise CommandError('Could not apply patch') if self.options.commit or self.options.commit_no_edit: if patch_data['commit_meta'] is not None: # We are patching a commit so we already have the metadata # required without making additional HTTP requests. meta = patch_data['commit_meta'] message = meta['message'] # Fun fact: object does not have a __dict__ so you cannot # call setattr() on them. We need this ability so we are # creating a type that does. author = type('Author', (object, ), {})() author.fullname = meta['author_name'] author.email = meta['author_email'] else: try: review_request = api_root.get_review_request( review_request_id=review_request_id, force_text_type='plain') except APIError as e: raise CommandError( 'Error getting review request %s: %s' % (request_id, e)) message = extract_commit_message(review_request) author = review_request.get_submitter() try: tool.create_commit(message, author, not self.options.commit_no_edit) print('Changes committed to current branch.') except NotImplementedError: raise CommandError('--commit is not supported with %s' % tool.name)
def main(self, branch_name=None, *args): """Run the command.""" self.cmd_args = list(args) if branch_name: self.cmd_args.insert(0, branch_name) 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) dry_run = self.options.dry_run # Check if repository info on reviewboard server match local ones. repository_info = repository_info.find_server_repository_info(api_root) if (not self.tool.can_merge or not self.tool.can_push_upstream or not self.tool.can_delete_branch): raise CommandError( "This command does not support %s repositories." % self.tool.name) if self.tool.has_pending_changes(): raise CommandError('Working directory is not clean.') if self.options.rid: request_id = self.options.rid is_local = branch_name is not None else: request = guess_existing_review_request( repository_info, self.options.repository_name, api_root, api_client, self.tool, get_revisions(self.tool, self.cmd_args), guess_summary=False, guess_description=False, is_fuzzy_match_func=self._ask_review_request_match) if not request or not request.id: raise CommandError('Could not determine the existing review ' 'request URL to land.') request_id = request.id is_local = True if self.options.is_local is not None: is_local = self.options.is_local destination_branch = self.options.destination_branch if not destination_branch: raise CommandError('Please specify a destination branch.') if is_local: if branch_name is None: branch_name = self.tool.get_current_branch() if branch_name == destination_branch: raise CommandError('The local branch cannot be merged onto ' 'itself. Try a different local branch or ' 'destination branch.') review_request = get_review_request(request_id, api_root) try: is_rr_approved = review_request.approved approval_failure = review_request.approval_failure except AttributeError: # The Review Board server is an old version (pre-2.0) that # doesn't support the `approved` field. Determining it manually. if review_request.ship_it_count == 0: is_rr_approved = False approval_failure = \ 'The review request has not been marked "Ship It!"' else: is_rr_approved = True finally: if not is_rr_approved: raise CommandError(approval_failure) if is_local: review_commit_message = extract_commit_message(review_request) author = review_request.get_submitter() if self.options.squash: print('Squashing branch "%s" into "%s"' % (branch_name, destination_branch)) else: print('Merging branch "%s" into "%s"' % (branch_name, destination_branch)) if not dry_run: try: self.tool.merge( branch_name, destination_branch, review_commit_message, author, self.options.squash, self.options.edit) except MergeError as e: raise CommandError(str(e)) if self.options.delete_branch: print('Deleting merged branch "%s"' % branch_name) if not dry_run: self.tool.delete_branch(branch_name, merged_only=False) else: print('Applying patch from review request %s' % request_id) if not dry_run: self.patch(request_id) if self.options.push: print('Pushing branch "%s" upstream' % destination_branch) if not dry_run: try: self.tool.push_upstream(destination_branch) except PushError as e: raise CommandError(str(e)) print('Review request %s has landed on "%s".' % (request_id, destination_branch))
def main(self, branch_name=None, *args): """Run the command.""" self.cmd_args = list(args) if branch_name: self.cmd_args.insert(0, branch_name) 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) dry_run = self.options.dry_run # Check if repository info on reviewboard server match local ones. repository_info = repository_info.find_server_repository_info(api_root) if (not self.tool.can_merge or not self.tool.can_push_upstream or not self.tool.can_delete_branch): raise CommandError( "This command does not support %s repositories." % self.tool.name) if self.tool.has_pending_changes(): raise CommandError('Working directory is not clean.') if self.options.rid: request_id = self.options.rid is_local = branch_name is not None else: request = guess_existing_review_request( repository_info, self.options.repository_name, api_root, api_client, self.tool, get_revisions(self.tool, self.cmd_args), guess_summary=False, guess_description=False, is_fuzzy_match_func=self._ask_review_request_match) if not request or not request.id: raise CommandError('Could not determine the existing review ' 'request URL to land.') request_id = request.id is_local = True if self.options.is_local is not None: is_local = self.options.is_local destination_branch = self.options.destination_branch if not destination_branch: raise CommandError('Please specify a destination branch.') if is_local: if branch_name is None: branch_name = self.tool.get_current_branch() if branch_name == destination_branch: raise CommandError('The local branch cannot be merged onto ' 'itself. Try a different local branch or ' 'destination branch.') review_request = get_review_request(request_id, api_root) try: is_rr_approved = review_request.approved approval_failure = review_request.approval_failure except AttributeError: # The Review Board server is an old version (pre-2.0) that # doesn't support the `approved` field. Determining it manually. if review_request.ship_it_count == 0: is_rr_approved = False approval_failure = \ 'The review request has not been marked "Ship It!"' else: is_rr_approved = True finally: if not is_rr_approved: raise CommandError(approval_failure) if is_local: review_commit_message = extract_commit_message(review_request) author = review_request.get_submitter() if self.options.squash: print('Squashing branch "%s" into "%s"' % (branch_name, destination_branch)) else: print('Merging branch "%s" into "%s"' % (branch_name, destination_branch)) if not dry_run: try: self.tool.merge(branch_name, destination_branch, review_commit_message, author, self.options.squash, self.options.edit) except MergeError as e: raise CommandError(str(e)) if self.options.delete_branch: print('Deleting merged branch "%s"' % branch_name) if not dry_run: self.tool.delete_branch(branch_name, merged_only=False) else: print('Applying patch from review request %s' % request_id) if not dry_run: self.patch(request_id) if self.options.push: print('Pushing branch "%s" upstream' % destination_branch) if not dry_run: try: self.tool.push_upstream(destination_branch) except PushError as e: raise CommandError(str(e)) print('Review request %s has landed on "%s".' % (request_id, destination_branch))
def main(self, review_request_id): """Run the command. Args: review_request_id (int): The ID of the review request to patch from. Raises: rbtools.command.CommandError: Patching the tree has failed. """ patch_stdout = self.options.patch_stdout revert = self.options.revert_patch if patch_stdout and revert: raise CommandError(_('--print and --revert cannot both be used.')) repository_info, tool = self.initialize_scm_tool( client_name=self.options.repository_type, require_repository_info=not patch_stdout) if revert and not tool.supports_patch_revert: raise CommandError( _('The %s backend does not support reverting patches.') % tool.name) server_url = self.get_server_url(repository_info, tool) api_client, api_root = self.get_api(server_url) self.setup_tool(tool, api_root=api_root) if not patch_stdout: # Check if the repository info on the Review Board server matches # the local checkout. repository_info = repository_info.find_server_repository_info( api_root) # Get the patch, the used patch ID and base dir for the diff patch_data = self.get_patch( tool, api_root, review_request_id, self.options.diff_revision, self.options.commit_id) diff_body = patch_data['diff'] diff_revision = patch_data['revision'] base_dir = patch_data['base_dir'] if self.options.patch_stdout: if isinstance(diff_body, bytes): print(diff_body.decode('utf-8')) else: print(diff_body) else: try: if tool.has_pending_changes(): message = 'Working directory is not clean.' if not self.options.commit: print('Warning: %s' % message) else: raise CommandError(message) except NotImplementedError: pass tmp_patch_file = make_tempfile(diff_body) success = self.apply_patch( repository_info, tool, review_request_id, diff_revision, tmp_patch_file, base_dir, revert=self.options.revert_patch) if not success: raise CommandError('Could not apply patch') if self.options.commit or self.options.commit_no_edit: if patch_data['commit_meta'] is not None: # We are patching a commit so we already have the metadata # required without making additional HTTP requests. meta = patch_data['commit_meta'] message = meta['message'] # Fun fact: object does not have a __dict__ so you cannot # call setattr() on them. We need this ability so we are # creating a type that does. author = type('Author', (object,), {})() author.fullname = meta['author_name'] author.email = meta['author_email'] else: try: review_request = api_root.get_review_request( review_request_id=review_request_id, force_text_type='plain') except APIError as e: raise CommandError('Error getting review request %s: %s' % (request_id, e)) message = extract_commit_message(review_request) author = review_request.get_submitter() try: tool.create_commit(message, author, not self.options.commit_no_edit) print('Changes committed to current branch.') except NotImplementedError: raise CommandError('--commit is not supported with %s' % tool.name)
def _apply_patches(self, patches): """Apply a list of patches to the tree. Args: patches (list of dict): The list of patches to apply. Raises: rbtools.command.CommandError: Patching the tree has failed. """ squash = self.options.squash revert = self.options.revert_patch commit_no_edit = self.options.commit_no_edit will_commit = self.options.commit or commit_no_edit total_patches = len(patches) # Check if we're planning to commit and have any patch without # metadata, in which case we'll need to fetch the review request so we # can generate a commit message. needs_review_request = will_commit and ( squash or total_patches == 1 or any(patch_data['commit_meta'] is None for patch_data in patches)) if needs_review_request: # Fetch the review request to use as a description. We only # want to fetch this once. try: review_request = self._api_root.get_review_request( review_request_id=self._review_request_id, force_text_type='plain') except APIError as e: raise CommandError( _('Error getting review request %(review_request_id)d: ' '%(error)s') % { 'review_request_id': self._review_request_id, 'error': e, }) default_author = review_request.get_submitter() default_commit_message = extract_commit_message(review_request) else: default_author = None default_commit_message = None # Display a summary of what's about to be applied. diff_revision = patches[0]['revision'] if revert: summary = ngettext( ('Reverting 1 patch from review request ' '%(review_request_id)s (diff revision %(diff_revision)s)'), ('Reverting %(num)d patches from review request ' '%(review_request_id)s (diff revision %(diff_revision)s)'), total_patches) else: summary = ngettext( ('Applying 1 patch from review request ' '%(review_request_id)s (diff revision %(diff_revision)s)'), ('Applying %(num)d patches from review request ' '%(review_request_id)s (diff revision %(diff_revision)s)'), total_patches) logger.info( summary, { 'num': total_patches, 'review_request_id': self._review_request_id, 'diff_revision': diff_revision, }) # Start applying all the patches. for patch_data in patches: patch_num = patch_data['patch_num'] tmp_patch_file = make_tempfile(patch_data['diff']) success = self.apply_patch(diff_file_path=tmp_patch_file, base_dir=patch_data['base_dir'], patch_num=patch_num, total_patches=total_patches, revert=revert) os.unlink(tmp_patch_file) if not success: if revert: error = _('Could not apply patch %(num)d of %(total)d') else: error = _('Could not revert patch %(num)d of %(total)d') raise CommandError(error % { 'num': patch_num, 'total': total_patches, }) # If the user wants to commit, then we'll be committing every # patch individually, unless the user wants to squash commits in # which case we'll only do this on the final commit. if will_commit and (not squash or patch_num == total_patches): meta = patch_data.get('commit_meta') if meta is not None and not squash and total_patches > 1: # We are patching a commit so we already have the metadata # required without making additional HTTP requests. message = meta['message'] author = meta['author'] else: # We'll build this based on the summary/description from # the review request and the patch number. message = default_commit_message author = default_author assert message is not None assert author is not None if total_patches > 1: # Record the patch number to help differentiate, in # case we only have review request information and # not commit messages. In practice, this shouldn't # happen, as we should always have commit messages, # but it's a decent safeguard. message = '[%s/%s] %s' % (patch_num, total_patches, message) if revert: # Make it clear that this commit is reverting a prior # patch, so it's easy to identify. message = '[Revert] %s' % message try: self._tool.create_commit(message=message, author=author, run_editor=not commit_no_edit) except CreateCommitError as e: raise CommandError(six.text_type(e)) except NotImplementedError: raise CommandError('--commit is not supported with %s' % self._tool.name)