From: Michael Tremer Date: Sat, 21 May 2011 15:19:26 +0000 (+0200) Subject: Some bigger rework of the transaction code and solver. X-Git-Tag: 0.9.3~32 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d4c94aa5ef294d8e9e30e71dfe8f4bd7ed079eb5;p=pakfire.git Some bigger rework of the transaction code and solver. --- diff --git a/pakfire/base.py b/pakfire/base.py index b3de45677..ef4ebbfe1 100644 --- a/pakfire/base.py +++ b/pakfire/base.py @@ -133,7 +133,6 @@ class Pakfire(object): # try to solve it. request = self.solver.create_request() for solvable in repo: - print solvable request.install(solvable) t = self.solver.solve(request) diff --git a/pakfire/builder.py b/pakfire/builder.py index 5904d22ea..0d61104cf 100644 --- a/pakfire/builder.py +++ b/pakfire/builder.py @@ -278,12 +278,15 @@ class Builder(object): if isinstance(req, packages.BinaryPackage): req = req.friendly_name - req = self.solver.create_relation(req) + if "<" in req or ">" in req or "=" in req: + req = self.solver.create_relation(req) request.install(req) # Do the solving. - transaction = self.solver.solve(request) + transaction = self.solver.solve(request, allow_downgrade=True) + + # XXX check for errors # Show the user what is going to be done. transaction.dump(logger=self.log) @@ -292,6 +295,9 @@ class Builder(object): transaction.run() def install_test(self): + # XXX currently disabled + return + pkgs = [] for dir, subdirs, files in os.walk(self.chrootPath("result")): for file in files: diff --git a/pakfire/packages/base.py b/pakfire/packages/base.py index bd90428b9..51dc3b7ab 100644 --- a/pakfire/packages/base.py +++ b/pakfire/packages/base.py @@ -1,6 +1,7 @@ #!/usr/bin/python import logging +import xml.sax.saxutils import util @@ -122,10 +123,20 @@ class Package(object): "maintainer" : self.maintainer, "url" : self.url, "license" : self.license, + "hash1" : self.hash1, + "vendor" : self.vendor, + "build_host" : self.build_host, + "build_time" : self.build_time, + "size" : self.size, + "inst_size" : self.inst_size, } return info + @property + def hash1(self): + return "0"*40 + @property def size(self): """ @@ -136,6 +147,11 @@ class Package(object): """ return 0 + @property + def inst_size(self): + # XXX to be done + return 0 + @property def local(self): """ @@ -267,7 +283,13 @@ class Package(object): @property def build_host(self): - return self.metadata.get("BUILD_HOST") + host = self.metadata.get("BUILD_HOST") + + # XXX Workaround tripple X as hostname. + if host == "X"*3: + host = "" + + return host @property def build_id(self): @@ -291,6 +313,10 @@ class Package(object): def vendor(self): return self.metadata.get("PKG_VENDOR", "") + @property + def pre_requires(self): + return set() # XXX to be done + @property def requires(self): ret = "" @@ -315,6 +341,12 @@ class Package(object): return set(provides) + @property + def conflicts(self): + conflicts = self.metadata.get("PKG_CONFLICTS", "").split() + + return set(conflicts) + @property def obsoletes(self): obsoletes = self.metadata.get("PKG_OBSOLETES", "").split() @@ -322,5 +354,69 @@ class Package(object): return set(obsoletes) def extract(self, path, prefix=None): - raise NotImplementedError, "%s" % type(self) + raise NotImplementedError, "%s" % repr(self) + + def export_xml_string(self): + info = self.info + info["groups"] = " ".join(info["groups"]) + + # Escape everything to conform to XML. + for key, value in info.items(): + if not type(value) in (type(a) for a in ("a", u"a")): + continue + + info[key] = xml.sax.saxutils.escape(value, {'"': """}) + + s = """\ + + %(name)s + %(arch)s + + %(hash1)s + %(summary)s + %(description)s + %(maintainer)s + %(url)s + " + return s diff --git a/pakfire/packages/file.py b/pakfire/packages/file.py index cb70bb45f..f692c3a2c 100644 --- a/pakfire/packages/file.py +++ b/pakfire/packages/file.py @@ -313,9 +313,9 @@ class FilePackage(Package): # Remove triple X placeholder that was used some time. if comp == "X"*3: - comp = None + return None - return comp + return comp or "xz" @property def signature(self): diff --git a/pakfire/packages/installed.py b/pakfire/packages/installed.py index 9d66dec4b..f31c8f213 100644 --- a/pakfire/packages/installed.py +++ b/pakfire/packages/installed.py @@ -176,6 +176,11 @@ class DatabasePackage(Package): Downloads the package from repository and returns a new instance of BinaryPackage. """ + + # XXX a bit hacky, but InstalledRepository has no cache. + if self.repo.name == "installed": + return self + # Marker, if we need to download the package. download = True diff --git a/pakfire/repository/base.py b/pakfire/repository/base.py index d80448cbb..50f06ca0e 100644 --- a/pakfire/repository/base.py +++ b/pakfire/repository/base.py @@ -2,6 +2,8 @@ import fnmatch import glob +import logging +import re class RepositoryFactory(object): def __init__(self, pakfire, name, description): @@ -12,6 +14,9 @@ class RepositoryFactory(object): # All repositories are enabled by default self.enabled = True + # Reference to corresponding Repo object in the solver. + self.solver_repo = None + def __repr__(self): return "<%s %s>" % (self.__class__.__name__, self.name) @@ -59,6 +64,26 @@ class RepositoryFactory(object): if pkg.name == name: yield pkg + def get_by_evr(self, name, evr): + m = re.match(r"([0-9]+\:)?([0-9A-Za-z\.\-]+)-([0-9]+\.?[a-z0-9]+|[0-9]+)", evr) + + if not m: + raise Exception, "Invalid input: %s" % evr + + (epoch, version, release) = m.groups() + if epoch and epoch.endswith(":"): + epoch = epoch[:-1] + + pkgs = [p for p in self.index.get_by_evr(name, epoch, version, release)] + + if not pkgs: + return + + if not len(pkgs) == 1: + raise Exception + + return pkgs[0] + def get_by_glob(self, pattern): """ Returns a list of all packages that names match the glob pattern @@ -142,3 +167,9 @@ class RepositoryFactory(object): return self.index.filelist return {} + + def import_to_solver(self, solver, repo): + if hasattr(self, "index"): + self.solver_repo = repo + + self.index.import_to_solver(solver, repo) diff --git a/pakfire/repository/index.py b/pakfire/repository/index.py index 854e0a3a9..79c412c22 100644 --- a/pakfire/repository/index.py +++ b/pakfire/repository/index.py @@ -6,6 +6,7 @@ import logging import os import random import shutil +import subprocess import time import database @@ -46,6 +47,20 @@ class Index(object): if match: yield pkg + def get_by_evr(self, name, epoch, version, release): + try: + epoch = int(epoch) + except TypeError: + epoch = 0 + + for pkg in self.packages: + if pkg.type == "source": + continue + + if pkg.name == name and pkg.epoch == epoch \ + and pkg.version == version and pkg.release == release: + yield pkg + def get_by_id(self, id): raise NotImplementedError @@ -78,6 +93,52 @@ class Index(object): def add_package(self, pkg): raise NotImplementedError + @property + def cachefile(self): + return None + + def import_to_solver(self, solver, repo): + if self.cachefile: + if not os.path.exists(self.cachefile): + self.create_solver_cache() + + logging.debug("Importing repository cache data from %s" % self.cachefile) + repo.add_solv(self.cachefile) + + else: + for pkg in self.packages: + solver.add_package(pkg, repo.name()) + + logging.debug("Initialized new repo '%s' with %s packages." % \ + (repo.name(), repo.size())) + + def create_solver_cache(self): + cachedir = os.path.dirname(self.cachefile) + if not os.path.exists(cachedir): + os.makedirs(cachedir) + + f = open(self.cachefile, "w") + + # Write metadata header. + xml = "\n" + xml += "\n" + + # We dump an XML string for every package in this repository and + # write it to the XML file. + for pkg in self.packages: + xml += pkg.export_xml_string() + + # Write footer. + xml += "" + + p = subprocess.Popen("rpmmd2solv", stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + stdout, stderr = p.communicate(xml) + + f.write(stdout) + f.close() + class DirectoryIndex(Index): def __init__(self, pakfire, repo, path): @@ -206,6 +267,7 @@ class DatabaseIndexFactory(Index): return files + class InstalledIndex(DatabaseIndexFactory): def open_database(self): # Open the local package database. @@ -380,3 +442,7 @@ class RemoteIndex(DatabaseIndexFactory): # XXX this code needs lots of work: # XXX * check the metadata content + + @property + def cachefile(self): + return "%s.cache" % self.db.filename diff --git a/pakfire/repository/remote.py b/pakfire/repository/remote.py index c2ffcff1a..52c348472 100644 --- a/pakfire/repository/remote.py +++ b/pakfire/repository/remote.py @@ -11,6 +11,8 @@ import pakfire.downloader as downloader from base import RepositoryFactory class RemoteRepository(RepositoryFactory): + cacheable = True + def __init__(self, pakfire, name, description, url, mirrorlist, gpgkey, enabled): RepositoryFactory.__init__(self, pakfire, name, description) @@ -76,9 +78,9 @@ class RemoteRepository(RepositoryFactory): return priority - def update(self, force=False): - if self.index: - self.index.update(force=force) + #def update(self, force=False): + # if self.index: + # self.index.update(force=force) def _replace_from_cache(self, pkg): for _pkg in self.cache.packages: diff --git a/pakfire/repository/solver.py b/pakfire/repository/solver.py index cf9bf610e..07d0f2613 100644 --- a/pakfire/repository/solver.py +++ b/pakfire/repository/solver.py @@ -35,10 +35,18 @@ class Solver(object): self.pool.set_arch(arch) # Initialize all repositories. - self.repos = self.init_repos() + for repo in self.repos.enabled: + self.init_repo(repo) + +# self.init_repos() self.pool.prepare() + logging.debug("Solver pool has %s solvables." % self.pool.size()) + + def create_repo(self, *args, **kwargs): + return self.pool.create_repo(*args, **kwargs) + def create_relation(self, s): s = str(s) @@ -55,17 +63,30 @@ class Solver(object): return satsolver.Relation(self.pool, s) - def init_repos(self): - repos = [] + def init_repo(self, repo): + solvrepo = self.pool.create_repo(repo.name) + if repo.name == "installed": + self.pool.set_installed(solvrepo) - for repo in self.repos.enabled: - solvrepo = self.pool.create_repo(repo.name) - if repo.name == "installed": - self.pool.set_installed(solvrepo) + repo.import_to_solver(self, solvrepo) + return - pb = util.make_progress(_("Loading %s") % repo.name, repo.size) - i = 0 + # XXX dead code + solvrepo = self.pool.create_repo(repo.name) + if repo.name == "installed": + self.pool.set_installed(solvrepo) + + pb = util.make_progress(_("Loading %s") % repo.name, repo.size) + i = 0 + + # Let's see if this repository has a cache and use it if possible. + cachefile = repo.metadata_cachefile + print cachefile + if cachefile and os.path.exists(cachefile): + solvrepo.add_solv(cachefile) + + else: for pkg in repo.get_all(): if pb: i += 1 @@ -73,36 +94,20 @@ class Solver(object): self.add_package(pkg) - logging.debug("Initialized new repo '%s' with %s packages." % \ - (solvrepo.name(), solvrepo.size())) + logging.debug("Initialized new repo '%s' with %s packages." % \ + (solvrepo.name(), solvrepo.size())) - if pb: - pb.finish() + if pb: + pb.finish() - repos.append(solvrepo) + repos.append(solvrepo) - return repos - - def get_repo(self, name): - for repo in self.pool.repos(): - if not repo.name() == name: - continue + def add_package(self, pkg, reponame): + repo = self.repos.get_repo_by_name(reponame) - return repo - - def add_package(self, pkg, repo_name=None): - if not repo_name: - repo_name = pkg.repo.name - - solvrepo = self.get_repo(repo_name) - assert solvrepo - - solvable = satsolver.Solvable(solvrepo, str(pkg.name), + solvable = satsolver.Solvable(repo.solver_repo, str(pkg.name), str(pkg.friendly_version), str(pkg.arch)) - # Store the solver's ID. - self.id2pkg[solvable.id()] = pkg - # Set vendor. solvable.set_vendor(pkg.vendor) @@ -136,10 +141,12 @@ class Solver(object): def create_request(self): return self.pool.create_request() - def solve(self, request, update=False, interactive=False): + def solve(self, request, update=False, allow_downgrade=False, interactive=False): solver = self.pool.create_solver() #solver.set_allow_uninstall(True) + solver.set_allow_downgrade(allow_downgrade) + # Configure the solver for an update. if update: solver.set_update_system(True) @@ -164,10 +171,6 @@ class Solver(object): # Solver had an error and we now see what we can do: logging.info("The solver returned %s problems." % solver.problems_count()) - # XXX everything after this line is totally broken and does not do its - # job correctly. - return - jobactions = { satsolver.INSTALL_SOLVABLE : "install", satsolver.UPDATE_SOLVABLE : "update", @@ -194,6 +197,8 @@ class Solver(object): if not interactive: continue + continue # XXX + logging.info(" - %s -" % _("Empty to abort.")) while True: @@ -225,17 +230,12 @@ class Solver(object): raise Exception, "Unknown action called." break - def solvables2packages(self, solvables): - pkgs = [] + def solv2pkg(self, solv): + repo = self.repos.get_repo_by_name(solv.repo().name()) - for solv in solvables: - pkg = self.id2pkg[solv.id()] - pkgs.append(pkg) - - return pkgs + return repo.get_by_evr(solv.name(), solv.evr()) def get_by_provides(self, provides): - print provides provides = self.create_relation(provides) pkgs = self.solvables2packages(self.pool.providers(provides)) diff --git a/pakfire/repository/transaction.py b/pakfire/repository/transaction.py index 01600f17e..dfc7f7a0a 100644 --- a/pakfire/repository/transaction.py +++ b/pakfire/repository/transaction.py @@ -38,6 +38,17 @@ class Action(object): logging.debug("Removing dep %s from %s" % (dep, self)) self.deps.remove(dep) + @property + def needs_download(self): + return self.type in ("install", "reinstall", "update", "downgrade",) \ + and not isinstance(self.pkg, packages.BinaryPackage) + + def download(self, text): + if not self.needs_download: + return + + self.pkg = self.pkg.download(text) + def run(self): raise NotImplementedError @@ -92,7 +103,18 @@ class ActionInstall(Action): self.local.index.add_package(self.pkg) def run(self): - self.extract(_("Installing: %s") % self.pkg.name) + msg = _("Extracting: %s") + + if self.type == "install": + msg = _("Installing: %s") + elif self.type == "reinstall": + msg = _("Reinstalling: %s") + elif self.type == "update": + msg = _("Updating: %s") + elif self.type == "downgrade": + msg = _("Downgrading: %s") + + self.extract(msg % self.pkg.name) self.pakfire.solver.add_package(self.pkg, "installed") @@ -100,10 +122,6 @@ class ActionInstall(Action): class ActionUpdate(ActionInstall): type = "upgrade" - def run(self): - self.extract(_("Updating: %s") % self.pkg.name) - - class ActionRemove(ActionCleanup): type = "erase" @@ -116,24 +134,28 @@ class ActionRemove(ActionCleanup): self.remove_files(_("Removing: %s") % self.pkg.name, files) +class ActionReinstall(ActionInstall): + type = "reinstall" + + +class ActionDowngrade(ActionInstall): + type = "downgrade" + + class Transaction(object): action_classes = [ ActionInstall, ActionUpdate, ActionRemove, ActionCleanup, + ActionReinstall, + ActionDowngrade, ] def __init__(self, pakfire): self.pakfire = pakfire self.actions = [] - self.downloads = [] - - self.installs = [] - self.updates = [] - self.removes = [] - @classmethod def from_solver(cls, pakfire, solver1, solver2): # Grab the original transaction object from the solver. @@ -146,17 +168,12 @@ class Transaction(object): # Create a new instance of our own transaction class. transaction = cls(pakfire) - # Copy all information. - transaction.installs = solver1.solvables2packages(solver2.installs()) - transaction.updates = \ - [p for p in solver1.solvables2packages(solver2.updates()) if not p.repo.name == "installed"] - transaction.removes = solver1.solvables2packages(solver2.removes()) - for step in _transaction.steps(): action = step.type_s(satsolver.TRANSACTION_MODE_ACTIVE) - pkg = solver1.id2pkg[step.solvable().id()] + pkg = solver1.solv2pkg(step.solvable()) - if action in ("install", "upgrade") and not isinstance(pkg, packages.BinaryPackage): + if action in ("install", "reinstall", "upgrade") and \ + not isinstance(pkg, packages.BinaryPackage): transaction.downloads.append(pkg) for action_cls in cls.action_classes: @@ -168,28 +185,41 @@ class Transaction(object): transaction.add_action(action) + print transaction.actions return transaction - def download(self): - if not self.downloads: - return + @property + def installs(self): + return [a.pkg for a in self.actions if isinstance(a, ActionInstall)] - i = 0 - for pkg in self.downloads: - i += 1 + @property + def reinstalls(self): + return [a.pkg for a in self.actions if isinstance(a, ActionReinstall)] - # Actually download the package. - pkg_bin = pkg.download(text="(%2d/%02d): " % (i, len(self.downloads))) + @property + def removes(self): + return [a.pkg for a in self.actions if isinstance(a, ActionRemove)] - # Replace the package in all actions where it matches. - actions = [a for a in self.actions if a.pkg == pkg] + @property + def updates(self): + return [a.pkg for a in self.actions if isinstance(a, ActionUpdate)] - for action in actions: - action.pkg = pkg_bin + @property + def downgrades(self): + return [a.pkg for a in self.actions if isinstance(a, ActionDowngrade)] - # Reset packages to be downloaded. - self.downloads = [] - print + @property + def downloads(self): + return [a for a in self.actions if a.needs_download] + + def download(self): + downloads = self.downloads + + i = 0 + for action in self.actions: + i += 1 + + action.download(text="(%02d/%02d): " % (i, len(downloads))) def dump_pkg(self, pkg): ret = [] @@ -221,32 +251,35 @@ class Transaction(object): width = 80 line = "=" * width - s = [] + s = [""] s.append(line) s.append(PKG_DUMP_FORMAT % (_("Package"), _("Arch"), _("Version"), _("Repository"), _("Size"))) s.append(line) - s += self.dump_pkgs(_("Installing:"), self.installs) - s += self.dump_pkgs(_("Updating:"), self.updates) - s += self.dump_pkgs(_("Removing:"), self.removes) + actions = ( + (_("Installing:"), self.installs), + (_("Reinstalling:"), self.reinstalls), + (_("Updating:"), self.updates), + (_("Downgrading:"), self.downgrades), + (_("Removing:"), self.removes), + ) + + for caption, pkgs in actions: + s += self.dump_pkgs(caption, pkgs) s.append(_("Transaction Summary")) s.append(line) format = "%-20s %-4d %s" - if self.installs: - s.append(format % (_("Install"), len(self.installs), _("Package(s)"))) - - if self.updates: - s.append(format % (_("Updates"), len(self.updates), _("Package(s)"))) - - if self.removes: - s.append(format % (_("Remove"), len(self.removes), _("Package(s)"))) + for caption, pkgs in actions: + if not len(pkgs): + continue + s.append(format % (caption, len(pkgs), _("package", "packages", len(pkgs)))) # Calculate the size of all files that need to be downloaded this this # transaction. - download_size = sum([p.size for p in self.downloads]) + download_size = sum([a.pkg.size for a in self.downloads]) if download_size: s.append(_("Total download size: %s") % util.format_size(download_size)) s.append("") diff --git a/po/pakfire.pot b/po/pakfire.pot index 846d4d484..565fe514e 100644 --- a/po/pakfire.pot +++ b/po/pakfire.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-05-19 12:48+0200\n" +"POT-Creation-Date: 2011-05-21 17:16+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -22,158 +22,168 @@ msgstr "" msgid "%s [y/N]" msgstr "" -#: ../pakfire/packages/base.py:67 +#: ../pakfire/packages/base.py:68 msgid "Name" msgstr "" -#: ../pakfire/packages/base.py:68 ../pakfire/repository/transaction.py:226 +#: ../pakfire/packages/base.py:69 ../pakfire/repository/transaction.py:256 msgid "Arch" msgstr "" -#: ../pakfire/packages/base.py:69 ../pakfire/repository/transaction.py:226 +#: ../pakfire/packages/base.py:70 ../pakfire/repository/transaction.py:256 msgid "Version" msgstr "" -#: ../pakfire/packages/base.py:70 +#: ../pakfire/packages/base.py:71 msgid "Release" msgstr "" -#: ../pakfire/packages/base.py:71 ../pakfire/repository/transaction.py:226 +#: ../pakfire/packages/base.py:72 ../pakfire/repository/transaction.py:256 msgid "Size" msgstr "" -#: ../pakfire/packages/base.py:72 +#: ../pakfire/packages/base.py:73 msgid "Repo" msgstr "" -#: ../pakfire/packages/base.py:73 +#: ../pakfire/packages/base.py:74 msgid "Summary" msgstr "" -#: ../pakfire/packages/base.py:74 +#: ../pakfire/packages/base.py:75 msgid "Groups" msgstr "" -#: ../pakfire/packages/base.py:75 +#: ../pakfire/packages/base.py:76 msgid "URL" msgstr "" -#: ../pakfire/packages/base.py:76 +#: ../pakfire/packages/base.py:77 msgid "License" msgstr "" -#: ../pakfire/packages/base.py:79 +#: ../pakfire/packages/base.py:80 msgid "Description" msgstr "" -#: ../pakfire/packages/base.py:85 +#: ../pakfire/packages/base.py:86 msgid "UUID" msgstr "" -#: ../pakfire/packages/base.py:86 +#: ../pakfire/packages/base.py:87 msgid "Build ID" msgstr "" -#: ../pakfire/packages/base.py:87 +#: ../pakfire/packages/base.py:88 msgid "Build date" msgstr "" -#: ../pakfire/packages/base.py:88 +#: ../pakfire/packages/base.py:89 msgid "Build host" msgstr "" -#: ../pakfire/packages/base.py:90 +#: ../pakfire/packages/base.py:91 msgid "Provides" msgstr "" -#: ../pakfire/packages/base.py:95 +#: ../pakfire/packages/base.py:96 msgid "Requires" msgstr "" -#: ../pakfire/repository/solver.py:66 +#: ../pakfire/repository/solver.py:80 #, python-format msgid "Loading %s" msgstr "" -#: ../pakfire/repository/solver.py:197 +#. XXX +#: ../pakfire/repository/solver.py:202 msgid "Empty to abort." msgstr "" -#: ../pakfire/repository/solver.py:200 +#: ../pakfire/repository/solver.py:205 msgid "Choose a solution:" msgstr "" -#: ../pakfire/repository/solver.py:216 +#: ../pakfire/repository/solver.py:221 msgid "You have entered an invalid solution. Try again." msgstr "" -#: ../pakfire/repository/transaction.py:95 +#: ../pakfire/repository/transaction.py:106 +#, python-format +msgid "Extracting: %s" +msgstr "" + +#: ../pakfire/repository/transaction.py:109 #, python-format msgid "Installing: %s" msgstr "" -#: ../pakfire/repository/transaction.py:104 +#: ../pakfire/repository/transaction.py:111 +#, python-format +msgid "Reinstalling: %s" +msgstr "" + +#: ../pakfire/repository/transaction.py:113 #, python-format msgid "Updating: %s" msgstr "" -#: ../pakfire/repository/transaction.py:116 +#: ../pakfire/repository/transaction.py:115 +#, python-format +msgid "Downgrading: %s" +msgstr "" + +#: ../pakfire/repository/transaction.py:134 #, python-format msgid "Removing: %s" msgstr "" -#: ../pakfire/repository/transaction.py:226 +#: ../pakfire/repository/transaction.py:256 msgid "Package" msgstr "" -#: ../pakfire/repository/transaction.py:226 ../pakfire/cli.py:223 +#: ../pakfire/repository/transaction.py:256 ../pakfire/cli.py:223 msgid "Repository" msgstr "" -#: ../pakfire/repository/transaction.py:229 +#: ../pakfire/repository/transaction.py:260 msgid "Installing:" msgstr "" -#: ../pakfire/repository/transaction.py:230 -msgid "Updating:" -msgstr "" - -#: ../pakfire/repository/transaction.py:231 -msgid "Removing:" +#: ../pakfire/repository/transaction.py:261 +msgid "Reinstalling:" msgstr "" -#: ../pakfire/repository/transaction.py:233 -msgid "Transaction Summary" +#: ../pakfire/repository/transaction.py:262 +msgid "Updating:" msgstr "" -#: ../pakfire/repository/transaction.py:239 -msgid "Install" +#: ../pakfire/repository/transaction.py:263 +msgid "Downgrading:" msgstr "" -#: ../pakfire/repository/transaction.py:239 -#: ../pakfire/repository/transaction.py:242 -#: ../pakfire/repository/transaction.py:245 -msgid "Package(s)" +#: ../pakfire/repository/transaction.py:264 +msgid "Removing:" msgstr "" -#: ../pakfire/repository/transaction.py:242 -msgid "Updates" +#: ../pakfire/repository/transaction.py:270 +msgid "Transaction Summary" msgstr "" -#: ../pakfire/repository/transaction.py:245 -msgid "Remove" +#: ../pakfire/repository/transaction.py:278 +msgid "package" msgstr "" -#: ../pakfire/repository/transaction.py:251 +#: ../pakfire/repository/transaction.py:284 #, python-format msgid "Total download size: %s" msgstr "" -#: ../pakfire/repository/transaction.py:260 +#: ../pakfire/repository/transaction.py:293 msgid "Is this okay?" msgstr "" -#: ../pakfire/repository/index.py:335 +#: ../pakfire/repository/index.py:397 #, python-format msgid "%s: package database" msgstr ""