Exemple #1
0
    def determine_review_request(self, api_client, api_root, repository_info,
                                 repository_name, revisions):
        """Determine the correct review request for a commit.

        A tuple (review request ID, review request absolute URL) is returned.
        If no review request ID is found by any of the strategies,
        (None, None) is returned.
        """
        # First, try to match the changeset to a review request directly.
        if repository_info.supports_changesets:
            review_request = find_review_request_by_change_id(
                api_client, api_root, repository_info, repository_name,
                revisions)

            if review_request and review_request.id:
                return review_request.id, review_request.absolute_url

        # Fall back on guessing based on the description. This may return None
        # if no suitable review request is found.
        logging.debug('Attempting to guess review request based on '
                      'summary and description')
        review_request = guess_existing_review_request(
            repository_info, 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 review_request:
            logging.debug('Found review request ID %d' % review_request.id)
            return review_request.id, review_request.absolute_url
        else:
            logging.debug('Could not find a matching review request')
            return None, None
Exemple #2
0
    def determine_review_request(self, api_client, api_root, repository_info,
                                 repository_name, revisions):
        """Determine the correct review request for a commit.

        A tuple (review request ID, review request absolute URL) is returned.
        If no review request ID is found by any of the strategies,
        (None, None) is returned.
        """
        # First, try to match the changeset to a review request directly.
        if repository_info.supports_changesets:
            review_request = find_review_request_by_change_id(
                api_client, api_root, repository_info, repository_name,
                revisions)

            if review_request and review_request.id:
                return review_request.id, review_request.absolute_url

        # Fall back on guessing based on the description. This may return None
        # if no suitable review request is found.
        logging.debug('Attempting to guess review request based on '
                      'summary and description')
        review_request = guess_existing_review_request(
            repository_info, 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 review_request:
            logging.debug('Found review request ID %d' % review_request.id)
            return review_request.id, review_request.absolute_url
        else:
            logging.debug('Could not find a matching review request')
            return None, None
Exemple #3
0
    def determine_review_request(self, revisions):
        """Determine the correct review request for a commit.

        Args:
            revisions (dict):
                The parsed revisions from the command line.

        Returns:
            tuple:
            A 2-tuple of the matched review request ID, and the review request
            URL. If no matching review request is found, both values will be
            ``None``.

        Raises:
            rbtools.commands.CommandError:
                An error occurred while attempting to find a matching review
                request.
        """
        # First, try to match the changeset to a review request directly.
        if self.tool.supports_changesets:
            review_request = find_review_request_by_change_id(
                api_client=self.api_client,
                api_root=self.api_root,
                revisions=revisions,
                repository_id=self.repository.id)

            if review_request and review_request.id:
                return review_request.id, review_request.absolute_url

        # Fall back on guessing based on the description. This may return None
        # if no suitable review request is found.
        logging.debug('Attempting to guess review request based on '
                      'summary and description')

        try:
            review_request = guess_existing_review_request(
                api_root=self.api_root,
                api_client=self.api_client,
                tool=self.tool,
                revisions=revisions,
                guess_summary=False,
                guess_description=False,
                is_fuzzy_match_func=self._ask_review_request_match,
                no_commit_error=self.no_commit_error,
                repository_id=self.repository.id)
        except ValueError as e:
            raise CommandError(six.text_type(e))

        if review_request:
            logging.debug('Found review request ID %d', review_request.id)
            return review_request.id, review_request.absolute_url
        else:
            logging.debug('Could not find a matching review request')
            return None, None
Exemple #4
0
    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)
Exemple #5
0
    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))
Exemple #6
0
    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)
Exemple #7
0
    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))
Exemple #8
0
    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))
Exemple #9
0
    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))
Exemple #10
0
    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))