def main(self, *args): """Stamp the latest commit with corresponding review request URL""" 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) commit_message = self.tool.get_raw_commit_message(revisions) if '\nReviewed at http' in commit_message: raise CommandError('This commit is already stamped.') if not self.options.rid: self.options.rid = guess_existing_review_request_id( repository_info, self.options.repository_name, api_root, api_client, self.tool, revisions, guess_summary=False, guess_description=False, is_fuzzy_match_func=self._ask_review_request_match, no_commit_error=self.no_commit_error) if not self.options.rid: raise CommandError('Could not determine the existing review ' 'request URL to stamp with.') review_request = get_review_request(self.options.rid, api_root) stamp_url = review_request.absolute_url commit_message += '\n\nReviewed at %s' % stamp_url self.tool.amend_commit(commit_message) print('Changes committed to current branch.')
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 main(self, *args): """Create and update review requests.""" # The 'args' tuple must be made into a list for some of the # SCM Clients code. The way arguments were structured in # post-review meant this was a list, and certain parts of # the code base try and concatenate args to the end of # other lists. Until the client code is restructured and # cleaned up we will satisfy the assumption here. self.cmd_args = list(args) self.post_process_options() origcwd = os.path.abspath(os.getcwd()) 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 (self.options.exclude_patterns and not self.tool.supports_diff_exclude_patterns): raise CommandError( 'The %s backend does not support excluding files via the ' '-X/--exclude commandline options or the EXCLUDE_PATTERNS ' '.reviewboardrc option.' % self.tool.name) # Check if repository info on reviewboard server match local ones. repository_info = repository_info.find_server_repository_info(api_root) if self.options.diff_filename: self.revisions = None parent_diff = None base_commit_id = None commit_id = None if self.options.diff_filename == '-': if hasattr(sys.stdin, 'buffer'): # Make sure we get bytes on Python 3.x diff = sys.stdin.buffer.read() else: diff = sys.stdin.read() else: try: diff_path = os.path.join(origcwd, self.options.diff_filename) with open(diff_path, 'rb') as fp: diff = fp.read() except IOError as e: raise CommandError('Unable to open diff filename: %s' % e) else: self.revisions = get_revisions(self.tool, self.cmd_args) if self.revisions: extra_args = None else: extra_args = self.cmd_args # Generate a diff against the revisions or arguments, filtering # by the requested files if provided. diff_info = self.tool.diff( revisions=self.revisions, include_files=self.options.include_files or [], exclude_patterns=self.options.exclude_patterns or [], extra_args=extra_args) diff = diff_info['diff'] parent_diff = diff_info.get('parent_diff') base_commit_id = diff_info.get('base_commit_id') commit_id = diff_info.get('commit_id') repository = ( self.options.repository_name or self.options.repository_url or self.get_repository_path(repository_info, api_root)) base_dir = self.options.basedir or repository_info.base_path if repository is None: raise CommandError('Could not find the repository on the Review ' 'Board server.') if len(diff) == 0: raise CommandError("There don't seem to be any diffs!") # Validate the diffs to ensure that they can be parsed and that # all referenced files can be found. # # Review Board 2.0.14+ (with the diffs.validation.base_commit_ids # capability) is required to successfully validate against hosting # services that need a base_commit_id. This is basically due to # the limitations of a couple Git-specific hosting services # (Beanstalk, Bitbucket, and Unfuddle). # # In order to validate, we need to either not be dealing with a # base commit ID (--diff-filename), or be on a new enough version # of Review Board, or be using a non-Git repository. can_validate_base_commit_ids = \ self.tool.capabilities.has_capability('diffs', 'validation', 'base_commit_ids') if (not base_commit_id or can_validate_base_commit_ids or self.tool.name != 'Git'): # We can safely validate this diff before posting it, but we # need to ensure we only pass base_commit_id if the capability # is set. validate_kwargs = {} if can_validate_base_commit_ids: validate_kwargs['base_commit_id'] = base_commit_id try: diff_validator = api_root.get_diff_validation() diff_validator.validate_diff( repository, diff, parent_diff=parent_diff, base_dir=base_dir, **validate_kwargs) except APIError as e: msg_prefix = '' if e.error_code == 207: msg_prefix = '%s: ' % e.rsp['file'] raise CommandError('Error validating diff\n\n%s%s' % (msg_prefix, e)) except AttributeError: # The server doesn't have a diff validation resource. Post as # normal. pass if repository_info.supports_changesets and 'changenum' in diff_info: changenum = diff_info['changenum'] commit_id = changenum else: changenum = None if self.options.update and self.revisions: review_request = guess_existing_review_request( repository_info, self.options.repository_name, api_root, api_client, self.tool, self.revisions, guess_summary=False, guess_description=False, is_fuzzy_match_func=self._ask_review_request_match, submit_as=self.options.submit_as) if not review_request or not review_request.id: raise CommandError('Could not determine the existing review ' 'request to update.') self.options.rid = review_request.id # If only certain files within a commit are being submitted for review, # do not include the commit id. This prevents conflicts if multiple # files from the same commit are posted for review separately. if self.options.include_files or self.options.exclude_patterns: commit_id = None request_id, review_url = self.post_request( repository_info, repository, server_url, api_root, self.options.rid, changenum=changenum, diff_content=diff, parent_diff_content=parent_diff, commit_id=commit_id, base_commit_id=base_commit_id, submit_as=self.options.submit_as, base_dir=base_dir) diff_review_url = review_url + 'diff/' print('Review request #%s posted.' % request_id) print() print(review_url) print(diff_review_url) # Load the review up in the browser if requested to. if self.options.open_browser: try: import webbrowser if 'open_new_tab' in dir(webbrowser): # open_new_tab is only in python 2.5+ webbrowser.open_new_tab(review_url) elif 'open_new' in dir(webbrowser): webbrowser.open_new(review_url) else: os.system('start %s' % review_url) except: logging.error('Error opening review URL: %s' % review_url)
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) # 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 not self.options.destination_branch: raise CommandError('Please specify a destination branch.') if self.options.rid: is_local = branch_name is not None review_request_id = self.options.rid else: review_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 review_request or not review_request.id: raise CommandError('Could not determine the existing review ' 'request URL to land.') review_request_id = review_request.id is_local = True review_request = get_review_request(review_request_id, api_root) if self.options.is_local is not None: is_local = self.options.is_local if is_local: if branch_name is None: branch_name = self.tool.get_current_branch() if branch_name == self.options.destination_branch: raise CommandError('The local branch cannot be merged onto ' 'itself. Try a different local branch or ' 'destination branch.') else: branch_name = None land_error = self.can_land(review_request) if land_error is not None: raise CommandError('Cannot land review request %s: %s' % (review_request_id, land_error)) if self.options.recursive: # The dependency graph shows us which review requests depend on # which other ones. What we are actually after is the order to land # them in, which is the topological sorting order of the converse # graph. It just so happens that if we reverse the topological sort # of a graph, it is a valid topological sorting of the converse # graph, so we don't have to compute the converse graph. dependency_graph = review_request.build_dependency_graph() dependencies = toposort(dependency_graph)[1:] if dependencies: print('Recursively landing dependencies of review request %s.' % review_request_id) for dependency in dependencies: land_error = self.can_land(dependency) if land_error is not None: raise CommandError( 'Aborting recursive land of review request %s.\n' 'Review request %s cannot be landed: %s' % (review_request_id, dependency.id, land_error)) for dependency in reversed(dependencies): self.land(self.options.destination_branch, dependency, None, self.options.squash, self.options.edit, self.options.delete_branch, self.options.dry_run) self.land(self.options.destination_branch, review_request, branch_name, self.options.squash, self.options.edit, self.options.delete_branch, self.options.dry_run) if self.options.push: print('Pushing branch "%s" upstream' % self.options.destination_branch) if not self.options.dry_run: try: self.tool.push_upstream(self.options.destination_branch) except PushError as e: raise CommandError(six.text_type(e))
def main(self, *args): """Create and update review requests.""" # The 'args' tuple must be made into a list for some of the # SCM Clients code. The way arguments were structured in # post-review meant this was a list, and certain parts of # the code base try and concatenate args to the end of # other lists. Until the client code is restructured and # cleaned up we will satisfy the assumption here. self.cmd_args = list(args) self.post_process_options() origcwd = os.path.abspath(os.getcwd()) 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 (self.options.exclude_patterns and not self.tool.supports_diff_exclude_patterns): raise CommandError( 'The %s backend does not support excluding files via the ' '-X/--exclude commandline options or the EXCLUDE_PATTERNS ' '.reviewboardrc option.' % self.tool.name) # Check if repository info on reviewboard server match local ones. repository_info = repository_info.find_server_repository_info(api_root) if self.options.diff_filename: self.revisions = None parent_diff = None base_commit_id = None commit_id = None if self.options.diff_filename == '-': if hasattr(sys.stdin, 'buffer'): # Make sure we get bytes on Python 3.x diff = sys.stdin.buffer.read() else: diff = sys.stdin.read() else: try: diff_path = os.path.join(origcwd, self.options.diff_filename) with open(diff_path, 'rb') as fp: diff = fp.read() except IOError as e: raise CommandError('Unable to open diff filename: %s' % e) else: self.revisions = get_revisions(self.tool, self.cmd_args) if self.revisions: extra_args = None else: extra_args = self.cmd_args # Generate a diff against the revisions or arguments, filtering # by the requested files if provided. diff_info = self.tool.diff( revisions=self.revisions, include_files=self.options.include_files or [], exclude_patterns=self.options.exclude_patterns or [], extra_args=extra_args) diff = diff_info['diff'] parent_diff = diff_info.get('parent_diff') base_commit_id = diff_info.get('base_commit_id') commit_id = diff_info.get('commit_id') repository = (self.options.repository_name or self.options.repository_url or self.get_repository_path(repository_info, api_root)) base_dir = self.options.basedir or repository_info.base_path if repository is None: raise CommandError('Could not find the repository on the Review ' 'Board server.') if len(diff) == 0: raise CommandError("There don't seem to be any diffs!") # Validate the diffs to ensure that they can be parsed and that # all referenced files can be found. # # Review Board 2.0.14+ (with the diffs.validation.base_commit_ids # capability) is required to successfully validate against hosting # services that need a base_commit_id. This is basically due to # the limitations of a couple Git-specific hosting services # (Beanstalk, Bitbucket, and Unfuddle). # # In order to validate, we need to either not be dealing with a # base commit ID (--diff-filename), or be on a new enough version # of Review Board, or be using a non-Git repository. can_validate_base_commit_ids = \ self.tool.capabilities.has_capability('diffs', 'validation', 'base_commit_ids') if (not base_commit_id or can_validate_base_commit_ids or self.tool.name != 'Git'): # We can safely validate this diff before posting it, but we # need to ensure we only pass base_commit_id if the capability # is set. validate_kwargs = {} if can_validate_base_commit_ids: validate_kwargs['base_commit_id'] = base_commit_id try: diff_validator = api_root.get_diff_validation() diff_validator.validate_diff(repository, diff, parent_diff=parent_diff, base_dir=base_dir, **validate_kwargs) except APIError as e: msg_prefix = '' if e.error_code == 207: msg_prefix = '%s: ' % e.rsp['file'] raise CommandError('Error validating diff\n\n%s%s' % (msg_prefix, e)) except AttributeError: # The server doesn't have a diff validation resource. Post as # normal. pass if (repository_info.supports_changesets and not self.options.diff_filename and 'changenum' in diff_info): changenum = diff_info['changenum'] else: changenum = self.tool.get_changenum(self.revisions) # Not all scm clients support get_changenum, so if get_changenum # returns None (the default for clients that don't have changenums), # we'll prefer the existing commit_id. commit_id = changenum or commit_id if self.options.update and self.revisions: review_request = guess_existing_review_request( repository_info, self.options.repository_name, api_root, api_client, self.tool, self.revisions, guess_summary=False, guess_description=False, is_fuzzy_match_func=self._ask_review_request_match, submit_as=self.options.submit_as) if not review_request or not review_request.id: raise CommandError('Could not determine the existing review ' 'request to update.') self.options.rid = review_request.id # If only certain files within a commit are being submitted for review, # do not include the commit id. This prevents conflicts if multiple # files from the same commit are posted for review separately. if self.options.include_files or self.options.exclude_patterns: commit_id = None request_id, review_url = self.post_request( repository_info, repository, server_url, api_root, self.options.rid, changenum=changenum, diff_content=diff, parent_diff_content=parent_diff, commit_id=commit_id, base_commit_id=base_commit_id, submit_as=self.options.submit_as, base_dir=base_dir) diff_review_url = review_url + 'diff/' print('Review request #%s posted.' % request_id) print() print(review_url) print(diff_review_url) # Load the review up in the browser if requested to. if self.options.open_browser: try: import webbrowser if 'open_new_tab' in dir(webbrowser): # open_new_tab is only in python 2.5+ webbrowser.open_new_tab(review_url) elif 'open_new' in dir(webbrowser): webbrowser.open_new(review_url) else: os.system('start %s' % review_url) except: logging.error('Error opening review URL: %s' % review_url)
def main(self, *args): """Create and update review requests.""" # The 'args' tuple must be made into a list for some of the # SCM Clients code. The way arguments were structured in # post-review meant this was a list, and certain parts of # the code base try and concatenate args to the end of # other lists. Until the client code is restructured and # cleaned up we will satisfy the assumption here. self.cmd_args = list(args) self.post_process_options() origcwd = os.path.abspath(os.getcwd()) 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 (self.options.exclude_patterns and not self.tool.supports_diff_exclude_patterns): raise CommandError( 'The %s backend does not support excluding files via the ' '-X/--exclude commandline options or the EXCLUDE_PATTERNS ' '.reviewboardrc option.' % self.tool.name) # Check if repository info on reviewboard server match local ones. repository_info = repository_info.find_server_repository_info(api_root) if self.options.diff_filename: self.revisions = None parent_diff = None base_commit_id = None commit_id = None if self.options.diff_filename == '-': if hasattr(sys.stdin, 'buffer'): # Make sure we get bytes on Python 3.x diff = sys.stdin.buffer.read() else: diff = sys.stdin.read() else: try: diff_path = os.path.join(origcwd, self.options.diff_filename) with open(diff_path, 'rb') as fp: diff = fp.read() except IOError as e: raise CommandError('Unable to open diff filename: %s' % e) else: self.revisions = get_revisions(self.tool, self.cmd_args) if self.revisions: extra_args = None else: extra_args = self.cmd_args # Generate a diff against the revisions or arguments, filtering # by the requested files if provided. diff_info = self.tool.diff( revisions=self.revisions, include_files=self.options.include_files or [], exclude_patterns=self.options.exclude_patterns or [], extra_args=extra_args) diff = diff_info['diff'] parent_diff = diff_info.get('parent_diff') base_commit_id = diff_info.get('base_commit_id') commit_id = diff_info.get('commit_id') repository = (self.options.repository_name or self.options.repository_url or self.get_repository_path(repository_info, api_root)) base_dir = self.options.basedir or repository_info.base_path if len(diff) == 0: raise CommandError("There don't seem to be any diffs!") try: diff_validator = api_root.get_validation().get_diff_validation() diff_validator.validate_diff(repository, diff, parent_diff=parent_diff, base_dir=base_dir) except APIError as e: msg_prefix = '' if e.error_code == 207: msg_prefix = '%s: ' % e.rsp['file'] raise CommandError('Error validating diff\n\n%s%s' % (msg_prefix, e)) except AttributeError: # The server doesn't have a diff validation resource. Post as # normal. pass if repository_info.supports_changesets and 'changenum' in diff_info: changenum = diff_info['changenum'] commit_id = changenum else: changenum = None if not self.options.diff_filename: # If the user has requested to guess the summary or description, # get the commit message and override the summary and description # options. self.check_guess_fields() if self.options.update and self.revisions: self.options.rid = guess_existing_review_request_id( repository_info, self.options.repository_name, api_root, api_client, self.tool, self.revisions, guess_summary=False, guess_description=False, is_fuzzy_match_func=self._ask_review_request_match) if not self.options.rid: raise CommandError('Could not determine the existing review ' 'request to update.') # If only certain files within a commit are being submitted for review, # do not include the commit id. This prevents conflicts if mutliple # files from the same commit are posted for review separately. if self.options.include_files: commit_id = None request_id, review_url = self.post_request( repository_info, repository, server_url, api_root, self.options.rid, changenum=changenum, diff_content=diff, parent_diff_content=parent_diff, commit_id=commit_id, base_commit_id=base_commit_id, submit_as=self.options.submit_as, base_dir=base_dir) diff_review_url = review_url + 'diff/' print('Review request #%s posted.' % request_id) print() print(review_url) print(diff_review_url) # Load the review up in the browser if requested to. if self.options.open_browser: try: import webbrowser if 'open_new_tab' in dir(webbrowser): # open_new_tab is only in python 2.5+ webbrowser.open_new_tab(review_url) elif 'open_new' in dir(webbrowser): webbrowser.open_new(review_url) else: os.system('start %s' % review_url) except: logging.error('Error opening review URL: %s' % review_url)
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, branch_name=None, *args): """Run the command.""" self.cmd_args = list(args) if branch_name: self.cmd_args.insert(0, branch_name) if not self.tool.can_merge: raise CommandError( 'This command does not support %s repositories.' % self.tool.name) if self.options.push and not self.tool.can_push_upstream: raise CommandError('--push is not supported for %s repositories.' % self.tool.name) if self.tool.has_pending_changes(): raise CommandError('Working directory is not clean.') if not self.options.destination_branch: raise CommandError('Please specify a destination branch.') if not self.tool.can_squash_merges: # If the client doesn't support squashing, then never squash. self.options.squash = False if self.options.rid: is_local = branch_name is not None review_request_id = self.options.rid else: try: review_request = guess_existing_review_request( api_root=self.api_root, api_client=self.api_client, tool=self.tool, revisions=get_revisions(self.tool, self.cmd_args), guess_summary=False, guess_description=False, is_fuzzy_match_func=self._ask_review_request_match, repository_id=self.repository.id) except ValueError as e: raise CommandError(six.text_type(e)) if not review_request or not review_request.id: raise CommandError('Could not determine the existing review ' 'request URL to land.') review_request_id = review_request.id is_local = True try: review_request = self.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)) if self.options.is_local is not None: is_local = self.options.is_local if is_local: if branch_name is None: branch_name = self.tool.get_current_branch() if branch_name == self.options.destination_branch: raise CommandError('The local branch cannot be merged onto ' 'itself. Try a different local branch or ' 'destination branch.') else: branch_name = None land_error = self.can_land(review_request) if land_error is not None: raise CommandError('Cannot land review request %s: %s' % (review_request_id, land_error)) land_kwargs = { 'delete_branch': self.options.delete_branch, 'destination_branch': self.options.destination_branch, 'dry_run': self.options.dry_run, 'edit': self.options.edit, 'squash': self.options.squash, } self.json.add('landed_review_requests', []) if self.options.recursive: # The dependency graph shows us which review requests depend on # which other ones. What we are actually after is the order to land # them in, which is the topological sorting order of the converse # graph. It just so happens that if we reverse the topological sort # of a graph, it is a valid topological sorting of the converse # graph, so we don't have to compute the converse graph. dependency_graph = review_request.build_dependency_graph() dependencies = toposort(dependency_graph)[1:] if dependencies: self.stdout.write('Recursively landing dependencies of ' 'review request %s.' % review_request_id) for dependency in dependencies: land_error = self.can_land(dependency) if land_error is not None: raise CommandError( 'Aborting recursive land of review request %s.\n' 'Review request %s cannot be landed: %s' % (review_request_id, dependency.id, land_error)) for dependency in reversed(dependencies): self.land(review_request=dependency, **land_kwargs) self.land(review_request=review_request, source_branch=branch_name, **land_kwargs) if self.options.push: self.stdout.write('Pushing branch "%s" upstream' % self.options.destination_branch) if not self.options.dry_run: try: self.tool.push_upstream(self.options.destination_branch) except PushError as e: raise CommandError(six.text_type(e))