Blame dnf/cli/commands/__init__.py

Packit 6f3914
# Copyright 2006 Duke University
Packit 6f3914
# Copyright (C) 2012-2016 Red Hat, Inc.
Packit 6f3914
#
Packit 6f3914
# This program is free software; you can redistribute it and/or modify
Packit 6f3914
# it under the terms of the GNU General Public License as published by
Packit 6f3914
# the Free Software Foundation; either version 2 of the License, or
Packit 6f3914
# (at your option) any later version.
Packit 6f3914
#
Packit 6f3914
# This program is distributed in the hope that it will be useful,
Packit 6f3914
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6f3914
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 6f3914
# GNU Library General Public License for more details.
Packit 6f3914
#
Packit 6f3914
# You should have received a copy of the GNU General Public License
Packit 6f3914
# along with this program; if not, write to the Free Software
Packit 6f3914
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Packit 6f3914
#
Packit 6f3914
# Written by Seth Vidal
Packit 6f3914
Packit 6f3914
"""
Packit 6f3914
Classes for subcommands of the yum command line interface.
Packit 6f3914
"""
Packit 6f3914
Packit 6f3914
from __future__ import print_function
Packit 6f3914
from __future__ import unicode_literals
Packit 6f3914
Packit 6f3914
import libdnf
Packit 6f3914
Packit 6f3914
from dnf.cli.option_parser import OptionParser
Packit 6f3914
from dnf.i18n import _, ucd
Packit 6f3914
Packit 6f3914
import argparse
Packit 6f3914
import dnf.cli
Packit 6f3914
import dnf.cli.demand
Packit 6f3914
import dnf.const
Packit 6f3914
import dnf.exceptions
Packit 6f3914
import dnf.i18n
Packit 6f3914
import dnf.pycomp
Packit 6f3914
import dnf.transaction
Packit 6f3914
import dnf.util
Packit 6f3914
import functools
Packit 6f3914
import logging
Packit 6f3914
import operator
Packit 6f3914
import os
Packit 6f3914
Packit 6f3914
logger = logging.getLogger('dnf')
Packit 6f3914
_RPM_VERIFY = _("To diagnose the problem, try running: '%s'.") % \
Packit 6f3914
    'rpm -Va --nofiles --nodigest'
Packit 6f3914
_RPM_REBUILDDB = _("You probably have corrupted RPMDB, running '%s'"
Packit 6f3914
                   " might fix the issue.") % 'rpm --rebuilddb'
Packit 6f3914
Packit 6f3914
gpg_msg = \
Packit 6f3914
    _("""You have enabled checking of packages via GPG keys. This is a good thing.
Packit 6f3914
However, you do not have any GPG public keys installed. You need to download
Packit 6f3914
the keys for packages you wish to install and install them.
Packit 6f3914
You can do that by running the command:
Packit 6f3914
    rpm --import public.gpg.key
Packit 6f3914
Packit 6f3914
Packit 6f3914
Alternatively you can specify the url to the key you would like to use
Packit 6f3914
for a repository in the 'gpgkey' option in a repository section and {prog}
Packit 6f3914
will install it for you.
Packit 6f3914
Packit 6f3914
For more information contact your distribution or package provider.""")
Packit 6f3914
Packit 6f3914
Packit 6f3914
def _checkGPGKey(base, cli):
Packit 6f3914
    """Verify that there are gpg keys for the enabled repositories in the
Packit 6f3914
    rpm database.
Packit 6f3914
Packit 6f3914
    :param base: a :class:`dnf.Base` object.
Packit 6f3914
    :raises: :class:`cli.CliError`
Packit 6f3914
    """
Packit 6f3914
    if not base.conf.gpgcheck:
Packit 6f3914
        return
Packit 6f3914
    if not base._gpg_key_check():
Packit 6f3914
        for repo in base.repos.iter_enabled():
Packit 6f3914
            if (repo.gpgcheck or repo.repo_gpgcheck) and not repo.gpgkey:
Packit 6f3914
                logger.critical("\n%s\n", gpg_msg.format(prog=dnf.util.MAIN_PROG_UPPER))
Packit 6f3914
                logger.critical(_("Problem repository: %s"), repo)
Packit 6f3914
                raise dnf.cli.CliError
Packit 6f3914
Packit 6f3914
Packit 6f3914
def _checkEnabledRepo(base, possible_local_files=()):
Packit 6f3914
    """Verify that there is at least one enabled repo.
Packit 6f3914
Packit 6f3914
    :param base: a :class:`dnf.Base` object.
Packit 6f3914
    :param possible_local_files: the list of strings that could be a local rpms
Packit 6f3914
    :raises: :class:`cli.CliError`:
Packit 6f3914
    """
Packit 6f3914
    if base.repos._any_enabled():
Packit 6f3914
        return
Packit 6f3914
Packit 6f3914
    for lfile in possible_local_files:
Packit 6f3914
        if lfile.endswith(".rpm") and os.path.exists(lfile):
Packit 6f3914
            return
Packit 6f3914
        scheme = dnf.pycomp.urlparse.urlparse(lfile)[0]
Packit 6f3914
        if scheme in ('http', 'ftp', 'file', 'https'):
Packit 6f3914
            return
Packit 6f3914
    msg = _('There are no enabled repositories in "{}".').format('", "'.join(base.conf.reposdir))
Packit 6f3914
    raise dnf.cli.CliError(msg)
Packit 6f3914
Packit 6f3914
Packit 6f3914
class Command(object):
Packit 6f3914
    """Abstract base class for CLI commands."""
Packit 6f3914
Packit 6f3914
    aliases = [] # :api
Packit 6f3914
    summary = ""  # :api
Packit 6f3914
    opts = None
Packit 6f3914
Packit 6f3914
    def __init__(self, cli):
Packit 6f3914
        # :api
Packit 6f3914
        self.cli = cli
Packit 6f3914
Packit 6f3914
    @property
Packit 6f3914
    def base(self):
Packit 6f3914
        # :api
Packit 6f3914
        return self.cli.base
Packit 6f3914
Packit 6f3914
    @property
Packit 6f3914
    def _basecmd(self):
Packit 6f3914
        return self.aliases[0]
Packit 6f3914
Packit 6f3914
    @property
Packit 6f3914
    def output(self):
Packit 6f3914
        return self.cli.base.output
Packit 6f3914
Packit 6f3914
    def set_argparser(self, parser):
Packit 6f3914
        """Define command specific options and arguments. #:api"""
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
    def pre_configure(self):
Packit 6f3914
        # :api
Packit 6f3914
        """Do any command-specific pre-configuration."""
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
    def configure(self):
Packit 6f3914
        # :api
Packit 6f3914
        """Do any command-specific configuration."""
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
    def get_error_output(self, error):
Packit 6f3914
        """Get suggestions for resolving the given error."""
Packit 6f3914
        if isinstance(error, dnf.exceptions.TransactionCheckError):
Packit 6f3914
            return (_RPM_VERIFY, _RPM_REBUILDDB)
Packit 6f3914
        raise NotImplementedError('error not supported yet: %s' % error)
Packit 6f3914
Packit 6f3914
    def run(self):
Packit 6f3914
        # :api
Packit 6f3914
        """Execute the command."""
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
    def run_transaction(self):
Packit 6f3914
        """Finalize operations post-transaction."""
Packit 6f3914
        pass
Packit 6f3914
Packit 6f3914
class InfoCommand(Command):
Packit 6f3914
    """A class containing methods needed by the cli to execute the
Packit 6f3914
    info command.
Packit 6f3914
    """
Packit 6f3914
Packit 6f3914
    aliases = ('info',)
Packit 6f3914
    summary = _('display details about a package or group of packages')
Packit 6f3914
    DEFAULT_PKGNARROW = 'all'
Packit 6f3914
    pkgnarrows = {'available', 'installed', 'extras', 'updates', 'upgrades',
Packit 6f3914
                  'autoremove', 'recent', 'obsoletes', DEFAULT_PKGNARROW}
Packit 6f3914
Packit 6f3914
    @classmethod
Packit 6f3914
    def set_argparser(cls, parser):
Packit 6f3914
        narrows = parser.add_mutually_exclusive_group()
Packit 6f3914
        narrows.add_argument('--all', dest='_packages_action',
Packit 6f3914
                             action='store_const', const='all', default=None,
Packit 6f3914
                             help=_("show all packages (default)"))
Packit 6f3914
        narrows.add_argument('--available', dest='_packages_action',
Packit 6f3914
                             action='store_const', const='available',
Packit 6f3914
                             help=_("show only available packages"))
Packit 6f3914
        narrows.add_argument('--installed', dest='_packages_action',
Packit 6f3914
                             action='store_const', const='installed',
Packit 6f3914
                             help=_("show only installed packages"))
Packit 6f3914
        narrows.add_argument('--extras', dest='_packages_action',
Packit 6f3914
                             action='store_const', const='extras',
Packit 6f3914
                             help=_("show only extras packages"))
Packit 6f3914
        narrows.add_argument('--updates', dest='_packages_action',
Packit 6f3914
                             action='store_const', const='upgrades',
Packit 6f3914
                             help=_("show only upgrades packages"))
Packit 6f3914
        narrows.add_argument('--upgrades', dest='_packages_action',
Packit 6f3914
                             action='store_const', const='upgrades',
Packit 6f3914
                             help=_("show only upgrades packages"))
Packit 6f3914
        narrows.add_argument('--autoremove', dest='_packages_action',
Packit 6f3914
                             action='store_const', const='autoremove',
Packit 6f3914
                             help=_("show only autoremove packages"))
Packit 6f3914
        narrows.add_argument('--recent', dest='_packages_action',
Packit 6f3914
                             action='store_const', const='recent',
Packit 6f3914
                             help=_("show only recently changed packages"))
Packit 6f3914
        parser.add_argument('packages', nargs='*', metavar=_('PACKAGE'),
Packit 6f3914
                            choices=cls.pkgnarrows, default=cls.DEFAULT_PKGNARROW,
Packit 6f3914
                            action=OptionParser.PkgNarrowCallback,
Packit 6f3914
                            help=_("Package name specification"))
Packit 6f3914
Packit 6f3914
    def configure(self):
Packit 6f3914
        demands = self.cli.demands
Packit 6f3914
        demands.sack_activation = True
Packit 6f3914
        if self.opts._packages_action:
Packit 6f3914
            self.opts.packages_action = self.opts._packages_action
Packit 6f3914
        if self.opts.packages_action != 'installed':
Packit 6f3914
            demands.available_repos = True
Packit 6f3914
        if self.opts.obsoletes:
Packit 6f3914
            if self.opts._packages_action:
Packit 6f3914
                self.cli._option_conflict("--obsoletes", "--" + self.opts._packages_action)
Packit 6f3914
            else:
Packit 6f3914
                self.opts.packages_action = 'obsoletes'
Packit 6f3914
        if self.opts.packages_action == 'updates':
Packit 6f3914
            self.opts.packages_action = 'upgrades'
Packit 6f3914
Packit 6f3914
    def run(self):
Packit 6f3914
        self.cli._populate_update_security_filter(self.opts, self.base.sack.query())
Packit 6f3914
        return self.base.output_packages('info', self.opts.packages_action,
Packit 6f3914
                                         self.opts.packages)
Packit 6f3914
Packit 6f3914
class ListCommand(InfoCommand):
Packit 6f3914
    """A class containing methods needed by the cli to execute the
Packit 6f3914
    list command.
Packit 6f3914
    """
Packit 6f3914
Packit 6f3914
    aliases = ('list',)
Packit 6f3914
    summary = _('list a package or groups of packages')
Packit 6f3914
Packit 6f3914
    def run(self):
Packit 6f3914
        self.cli._populate_update_security_filter(self.opts, self.base.sack.query())
Packit 6f3914
        return self.base.output_packages('list', self.opts.packages_action,
Packit 6f3914
                                         self.opts.packages)
Packit 6f3914
Packit 6f3914
Packit 6f3914
class ProvidesCommand(Command):
Packit 6f3914
    """A class containing methods needed by the cli to execute the
Packit 6f3914
    provides command.
Packit 6f3914
    """
Packit 6f3914
Packit 6f3914
    aliases = ('provides', 'whatprovides', 'prov')
Packit 6f3914
    summary = _('find what package provides the given value')
Packit 6f3914
Packit 6f3914
    @staticmethod
Packit 6f3914
    def set_argparser(parser):
Packit 6f3914
        parser.add_argument('dependency', nargs='+', metavar=_('PROVIDE'),
Packit 6f3914
                            help=_("Provide specification to search for"))
Packit 6f3914
Packit 6f3914
    def configure(self):
Packit 6f3914
        demands = self.cli.demands
Packit 6f3914
        demands.available_repos = True
Packit 6f3914
        demands.fresh_metadata = False
Packit 6f3914
        demands.sack_activation = True
Packit 6f3914
Packit 6f3914
    def run(self):
Packit 6f3914
        logger.debug(_("Searching Packages: "))
Packit 6f3914
        return self.base.provides(self.opts.dependency)
Packit 6f3914
Packit 6f3914
class CheckUpdateCommand(Command):
Packit 6f3914
    """A class containing methods needed by the cli to execute the
Packit 6f3914
    check-update command.
Packit 6f3914
    """
Packit 6f3914
Packit 6f3914
    aliases = ('check-update', 'check-upgrade')
Packit 6f3914
    summary = _('check for available package upgrades')
Packit 6f3914
Packit 6f3914
    @staticmethod
Packit 6f3914
    def set_argparser(parser):
Packit 6f3914
        parser.add_argument('--changelogs', dest='changelogs',
Packit 6f3914
                            default=False, action='store_true',
Packit 6f3914
                            help=_('show changelogs before update'))
Packit 6f3914
        parser.add_argument('packages', nargs='*', metavar=_('PACKAGE'))
Packit 6f3914
Packit 6f3914
    def configure(self):
Packit 6f3914
        demands = self.cli.demands
Packit 6f3914
        demands.sack_activation = True
Packit 6f3914
        demands.available_repos = True
Packit 6f3914
        demands.plugin_filtering_enabled = True
Packit 6f3914
        if self.opts.changelogs:
Packit 6f3914
            demands.changelogs = True
Packit 6f3914
        _checkEnabledRepo(self.base)
Packit 6f3914
Packit 6f3914
    def run(self):
Packit Service e4a73a
        query = self.base.sack.query().upgrades()
Packit 6f3914
        if self.base.conf.obsoletes:
Packit 6f3914
            obsoleted = query.union(self.base.sack.query().installed())
Packit 6f3914
            obsoletes = self.base.sack.query().filter(obsoletes=obsoleted)
Packit 6f3914
            query = query.union(obsoletes)
Packit 6f3914
        self.cli._populate_update_security_filter(self.opts, query, cmp_type="gte")
Packit 6f3914
Packit 6f3914
        found = self.base.check_updates(self.opts.packages, print_=True,
Packit 6f3914
                                        changelogs=self.opts.changelogs)
Packit 6f3914
        if found:
Packit 6f3914
            self.cli.demands.success_exit_status = 100
Packit 6f3914
Packit 6f3914
        if self.base.conf.autocheck_running_kernel:
Packit 6f3914
            self.cli._check_running_kernel()
Packit 6f3914
Packit 6f3914
Packit 6f3914
class RepoPkgsCommand(Command):
Packit 6f3914
    """Implementation of the repository-packages command."""
Packit 6f3914
Packit 6f3914
    class CheckUpdateSubCommand(Command):
Packit 6f3914
        """Implementation of the info sub-command."""
Packit 6f3914
Packit 6f3914
        aliases = ('check-update',)
Packit 6f3914
Packit 6f3914
        def configure(self):
Packit 6f3914
            demands = self.cli.demands
Packit 6f3914
            demands.available_repos = True
Packit 6f3914
            demands.sack_activation = True
Packit 6f3914
Packit 6f3914
        def run_on_repo(self):
Packit 6f3914
            """Execute the command with respect to given arguments *cli_args*."""
Packit 6f3914
            found = self.base.check_updates(self.opts.pkg_specs,
Packit 6f3914
                                            self.reponame, print_=True)
Packit 6f3914
            if found:
Packit 6f3914
                self.cli.demands.success_exit_status = 100
Packit 6f3914
Packit 6f3914
    class InfoSubCommand(Command):
Packit 6f3914
        """Implementation of the info sub-command."""
Packit 6f3914
Packit 6f3914
        aliases = ('info',)
Packit 6f3914
Packit 6f3914
        def configure(self):
Packit 6f3914
            demands = self.cli.demands
Packit 6f3914
            demands.sack_activation = True
Packit 6f3914
            if self.opts._pkg_specs_action:
Packit 6f3914
                self.opts.pkg_specs_action = self.opts._pkg_specs_action
Packit 6f3914
            if self.opts.pkg_specs_action != 'installed':
Packit 6f3914
                demands.available_repos = True
Packit 6f3914
            if self.opts.obsoletes:
Packit 6f3914
                if self.opts._pkg_specs_action:
Packit 6f3914
                    self.cli._option_conflict("--obsoletes", "--" + self.opts._pkg_specs_action)
Packit 6f3914
                else:
Packit 6f3914
                    self.opts.pkg_specs_action = 'obsoletes'
Packit 6f3914
Packit 6f3914
        def run_on_repo(self):
Packit 6f3914
            """Execute the command with respect to given arguments *cli_args*."""
Packit 6f3914
            self.cli._populate_update_security_filter(self.opts, self.base.sack.query())
Packit 6f3914
            self.base.output_packages('info', self.opts.pkg_specs_action,
Packit 6f3914
                                      self.opts.pkg_specs, self.reponame)
Packit 6f3914
Packit 6f3914
    class InstallSubCommand(Command):
Packit 6f3914
        """Implementation of the install sub-command."""
Packit 6f3914
Packit 6f3914
        aliases = ('install',)
Packit 6f3914
Packit 6f3914
        def configure(self):
Packit 6f3914
            demands = self.cli.demands
Packit 6f3914
            demands.available_repos = True
Packit 6f3914
            demands.sack_activation = True
Packit 6f3914
            demands.resolving = True
Packit 6f3914
            demands.root_user = True
Packit 6f3914
Packit 6f3914
        def run_on_repo(self):
Packit 6f3914
            self.cli._populate_update_security_filter(self.opts, self.base.sack.query())
Packit 6f3914
            """Execute the command with respect to given arguments *cli_args*."""
Packit 6f3914
            _checkGPGKey(self.base, self.cli)
Packit 6f3914
Packit 6f3914
            done = False
Packit 6f3914
Packit 6f3914
            if not self.opts.pkg_specs:
Packit 6f3914
                # Install all packages.
Packit 6f3914
                try:
Packit 6f3914
                    self.base.install('*', self.reponame)
Packit 6f3914
                except dnf.exceptions.MarkingError:
Packit 6f3914
                    logger.info(_('No package available.'))
Packit 6f3914
                else:
Packit 6f3914
                    done = True
Packit 6f3914
            else:
Packit 6f3914
                # Install packages.
Packit 6f3914
                for pkg_spec in self.opts.pkg_specs:
Packit 6f3914
                    try:
Packit 6f3914
                        self.base.install(pkg_spec, self.reponame)
Packit 6f3914
                    except dnf.exceptions.MarkingError as e:
Packit 6f3914
                        msg = '{}: {}'.format(e.value, self.base.output.term.bold(pkg_spec))
Packit 6f3914
                        logger.info(msg)
Packit 6f3914
                    else:
Packit 6f3914
                        done = True
Packit 6f3914
Packit 6f3914
            if not done:
Packit 6f3914
                raise dnf.exceptions.Error(_('No packages marked for install.'))
Packit 6f3914
Packit 6f3914
    class ListSubCommand(InfoSubCommand):
Packit 6f3914
        """Implementation of the list sub-command."""
Packit 6f3914
Packit 6f3914
        aliases = ('list',)
Packit 6f3914
Packit 6f3914
        def run_on_repo(self):
Packit 6f3914
            """Execute the command with respect to given arguments *cli_args*."""
Packit 6f3914
            self.cli._populate_update_security_filter(self.opts, self.base.sack.query())
Packit 6f3914
            self.base.output_packages('list', self.opts.pkg_specs_action,
Packit 6f3914
                                      self.opts.pkg_specs, self.reponame)
Packit 6f3914
Packit 6f3914
    class MoveToSubCommand(Command):
Packit 6f3914
        """Implementation of the move-to sub-command."""
Packit 6f3914
Packit 6f3914
        aliases = ('move-to',)
Packit 6f3914
Packit 6f3914
        def configure(self):
Packit 6f3914
            demands = self.cli.demands
Packit 6f3914
            demands.sack_activation = True
Packit 6f3914
            demands.available_repos = True
Packit 6f3914
            demands.resolving = True
Packit 6f3914
            demands.root_user = True
Packit 6f3914
Packit 6f3914
        def run_on_repo(self):
Packit 6f3914
            """Execute the command with respect to given arguments *cli_args*."""
Packit 6f3914
            _checkGPGKey(self.base, self.cli)
Packit 6f3914
Packit 6f3914
            done = False
Packit 6f3914
Packit 6f3914
            if not self.opts.pkg_specs:
Packit 6f3914
                # Reinstall all packages.
Packit 6f3914
                try:
Packit 6f3914
                    self.base.reinstall('*', new_reponame=self.reponame)
Packit 6f3914
                except dnf.exceptions.PackagesNotInstalledError:
Packit 6f3914
                    logger.info(_('No package installed.'))
Packit 6f3914
                except dnf.exceptions.PackagesNotAvailableError:
Packit 6f3914
                    logger.info(_('No package available.'))
Packit 6f3914
                except dnf.exceptions.MarkingError:
Packit 6f3914
                    assert False, 'Only the above marking errors are expected.'
Packit 6f3914
                else:
Packit 6f3914
                    done = True
Packit 6f3914
            else:
Packit 6f3914
                # Reinstall packages.
Packit 6f3914
                for pkg_spec in self.opts.pkg_specs:
Packit 6f3914
                    try:
Packit 6f3914
                        self.base.reinstall(pkg_spec, new_reponame=self.reponame)
Packit 6f3914
                    except dnf.exceptions.PackagesNotInstalledError:
Packit 6f3914
                        msg = _('No match for argument: %s')
Packit 6f3914
                        logger.info(msg, pkg_spec)
Packit 6f3914
                    except dnf.exceptions.PackagesNotAvailableError as err:
Packit 6f3914
                        for pkg in err.packages:
Packit 6f3914
                            xmsg = ''
Packit 6f3914
                            pkgrepo = self.base.history.repo(pkg)
Packit 6f3914
                            if pkgrepo:
Packit 6f3914
                                xmsg = _(' (from %s)') % pkgrepo
Packit 6f3914
                            msg = _('Installed package %s%s not available.')
Packit 6f3914
                            logger.info(msg, self.output.term.bold(pkg), xmsg)
Packit 6f3914
                    except dnf.exceptions.MarkingError:
Packit 6f3914
                        assert False, \
Packit 6f3914
                               'Only the above marking errors are expected.'
Packit 6f3914
                    else:
Packit 6f3914
                        done = True
Packit 6f3914
Packit 6f3914
            if not done:
Packit 6f3914
                raise dnf.exceptions.Error(_('Nothing to do.'))
Packit 6f3914
Packit 6f3914
    class ReinstallOldSubCommand(Command):
Packit 6f3914
        """Implementation of the reinstall-old sub-command."""
Packit 6f3914
Packit 6f3914
        aliases = ('reinstall-old',)
Packit 6f3914
Packit 6f3914
        def configure(self):
Packit 6f3914
            demands = self.cli.demands
Packit 6f3914
            demands.sack_activation = True
Packit 6f3914
            demands.available_repos = True
Packit 6f3914
            demands.resolving = True
Packit 6f3914
            demands.root_user = True
Packit 6f3914
Packit 6f3914
        def run_on_repo(self):
Packit 6f3914
            """Execute the command with respect to given arguments *cli_args*."""
Packit 6f3914
            _checkGPGKey(self.base, self.cli)
Packit 6f3914
Packit 6f3914
            done = False
Packit 6f3914
Packit 6f3914
            if not self.opts.pkg_specs:
Packit 6f3914
                # Reinstall all packages.
Packit 6f3914
                try:
Packit 6f3914
                    self.base.reinstall('*', self.reponame, self.reponame)
Packit 6f3914
                except dnf.exceptions.PackagesNotInstalledError:
Packit 6f3914
                    msg = _('No package installed from the repository.')
Packit 6f3914
                    logger.info(msg)
Packit 6f3914
                except dnf.exceptions.PackagesNotAvailableError:
Packit 6f3914
                    logger.info(_('No package available.'))
Packit 6f3914
                except dnf.exceptions.MarkingError:
Packit 6f3914
                    assert False, 'Only the above marking errors are expected.'
Packit 6f3914
                else:
Packit 6f3914
                    done = True
Packit 6f3914
            else:
Packit 6f3914
                # Reinstall packages.
Packit 6f3914
                for pkg_spec in self.opts.pkg_specs:
Packit 6f3914
                    try:
Packit 6f3914
                        self.base.reinstall(pkg_spec, self.reponame,
Packit 6f3914
                                            self.reponame)
Packit 6f3914
                    except dnf.exceptions.PackagesNotInstalledError:
Packit 6f3914
                        msg = _('No match for argument: %s')
Packit 6f3914
                        logger.info(msg, pkg_spec)
Packit 6f3914
                    except dnf.exceptions.PackagesNotAvailableError as err:
Packit 6f3914
                        for pkg in err.packages:
Packit 6f3914
                            xmsg = ''
Packit 6f3914
                            pkgrepo = self.base.history.repo(pkg)
Packit 6f3914
                            if pkgrepo:
Packit 6f3914
                                xmsg = _(' (from %s)') % pkgrepo
Packit 6f3914
                            msg = _('Installed package %s%s not available.')
Packit 6f3914
                            logger.info(msg, self.output.term.bold(pkg), xmsg)
Packit 6f3914
                    except dnf.exceptions.MarkingError:
Packit 6f3914
                        assert False, \
Packit 6f3914
                               'Only the above marking errors are expected.'
Packit 6f3914
                    else:
Packit 6f3914
                        done = True
Packit 6f3914
Packit 6f3914
            if not done:
Packit 6f3914
                raise dnf.exceptions.Error(_('Nothing to do.'))
Packit 6f3914
Packit 6f3914
    class ReinstallSubCommand(Command):
Packit 6f3914
        """Implementation of the reinstall sub-command."""
Packit 6f3914
Packit 6f3914
        aliases = ('reinstall',)
Packit 6f3914
Packit 6f3914
        def __init__(self, cli):
Packit 6f3914
            """Initialize the command."""
Packit 6f3914
            super(RepoPkgsCommand.ReinstallSubCommand, self).__init__(cli)
Packit 6f3914
            self.wrapped_commands = (RepoPkgsCommand.ReinstallOldSubCommand(cli),
Packit 6f3914
                                     RepoPkgsCommand.MoveToSubCommand(cli))
Packit 6f3914
Packit 6f3914
        def configure(self):
Packit 6f3914
            self.cli.demands.available_repos = True
Packit 6f3914
            for command in self.wrapped_commands:
Packit 6f3914
                command.opts = self.opts
Packit 6f3914
                command.reponame = self.reponame
Packit 6f3914
                command.configure()
Packit 6f3914
Packit 6f3914
        def run_on_repo(self):
Packit 6f3914
            """Execute the command with respect to given arguments *cli_args*."""
Packit 6f3914
            _checkGPGKey(self.base, self.cli)
Packit 6f3914
            for command in self.wrapped_commands:
Packit 6f3914
                try:
Packit 6f3914
                    command.run_on_repo()
Packit 6f3914
                except dnf.exceptions.Error:
Packit 6f3914
                    continue
Packit 6f3914
                else:
Packit 6f3914
                    break
Packit 6f3914
            else:
Packit 6f3914
                raise dnf.exceptions.Error(_('No packages marked for reinstall.'))
Packit 6f3914
Packit 6f3914
    class RemoveOrDistroSyncSubCommand(Command):
Packit 6f3914
        """Implementation of the remove-or-distro-sync sub-command."""
Packit 6f3914
Packit 6f3914
        aliases = ('remove-or-distro-sync',)
Packit 6f3914
Packit 6f3914
        def configure(self):
Packit 6f3914
            demands = self.cli.demands
Packit 6f3914
            demands.available_repos = True
Packit 6f3914
            demands.sack_activation = True
Packit 6f3914
            demands.resolving = True
Packit 6f3914
            demands.root_user = True
Packit 6f3914
Packit 6f3914
        def _replace(self, pkg_spec, reponame):
Packit 6f3914
            """Synchronize a package with another repository or remove it."""
Packit 6f3914
            self.cli.base.sack.disable_repo(reponame)
Packit 6f3914
Packit 6f3914
            subject = dnf.subject.Subject(pkg_spec)
Packit 6f3914
            matches = subject.get_best_query(self.cli.base.sack)
Packit 6f3914
            history = self.cli.base.history
Packit 6f3914
            installed = [
Packit 6f3914
                pkg for pkg in matches.installed()
Packit 6f3914
                if history.repo(pkg) == reponame]
Packit 6f3914
            if not installed:
Packit 6f3914
                raise dnf.exceptions.PackagesNotInstalledError(
Packit 6f3914
                    'no package matched', pkg_spec)
Packit 6f3914
            available = matches.available()
Packit 6f3914
            clean_deps = self.cli.base.conf.clean_requirements_on_remove
Packit 6f3914
            for package in installed:
Packit 6f3914
                if available.filter(name=package.name, arch=package.arch):
Packit 6f3914
                    self.cli.base._goal.distupgrade(package)
Packit 6f3914
                else:
Packit 6f3914
                    self.cli.base._goal.erase(package, clean_deps=clean_deps)
Packit 6f3914
Packit 6f3914
        def run_on_repo(self):
Packit 6f3914
            """Execute the command with respect to given arguments *cli_args*."""
Packit 6f3914
            _checkGPGKey(self.base, self.cli)
Packit 6f3914
Packit 6f3914
            done = False
Packit 6f3914
Packit 6f3914
            if not self.opts.pkg_specs:
Packit 6f3914
                # Sync all packages.
Packit 6f3914
                try:
Packit 6f3914
                    self._replace('*', self.reponame)
Packit 6f3914
                except dnf.exceptions.PackagesNotInstalledError:
Packit 6f3914
                    msg = _('No package installed from the repository.')
Packit 6f3914
                    logger.info(msg)
Packit 6f3914
                else:
Packit 6f3914
                    done = True
Packit 6f3914
            else:
Packit 6f3914
                # Reinstall packages.
Packit 6f3914
                for pkg_spec in self.opts.pkg_specs:
Packit 6f3914
                    try:
Packit 6f3914
                        self._replace(pkg_spec, self.reponame)
Packit 6f3914
                    except dnf.exceptions.PackagesNotInstalledError:
Packit 6f3914
                        msg = _('No match for argument: %s')
Packit 6f3914
                        logger.info(msg, pkg_spec)
Packit 6f3914
                    else:
Packit 6f3914
                        done = True
Packit 6f3914
Packit 6f3914
            if not done:
Packit 6f3914
                raise dnf.exceptions.Error(_('Nothing to do.'))
Packit 6f3914
Packit 6f3914
    class RemoveOrReinstallSubCommand(Command):
Packit 6f3914
        """Implementation of the remove-or-reinstall sub-command."""
Packit 6f3914
Packit 6f3914
        aliases = ('remove-or-reinstall',)
Packit 6f3914
Packit 6f3914
        def configure(self):
Packit 6f3914
            demands = self.cli.demands
Packit 6f3914
            demands.sack_activation = True
Packit 6f3914
            demands.available_repos = True
Packit 6f3914
            demands.resolving = True
Packit 6f3914
            demands.root_user = True
Packit 6f3914
Packit 6f3914
        def run_on_repo(self):
Packit 6f3914
            """Execute the command with respect to given arguments *cli_args*."""
Packit 6f3914
            _checkGPGKey(self.base, self.cli)
Packit 6f3914
Packit 6f3914
            done = False
Packit 6f3914
Packit 6f3914
            if not self.opts.pkg_specs:
Packit 6f3914
                # Reinstall all packages.
Packit 6f3914
                try:
Packit 6f3914
                    self.base.reinstall('*', old_reponame=self.reponame,
Packit 6f3914
                                        new_reponame_neq=self.reponame,
Packit 6f3914
                                        remove_na=True)
Packit 6f3914
                except dnf.exceptions.PackagesNotInstalledError:
Packit 6f3914
                    msg = _('No package installed from the repository.')
Packit 6f3914
                    logger.info(msg)
Packit 6f3914
                except dnf.exceptions.MarkingError:
Packit 6f3914
                    assert False, 'Only the above marking error is expected.'
Packit 6f3914
                else:
Packit 6f3914
                    done = True
Packit 6f3914
            else:
Packit 6f3914
                # Reinstall packages.
Packit 6f3914
                for pkg_spec in self.opts.pkg_specs:
Packit 6f3914
                    try:
Packit 6f3914
                        self.base.reinstall(
Packit 6f3914
                            pkg_spec, old_reponame=self.reponame,
Packit 6f3914
                            new_reponame_neq=self.reponame, remove_na=True)
Packit 6f3914
                    except dnf.exceptions.PackagesNotInstalledError:
Packit 6f3914
                        msg = _('No match for argument: %s')
Packit 6f3914
                        logger.info(msg, pkg_spec)
Packit 6f3914
                    except dnf.exceptions.MarkingError:
Packit 6f3914
                        assert False, 'Only the above marking error is expected.'
Packit 6f3914
                    else:
Packit 6f3914
                        done = True
Packit 6f3914
Packit 6f3914
            if not done:
Packit 6f3914
                raise dnf.exceptions.Error(_('Nothing to do.'))
Packit 6f3914
Packit 6f3914
    class RemoveSubCommand(Command):
Packit 6f3914
        """Implementation of the remove sub-command."""
Packit 6f3914
Packit 6f3914
        aliases = ('remove',)
Packit 6f3914
Packit 6f3914
        def configure(self):
Packit 6f3914
            demands = self.cli.demands
Packit 6f3914
            demands.sack_activation = True
Packit 6f3914
            demands.allow_erasing = True
Packit 6f3914
            demands.available_repos = False
Packit 6f3914
            demands.resolving = True
Packit 6f3914
            demands.root_user = True
Packit 6f3914
Packit 6f3914
        def run_on_repo(self):
Packit 6f3914
            """Execute the command with respect to given arguments *cli_args*."""
Packit 6f3914
Packit 6f3914
            done = False
Packit 6f3914
Packit 6f3914
            if not self.opts.pkg_specs:
Packit 6f3914
                # Remove all packages.
Packit 6f3914
                try:
Packit 6f3914
                    self.base.remove('*', self.reponame)
Packit 6f3914
                except dnf.exceptions.MarkingError:
Packit 6f3914
                    msg = _('No package installed from the repository.')
Packit 6f3914
                    logger.info(msg)
Packit 6f3914
                else:
Packit 6f3914
                    done = True
Packit 6f3914
            else:
Packit 6f3914
                # Remove packages.
Packit 6f3914
                for pkg_spec in self.opts.pkg_specs:
Packit 6f3914
                    try:
Packit 6f3914
                        self.base.remove(pkg_spec, self.reponame)
Packit 6f3914
                    except dnf.exceptions.MarkingError as e:
Packit 6f3914
                        logger.info(str(e))
Packit 6f3914
                    else:
Packit 6f3914
                        done = True
Packit 6f3914
Packit 6f3914
            if not done:
Packit 6f3914
                logger.warning(_('No packages marked for removal.'))
Packit 6f3914
Packit 6f3914
    class UpgradeSubCommand(Command):
Packit 6f3914
        """Implementation of the upgrade sub-command."""
Packit 6f3914
Packit 6f3914
        aliases = ('upgrade', 'upgrade-to')
Packit 6f3914
Packit 6f3914
        def configure(self):
Packit 6f3914
            demands = self.cli.demands
Packit 6f3914
            demands.sack_activation = True
Packit 6f3914
            demands.available_repos = True
Packit 6f3914
            demands.resolving = True
Packit 6f3914
            demands.root_user = True
Packit 6f3914
Packit 6f3914
        def run_on_repo(self):
Packit 6f3914
            """Execute the command with respect to given arguments *cli_args*."""
Packit 6f3914
            _checkGPGKey(self.base, self.cli)
Packit 6f3914
Packit 6f3914
            done = False
Packit 6f3914
Packit 6f3914
            if not self.opts.pkg_specs:
Packit 6f3914
                # Update all packages.
Packit 6f3914
                self.base.upgrade_all(self.reponame)
Packit 6f3914
                done = True
Packit 6f3914
            else:
Packit 6f3914
                # Update packages.
Packit 6f3914
                for pkg_spec in self.opts.pkg_specs:
Packit 6f3914
                    try:
Packit 6f3914
                        self.base.upgrade(pkg_spec, self.reponame)
Packit 6f3914
                    except dnf.exceptions.MarkingError:
Packit 6f3914
                        logger.info(_('No match for argument: %s'), pkg_spec)
Packit 6f3914
                    else:
Packit 6f3914
                        done = True
Packit 6f3914
Packit 6f3914
            if not done:
Packit 6f3914
                raise dnf.exceptions.Error(_('No packages marked for upgrade.'))
Packit 6f3914
Packit 6f3914
    SUBCMDS = {CheckUpdateSubCommand, InfoSubCommand, InstallSubCommand,
Packit 6f3914
               ListSubCommand, MoveToSubCommand, ReinstallOldSubCommand,
Packit 6f3914
               ReinstallSubCommand, RemoveOrDistroSyncSubCommand,
Packit 6f3914
               RemoveOrReinstallSubCommand, RemoveSubCommand,
Packit 6f3914
               UpgradeSubCommand}
Packit 6f3914
Packit 6f3914
    aliases = ('repository-packages',
Packit 6f3914
               'repo-pkgs', 'repo-packages', 'repository-pkgs')
Packit 6f3914
    summary = _('run commands on top of all packages in given repository')
Packit 6f3914
Packit 6f3914
    def __init__(self, cli):
Packit 6f3914
        """Initialize the command."""
Packit 6f3914
        super(RepoPkgsCommand, self).__init__(cli)
Packit 6f3914
        subcmd_objs = (subcmd(cli) for subcmd in self.SUBCMDS)
Packit 6f3914
        self.subcmd = None
Packit 6f3914
        self._subcmd_name2obj = {
Packit 6f3914
            alias: subcmd for subcmd in subcmd_objs for alias in subcmd.aliases}
Packit 6f3914
Packit 6f3914
    def set_argparser(self, parser):
Packit 6f3914
        narrows = parser.add_mutually_exclusive_group()
Packit 6f3914
        narrows.add_argument('--all', dest='_pkg_specs_action',
Packit 6f3914
                             action='store_const', const='all', default=None,
Packit 6f3914
                             help=_("show all packages (default)"))
Packit 6f3914
        narrows.add_argument('--available', dest='_pkg_specs_action',
Packit 6f3914
                             action='store_const', const='available',
Packit 6f3914
                             help=_("show only available packages"))
Packit 6f3914
        narrows.add_argument('--installed', dest='_pkg_specs_action',
Packit 6f3914
                             action='store_const', const='installed',
Packit 6f3914
                             help=_("show only installed packages"))
Packit 6f3914
        narrows.add_argument('--extras', dest='_pkg_specs_action',
Packit 6f3914
                             action='store_const', const='extras',
Packit 6f3914
                             help=_("show only extras packages"))
Packit 6f3914
        narrows.add_argument('--updates', dest='_pkg_specs_action',
Packit 6f3914
                             action='store_const', const='upgrades',
Packit 6f3914
                             help=_("show only upgrades packages"))
Packit 6f3914
        narrows.add_argument('--upgrades', dest='_pkg_specs_action',
Packit 6f3914
                             action='store_const', const='upgrades',
Packit 6f3914
                             help=_("show only upgrades packages"))
Packit 6f3914
        narrows.add_argument('--autoremove', dest='_pkg_specs_action',
Packit 6f3914
                             action='store_const', const='autoremove',
Packit 6f3914
                             help=_("show only autoremove packages"))
Packit 6f3914
        narrows.add_argument('--recent', dest='_pkg_specs_action',
Packit 6f3914
                             action='store_const', const='recent',
Packit 6f3914
                             help=_("show only recently changed packages"))
Packit 6f3914
Packit 6f3914
        parser.add_argument(
Packit 6f3914
            'reponame', nargs=1, action=OptionParser._RepoCallbackEnable,
Packit 6f3914
            metavar=_('REPOID'), help=_("Repository ID"))
Packit 6f3914
        subcommand_choices = [subcmd.aliases[0] for subcmd in self.SUBCMDS]
Packit 6f3914
        parser.add_argument('subcmd', nargs=1, metavar="SUBCOMMAND",
Packit 6f3914
                            choices=subcommand_choices, help=", ".join(subcommand_choices))
Packit 6f3914
        DEFAULT_PKGNARROW = 'all'
Packit 6f3914
        pkgnarrows = {DEFAULT_PKGNARROW, 'installed', 'available',
Packit 6f3914
                      'autoremove', 'extras', 'obsoletes', 'recent',
Packit 6f3914
                      'upgrades'}
Packit 6f3914
        parser.add_argument('pkg_specs', nargs='*', metavar=_('PACKAGE'),
Packit 6f3914
                            choices=pkgnarrows, default=DEFAULT_PKGNARROW,
Packit 6f3914
                            action=OptionParser.PkgNarrowCallback,
Packit 6f3914
                            help=_("Package specification"))
Packit 6f3914
Packit 6f3914
    def configure(self):
Packit 6f3914
        """Verify whether the command can run with given arguments."""
Packit 6f3914
        # Check sub-command.
Packit 6f3914
        try:
Packit 6f3914
            self.subcmd = self._subcmd_name2obj[self.opts.subcmd[0]]
Packit 6f3914
        except (dnf.cli.CliError, KeyError) as e:
Packit 6f3914
            self.cli.optparser.print_usage()
Packit 6f3914
            raise dnf.cli.CliError
Packit 6f3914
        self.subcmd.opts = self.opts
Packit 6f3914
        self.subcmd.reponame = self.opts.reponame[0]
Packit 6f3914
        self.subcmd.configure()
Packit 6f3914
Packit 6f3914
    def run(self):
Packit 6f3914
        """Execute the command with respect to given arguments *extcmds*."""
Packit 6f3914
        self.subcmd.run_on_repo()
Packit 6f3914
Packit 6f3914
class HelpCommand(Command):
Packit 6f3914
    """A class containing methods needed by the cli to execute the
Packit 6f3914
    help command.
Packit 6f3914
    """
Packit 6f3914
Packit 6f3914
    aliases = ('help',)
Packit 6f3914
    summary = _('display a helpful usage message')
Packit 6f3914
Packit 6f3914
    @staticmethod
Packit 6f3914
    def set_argparser(parser):
Packit 6f3914
        parser.add_argument('cmd', nargs='?', metavar=_('COMMAND'),
Packit 6f3914
                            help=_("{prog} command to get help for").format(
Packit 6f3914
                                prog=dnf.util.MAIN_PROG_UPPER))
Packit 6f3914
Packit 6f3914
    def run(self):
Packit 6f3914
        if (not self.opts.cmd
Packit 6f3914
                or self.opts.cmd not in self.cli.cli_commands):
Packit 6f3914
            self.cli.optparser.print_help()
Packit 6f3914
        else:
Packit 6f3914
            command = self.cli.cli_commands[self.opts.cmd]
Packit 6f3914
            self.cli.optparser.print_help(command(self))
Packit 6f3914
Packit 6f3914
class HistoryCommand(Command):
Packit 6f3914
    """A class containing methods needed by the cli to execute the
Packit 6f3914
    history command.
Packit 6f3914
    """
Packit 6f3914
Packit 6f3914
    aliases = ('history', 'hist')
Packit 6f3914
    summary = _('display, or use, the transaction history')
Packit 6f3914
Packit 6f3914
    _CMDS = ['list', 'info', 'redo', 'undo', 'rollback', 'userinstalled']
Packit 6f3914
Packit 6f3914
    transaction_ids = set()
Packit 6f3914
    merged_transaction_ids = set()
Packit 6f3914
Packit 6f3914
    @staticmethod
Packit 6f3914
    def set_argparser(parser):
Packit 6f3914
        parser.add_argument('transactions_action', nargs='?', metavar="COMMAND",
Packit 6f3914
                            help="Available commands: {} (default), {}".format(
Packit 6f3914
                                HistoryCommand._CMDS[0],
Packit 6f3914
                                ", ".join(HistoryCommand._CMDS[1:])))
Packit 6f3914
        parser.add_argument('transactions', nargs='*', metavar="TRANSACTION",
Packit 6f3914
                            help="Transaction ID (<number>, 'last' or 'last-<number>' "
Packit 6f3914
                                 "for one transaction, <transaction-id>..<transaction-id> "
Packit 6f3914
                                 "for range)")
Packit 6f3914
Packit 6f3914
    def configure(self):
Packit 6f3914
        if not self.opts.transactions_action:
Packit 6f3914
            # no positional argument given
Packit 6f3914
            self.opts.transactions_action = self._CMDS[0]
Packit 6f3914
        elif self.opts.transactions_action not in self._CMDS:
Packit 6f3914
            # first positional argument is not a command
Packit 6f3914
            self.opts.transactions.insert(0, self.opts.transactions_action)
Packit 6f3914
            self.opts.transactions_action = self._CMDS[0]
Packit 6f3914
Packit 6f3914
        require_one_transaction_id = False
Packit 6f3914
        require_one_transaction_id_msg = _("Found more than one transaction ID.\n"
Packit 6f3914
                                           "'{}' requires one transaction ID or package name."
Packit 6f3914
                                           ).format(self.opts.transactions_action)
Packit 6f3914
        demands = self.cli.demands
Packit 6f3914
        if self.opts.transactions_action in ['redo', 'undo', 'rollback']:
Packit 6f3914
            demands.root_user = True
Packit 6f3914
            require_one_transaction_id = True
Packit 6f3914
            if not self.opts.transactions:
Packit 6f3914
                msg = _('No transaction ID or package name given.')
Packit 6f3914
                logger.critical(msg)
Packit 6f3914
                raise dnf.cli.CliError(msg)
Packit 6f3914
            elif len(self.opts.transactions) > 1:
Packit 6f3914
                logger.critical(require_one_transaction_id_msg)
Packit 6f3914
                raise dnf.cli.CliError(require_one_transaction_id_msg)
Packit 6f3914
            demands.available_repos = True
Packit 6f3914
            _checkGPGKey(self.base, self.cli)
Packit 6f3914
        else:
Packit 6f3914
            demands.fresh_metadata = False
Packit 6f3914
        demands.sack_activation = True
Packit 6f3914
        if not os.access(self.base.history.path, os.R_OK):
Packit 6f3914
            msg = _("You don't have access to the history DB.")
Packit 6f3914
            logger.critical(msg)
Packit 6f3914
            raise dnf.cli.CliError(msg)
Packit 6f3914
        self.transaction_ids = self._args2transaction_ids(self.merged_transaction_ids,
Packit 6f3914
                                                          require_one_transaction_id,
Packit 6f3914
                                                          require_one_transaction_id_msg)
Packit 6f3914
Packit 6f3914
    def get_error_output(self, error):
Packit 6f3914
        """Get suggestions for resolving the given error."""
Packit 6f3914
        if isinstance(error, dnf.exceptions.TransactionCheckError):
Packit 6f3914
            if self.opts.transactions_action == 'undo':
Packit 6f3914
                id_, = self.opts.transactions
Packit 6f3914
                return (_('Cannot undo transaction %s, doing so would result '
Packit 6f3914
                          'in an inconsistent package database.') % id_,)
Packit 6f3914
            elif self.opts.transactions_action == 'rollback':
Packit 6f3914
                id_, = (self.opts.transactions if self.opts.transactions[0] != 'force'
Packit 6f3914
                        else self.opts.transactions[1:])
Packit 6f3914
                return (_('Cannot rollback transaction %s, doing so would '
Packit 6f3914
                          'result in an inconsistent package database.') % id_,)
Packit 6f3914
Packit 6f3914
        return Command.get_error_output(self, error)
Packit 6f3914
Packit 6f3914
    def _hcmd_redo(self, extcmds):
Packit 6f3914
        old = self.base.history_get_transaction(extcmds)
Packit 6f3914
        if old is None:
Packit 6f3914
            return 1, ['Failed history redo']
Packit 6f3914
        tm = dnf.util.normalize_time(old.beg_timestamp)
Packit 6f3914
        print('Repeating transaction %u, from %s' % (old.tid, tm))
Packit 6f3914
        self.output.historyInfoCmdPkgsAltered(old)
Packit 6f3914
Packit 6f3914
        for i in old.packages():
Packit 6f3914
            pkgs = list(self.base.sack.query().filter(nevra=str(i), reponame=i.from_repo))
Packit 6f3914
            if i.action in dnf.transaction.FORWARD_ACTIONS:
Packit 6f3914
                if not pkgs:
Packit 6f3914
                    logger.info(_('No package %s available.'),
Packit 6f3914
                    self.output.term.bold(ucd(str(i))))
Packit 6f3914
                    return 1, ['An operation cannot be redone']
Packit 6f3914
                pkg = pkgs[0]
Packit 6f3914
                self.base.install(str(pkg))
Packit 6f3914
            elif i.action == libdnf.transaction.TransactionItemAction_REMOVE:
Packit 6f3914
                if not pkgs:
Packit 6f3914
                    # package was removed already, we can skip removing it again
Packit 6f3914
                    continue
Packit 6f3914
                pkg = pkgs[0]
Packit 6f3914
                self.base.remove(str(pkg))
Packit 6f3914
Packit 6f3914
        self.base.resolve()
Packit 6f3914
        self.base.do_transaction()
Packit 6f3914
Packit 6f3914
    def _hcmd_undo(self, extcmds):
Packit 6f3914
        try:
Packit 6f3914
            return self.base.history_undo_transaction(extcmds[0])
Packit 6f3914
        except dnf.exceptions.Error as err:
Packit 6f3914
            return 1, [str(err)]
Packit 6f3914
Packit 6f3914
    def _hcmd_rollback(self, extcmds):
Packit 6f3914
        try:
Packit 6f3914
            return self.base.history_rollback_transaction(extcmds[0])
Packit 6f3914
        except dnf.exceptions.Error as err:
Packit 6f3914
            return 1, [str(err)]
Packit 6f3914
Packit 6f3914
    def _hcmd_userinstalled(self):
Packit 6f3914
        """Execute history userinstalled command."""
Packit 6f3914
        pkgs = tuple(self.base.iter_userinstalled())
Packit 6f3914
        return self.output.listPkgs(pkgs, 'Packages installed by user', 'nevra')
Packit 6f3914
Packit 6f3914
    def _args2transaction_ids(self, merged_ids=set(),
Packit 6f3914
                              require_one_trans_id=False, require_one_trans_id_msg=''):
Packit 6f3914
        """Convert commandline arguments to transaction ids"""
Packit 6f3914
Packit 6f3914
        def str2transaction_id(s):
Packit 6f3914
            if s == 'last':
Packit 6f3914
                s = '0'
Packit 6f3914
            elif s.startswith('last-'):
Packit 6f3914
                s = s[4:]
Packit 6f3914
            transaction_id = int(s)
Packit 6f3914
            if transaction_id <= 0:
Packit 6f3914
                transaction_id += self.output.history.last().tid
Packit 6f3914
            return transaction_id
Packit 6f3914
Packit 6f3914
        transaction_ids = set()
Packit 6f3914
        for t in self.opts.transactions:
Packit 6f3914
            if '..' in t:
Packit 6f3914
                try:
Packit 6f3914
                    begin_transaction_id, end_transaction_id = t.split('..', 2)
Packit 6f3914
                except ValueError:
Packit 6f3914
                    logger.critical(
Packit 6f3914
                        _("Invalid transaction ID range definition '{}'.\n"
Packit 6f3914
                          "Use '<transaction-id>..<transaction-id>'."
Packit 6f3914
                          ).format(t))
Packit 6f3914
                    raise dnf.cli.CliError
Packit 6f3914
                cant_convert_msg = _("Can't convert '{}' to transaction ID.\n"
Packit 6f3914
                                     "Use '<number>', 'last', 'last-<number>'.")
Packit 6f3914
                try:
Packit 6f3914
                    begin_transaction_id = str2transaction_id(begin_transaction_id)
Packit 6f3914
                except ValueError:
Packit 6f3914
                    logger.critical(_(cant_convert_msg).format(begin_transaction_id))
Packit 6f3914
                    raise dnf.cli.CliError
Packit 6f3914
                try:
Packit 6f3914
                    end_transaction_id = str2transaction_id(end_transaction_id)
Packit 6f3914
                except ValueError:
Packit 6f3914
                    logger.critical(_(cant_convert_msg).format(end_transaction_id))
Packit 6f3914
                    raise dnf.cli.CliError
Packit 6f3914
                if require_one_trans_id and begin_transaction_id != end_transaction_id:
Packit 6f3914
                        logger.critical(require_one_trans_id_msg)
Packit 6f3914
                        raise dnf.cli.CliError
Packit 6f3914
                if begin_transaction_id > end_transaction_id:
Packit 6f3914
                    begin_transaction_id, end_transaction_id = \
Packit 6f3914
                        end_transaction_id, begin_transaction_id
Packit 6f3914
                merged_ids.add((begin_transaction_id, end_transaction_id))
Packit 6f3914
                transaction_ids.update(range(begin_transaction_id, end_transaction_id + 1))
Packit 6f3914
            else:
Packit 6f3914
                try:
Packit 6f3914
                    transaction_ids.add(str2transaction_id(t))
Packit 6f3914
                except ValueError:
Packit 6f3914
                    # not a transaction id, assume it's package name
Packit 6f3914
                    transact_ids_from_pkgname = self.output.history.search([t])
Packit 6f3914
                    if transact_ids_from_pkgname:
Packit 6f3914
                        transaction_ids.update(transact_ids_from_pkgname)
Packit 6f3914
                    else:
Packit 6f3914
                        msg = _("No transaction which manipulates package '{}' was found."
Packit 6f3914
                                ).format(t)
Packit 6f3914
                        if require_one_trans_id:
Packit 6f3914
                            logger.critical(msg)
Packit 6f3914
                            raise dnf.cli.CliError
Packit 6f3914
                        else:
Packit 6f3914
                            logger.info(msg)
Packit 6f3914
Packit 6f3914
        return sorted(transaction_ids, reverse=True)
Packit 6f3914
Packit 6f3914
    def run(self):
Packit 6f3914
        vcmd = self.opts.transactions_action
Packit 6f3914
Packit 6f3914
        ret = None
Packit 6f3914
        if vcmd == 'list' and (self.transaction_ids or not self.opts.transactions):
Packit 6f3914
            ret = self.output.historyListCmd(self.transaction_ids)
Packit 6f3914
        elif vcmd == 'info' and (self.transaction_ids or not self.opts.transactions):
Packit 6f3914
            ret = self.output.historyInfoCmd(self.transaction_ids, self.opts.transactions,
Packit 6f3914
                                             self.merged_transaction_ids)
Packit 6f3914
        elif vcmd == 'undo':
Packit 6f3914
            ret = self._hcmd_undo(self.transaction_ids)
Packit 6f3914
        elif vcmd == 'redo':
Packit 6f3914
            ret = self._hcmd_redo(self.transaction_ids)
Packit 6f3914
        elif vcmd == 'rollback':
Packit 6f3914
            ret = self._hcmd_rollback(self.transaction_ids)
Packit 6f3914
        elif vcmd == 'userinstalled':
Packit 6f3914
            ret = self._hcmd_userinstalled()
Packit 6f3914
Packit 6f3914
        if ret is None:
Packit 6f3914
            return
Packit 6f3914
        (code, strs) = ret
Packit 6f3914
        if code == 2:
Packit 6f3914
            self.cli.demands.resolving = True
Packit 6f3914
        elif code != 0:
Packit 6f3914
            raise dnf.exceptions.Error(strs[0])