Blob Blame History Raw
diff --git a/cli.py b/cli.py
index 8f50541..f93a706 100644
--- a/cli.py
+++ b/cli.py
@@ -894,10 +894,12 @@ class YumBaseCli(yum.YumBase, output.YumOutput):
             hdrcode, hdrresults = self.cleanHeaders()
             xmlcode, xmlresults = self.cleanMetadata()
             dbcode, dbresults = self.cleanSqlite()
+            rpmcode, rpmresults = self.cleanRpmDB()
             self.plugins.run('clean')
             
-            code = hdrcode + pkgcode + xmlcode + dbcode
-            results = hdrresults + pkgresults + xmlresults + dbresults
+            code = hdrcode + pkgcode + xmlcode + dbcode + rpmdb
+            results = (hdrresults + pkgresults + xmlresults + dbresults +
+                       rpmresults)
             for msg in results:
                 self.logger.debug(msg)
             return code, []
@@ -917,6 +919,9 @@ class YumBaseCli(yum.YumBase, output.YumOutput):
         if 'expire-cache' in userlist or 'metadata' in userlist:
             self.logger.debug(_('Cleaning up expire-cache metadata'))
             expccode, expcresults = self.cleanExpireCache()
+        if 'rpmdb' in userlist:
+            self.logger.debug(_('Cleaning up cached rpmdb data'))
+            expccode, expcresults = self.cleanRpmDB()
         if 'plugins' in userlist:
             self.logger.debug(_('Cleaning up plugins'))
             self.plugins.run('clean')
diff --git a/docs/yum.8 b/docs/yum.8
index 4158453..9472a82 100644
--- a/docs/yum.8
+++ b/docs/yum.8
@@ -35,7 +35,7 @@ gnome\-packagekit application\&.
 .br 
 .I \fR * provides  | whatprovides feature1 [feature2] [\&.\&.\&.]
 .br  
-.I \fR * clean [ packages | headers | metadata | dbcache | all ]
+.I \fR * clean [ packages | metadata | expire-cache | rpmdb | plugins | all ]
 .br
 .I \fR * makecache
 .br
@@ -424,7 +424,8 @@ Eliminate the local data saying when the metadata and mirrorlists were downloade
 Eliminate any cached packages from the system.  Note that packages are not automatically deleted after they are downloaded.
 
 .IP "\fByum clean headers\fP"
-Eliminate all of the header files which yum uses for dependency resolution.
+Eliminate all of the header files, which old versions of yum used for
+dependency resolution.
 
 .IP "\fByum clean metadata\fP"
 Eliminate all of the files which yum uses to determine the remote
@@ -433,11 +434,22 @@ metadata the next time it is run.
 
 .IP "\fByum clean dbcache\fP"
 Eliminate the sqlite cache used for faster access to metadata.
-Using this option will force yum to recreate the cache the next time
-it is run.
+Using this option will force yum to download the sqlite metadata the next time
+it is run, or recreate the sqlite metadata if using an older repo.
+
+.IP "\fByum clean dbcache\fP"
+Eliminate the sqlite cache used for faster access to metadata.
+Using this option will force yum to download the sqlite metadata the next time
+it is run, or recreate the sqlite metadata if using an older repo.
+
+.IP "\fByum clean rpmdb\fP"
+Eliminate any cached data from the local rpmdb.
+
+.IP "\fByum clean plugins\fP"
+Tell any enabled plugins to eliminate their cached data.
 
 .IP "\fByum clean all\fP"
-Runs \fByum clean packages\fP and \fByum clean headers\fP, \fByum clean metadata\fP and \fByum clean dbcache\fP as above.
+Does all of the above.
 
 .PP
 .SH "MISC"
diff --git a/test/rpmdb-cache.py b/test/rpmdb-cache.py
new file mode 100755
index 0000000..7768a93
--- /dev/null
+++ b/test/rpmdb-cache.py
@@ -0,0 +1,87 @@
+#! /usr/bin/python -tt
+
+import sys
+import yum
+
+__provides_of_requires_exact__ = False
+
+yb1 = yum.YumBase()
+yb1.conf.cache = True
+yb2 = yum.YumBase()
+yb2.conf.cache = True
+
+if len(sys.argv) > 1 and sys.argv[1].lower() == 'full':
+    print "Doing full test"
+    __provides_of_requires_exact__ = True
+
+assert hasattr(yb1.rpmdb, '__cache_rpmdb__')
+yb1.rpmdb.__cache_rpmdb__ = False
+yb2.setCacheDir()
+
+# Version
+ver1 = yb1.rpmdb.simpleVersion(main_only=True)[0]
+ver2 = yb2.rpmdb.simpleVersion(main_only=True)[0]
+if ver1 != ver2:
+    print >>sys.stderr, "Error: Version mismatch:", ver1, ver2
+
+# Conflicts
+cpkgs1 = yb1.rpmdb.returnConflictPackages()
+cpkgs2 = yb2.rpmdb.returnConflictPackages()
+if len(cpkgs1) != len(cpkgs2):
+    print >>sys.stderr, "Error: Conflict len mismatch:", len(cpkgs1),len(cpkgs2)
+for pkg in cpkgs1:
+    if pkg not in cpkgs2:
+        print >>sys.stderr, "Error: Conflict cache missing", pkg
+for pkg in cpkgs2:
+    if pkg not in cpkgs1:
+        print >>sys.stderr, "Error: Conflict cache extra", pkg
+
+# File Requires
+frd1, blah, fpd1 = yb1.rpmdb.fileRequiresData()
+frd2, blah, fpd2 = yb2.rpmdb.fileRequiresData()
+if len(frd1) != len(frd2):
+    print >>sys.stderr, "Error: FileReq len mismatch:", len(frd1), len(frd2)
+for pkgtup in frd1:
+    if pkgtup not in frd2:
+        print >>sys.stderr, "Error: FileReq cache missing", pkgtup
+        continue
+    if len(set(frd1[pkgtup])) != len(set(frd2[pkgtup])):
+        print >>sys.stderr, ("Error: FileReq[%s] len mismatch:" % (pkgtup,),
+                             len(frd1[pkgtup]), len(frd2[pkgtup]))
+    for name in frd1[pkgtup]:
+        if name not in frd2[pkgtup]:
+            print >>sys.stderr, ("Error: FileReq[%s] cache missing" % (pkgtup,),
+                                 name)
+for pkgtup in frd2:
+    if pkgtup not in frd1:
+        print >>sys.stderr, "Error: FileReq cache extra", pkgtup
+        continue
+    for name in frd2[pkgtup]:
+        if name not in frd1[pkgtup]:
+            print >>sys.stderr, ("Error: FileReq[%s] cache extra" % (pkgtup,),
+                                 name)
+
+# File Provides (of requires) -- not exact
+if len(fpd1) != len(fpd2):
+    print >>sys.stderr, "Error: FileProv len mismatch:", len(fpd1), len(fpd2)
+for name in fpd1:
+    if name not in fpd2:
+        print >>sys.stderr, "Error: FileProv cache missing", name
+        continue
+
+    if not __provides_of_requires_exact__:
+        continue # We might be missing some providers
+
+    if len(fpd1[name]) != len(fpd2[name]):
+        print >>sys.stderr, ("Error: FileProv[%s] len mismatch:" % (pkgtup,),
+                             len(fpd1[name]), len(fpd2[name]))
+    for pkgtup in fpd1[name]:
+        if pkgtup not in fpd2[name]:
+            print >>sys.stderr,"Error: FileProv[%s] cache missing" % name,pkgtup
+for name in fpd2:
+    if name not in fpd1:
+        print >>sys.stderr, "Error: FileProv cache extra", name
+        continue
+    for pkgtup in fpd2[name]:
+        if pkgtup not in fpd1[name]:
+            print >>sys.stderr,"Error: FileProv[%s] cache extra" % name,pkgtup
diff --git a/test/testbase.py b/test/testbase.py
index 3edfe57..c7a1ffe 100644
--- a/test/testbase.py
+++ b/test/testbase.py
@@ -14,6 +14,7 @@ from yum import packages
 from yum import packageSack
 from yum.constants import TS_INSTALL_STATES, TS_REMOVE_STATES
 from cli import YumBaseCli
+from yum.rpmsack import RPMDBPackageSack as _rpmdbsack
 import inspect
 from rpmUtils import arch
 
@@ -233,6 +234,50 @@ class FakeRpmDb(packageSack.PackageSack):
     def __init__(self):
         packageSack.PackageSack.__init__(self)
 
+    # Need to mock out rpmdb caching... copy&paste. Gack.
+    def returnConflictPackages(self):
+        ret = []
+        for pkg in self.returnPackages():
+            if len(pkg.conflicts):
+                ret.append(pkg)
+        return ret
+    def fileRequiresData(self):
+        installedFileRequires = {}
+        installedUnresolvedFileRequires = set()
+        resolved = set()
+        for pkg in self.returnPackages():
+            for name, flag, evr in pkg.requires:
+                if not name.startswith('/'):
+                    continue
+                installedFileRequires.setdefault(pkg.pkgtup, []).append(name)
+                if name not in resolved:
+                    dep = self.getProvides(name, flag, evr)
+                    resolved.add(name)
+                    if not dep:
+                        installedUnresolvedFileRequires.add(name)
+
+        fileRequires = set()
+        for fnames in installedFileRequires.itervalues():
+            fileRequires.update(fnames)
+        installedFileProviders = {}
+        for fname in fileRequires:
+            pkgtups = [pkg.pkgtup for pkg in self.getProvides(fname)]
+            installedFileProviders[fname] = pkgtups
+
+        ret =  (installedFileRequires, installedUnresolvedFileRequires,
+                installedFileProviders)
+
+        return ret
+    def transactionCacheFileRequires(self, installedFileRequires,
+                                     installedUnresolvedFileRequires,
+                                     installedFileProvides,
+                                     problems):
+        return
+    def transactionCacheConflictPackages(self, pkgs):
+        return
+    def transactionResultVersion(self, rpmdbv):
+        return
+
     def getProvides(self, name, flags=None, version=(None, None, None)):
         """return dict { packages -> list of matching provides }"""
         self._checkIndexes(failure='build')
diff --git a/yum/__init__.py b/yum/__init__.py
index 2aaa66a..affa92b 100644
--- a/yum/__init__.py
+++ b/yum/__init__.py
@@ -469,7 +469,8 @@ class YumBase(depsolve.Depsolve):
             self.verbose_logger.log(logginglevels.DEBUG_4,
                                     _('Reading Local RPMDB'))
             self._rpmdb = rpmsack.RPMDBPackageSack(root=self.conf.installroot,
-                                                   releasever=self.yumvar['releasever'])
+                                                   releasever=self.yumvar['releasever'],
+                                                   cachedir=self.conf.cachedir)
             self.verbose_logger.debug('rpmdb time: %0.3f' % (time.time() - rpmdb_st))
         return self._rpmdb
 
@@ -1072,6 +1073,13 @@ class YumBase(depsolve.Depsolve):
         if self.conf.history_record:
             self.history.beg(rpmdbv, using_pkgs, list(self.tsInfo))
 
+        #  Just before we update the transaction, update what we think the
+        # rpmdb will look like. This needs to be done before the run, so that if
+        # "something" happens and the rpmdb is different from what we think it
+        # will be we store what we thought, not what happened (so it'll be an
+        # invalid cache).
+        self.rpmdb.transactionResultVersion(self.tsInfo.futureRpmDBVersion())
+
         errors = self.ts.run(cb.callback, '')
         # ts.run() exit codes are, hmm, "creative": None means all ok, empty 
         # list means some errors happened in the transaction and non-empty 
@@ -1699,6 +1707,11 @@ class YumBase(depsolve.Depsolve):
         exts = ['cachecookie', 'mirrorlist.txt']
         return self._cleanFiles(exts, 'cachedir', 'metadata')
 
+    def cleanRpmDB(self):
+        cachedir = self.conf.cachedir + "/rpmdb-cache/"
+        filelist = misc.getFileList(cachedir, '', [])
+        return self._cleanFilelist('rpmdb', filelist)
+
     def _cleanFiles(self, exts, pathattr, filetype):
         filelist = []
         removed = 0
@@ -1707,7 +1720,9 @@ class YumBase(depsolve.Depsolve):
                 path = getattr(repo, pathattr)
                 if os.path.exists(path) and os.path.isdir(path):
                     filelist = misc.getFileList(path, ext, filelist)
+        self._cleanFilelist(filetype, filelist)
 
+    def _cleanFilelist(self, filetype, filelist):
         for item in filelist:
             try:
                 misc.unlink_f(item)
@@ -4163,7 +4178,9 @@ class YumBase(depsolve.Depsolve):
         if cachedir is None:
             return False # Tried, but failed, to get a "user" cachedir
 
-        self.repos.setCacheDir(cachedir + varReplace(suffix, self.yumvar))
+        cachedir += varReplace(suffix, self.yumvar)
+        self.repos.setCacheDir(cachedir)
+        self.rpmdb.setCacheDir(cachedir)
 
         return True # We got a new cache dir
 
diff --git a/yum/depsolve.py b/yum/depsolve.py
index 7871e98..b5953c2 100644
--- a/yum/depsolve.py
+++ b/yum/depsolve.py
@@ -918,31 +918,27 @@ class Depsolve(object):
 
     def _checkFileRequires(self):
         fileRequires = set()
+        nfileRequires = set() # These need to be looked up in the rpmdb.
         reverselookup = {}
         ret = []
 
         # generate list of file requirement in rpmdb
         if self.installedFileRequires is None:
-            self.installedFileRequires = {}
-            self.installedUnresolvedFileRequires = set()
-            resolved = set()
-            for pkg in self.rpmdb.returnPackages():
-                for name, flag, evr in pkg.requires:
-                    if not name.startswith('/'):
-                        continue
-                    self.installedFileRequires.setdefault(pkg, []).append(name)
-                    if name not in resolved:
-                        dep = self.rpmdb.getProvides(name, flag, evr)
-                        resolved.add(name)
-                        if not dep:
-                            self.installedUnresolvedFileRequires.add(name)
+            self.installedFileRequires, \
+              self.installedUnresolvedFileRequires, \
+              self.installedFileProviders = self.rpmdb.fileRequiresData()
 
         # get file requirements from packages not deleted
-        for po, files in self.installedFileRequires.iteritems():
-            if not self._tsInfo.getMembersWithState(po.pkgtup, output_states=TS_REMOVE_STATES):
+        todel = []
+        for pkgtup, files in self.installedFileRequires.iteritems():
+            if self._tsInfo.getMembersWithState(pkgtup, output_states=TS_REMOVE_STATES):
+                todel.append(pkgtup)
+            else:
                 fileRequires.update(files)
                 for filename in files:
-                    reverselookup.setdefault(filename, []).append(po)
+                    reverselookup.setdefault(filename, []).append(pkgtup)
+        for pkgtup in todel:
+            del self.installedFileRequires[pkgtup]
 
         fileRequires -= self.installedUnresolvedFileRequires
 
@@ -950,6 +946,8 @@ class Depsolve(object):
         for txmbr in self._tsInfo.getMembersWithState(output_states=TS_INSTALL_STATES):
             for name, flag, evr in txmbr.po.requires:
                 if name.startswith('/'):
+                    pt = txmbr.po.pkgtup
+                    self.installedFileRequires.setdefault(pt, []).append(name)
                     # check if file requires was already unresolved in update
                     if name in self.installedUnresolvedFileRequires:
                         already_broken = False
@@ -959,23 +957,75 @@ class Depsolve(object):
                                 break
                         if already_broken:
                             continue
+                    if name not in fileRequires:
+                        nfileRequires.add(name)
                     fileRequires.add(name)
-                    reverselookup.setdefault(name, []).append(txmbr.po)
+                    reverselookup.setdefault(name, []).append(txmbr.po.pkgtup)
+
+        todel = []
+        for fname in self.installedFileProviders:
+            niFP_fname = []
+            for pkgtup in self.installedFileProviders[fname]:
+                if self._tsInfo.getMembersWithState(pkgtup, output_states=TS_REMOVE_STATES):
+                    continue
+                niFP_fname.append(pkgtup)
+
+            if not niFP_fname:
+                todel.append(fname)
+                continue
+
+            self.installedFileProviders[fname] = niFP_fname
+        for fname in todel:
+            del self.installedFileProviders[fname]
 
         # check the file requires
+        iFP = self.installedFileProviders
         for filename in fileRequires:
-            if not self.tsInfo.getOldProvides(filename) and not self.tsInfo.getNewProvides(filename):
-                for po in reverselookup[filename]:
-                    ret.append( (po, (filename, 0, '')) )
+            # In theory we need this to be:
+            #
+            # nprov, filename in iFP (or new), oprov
+            #
+            # ...this keeps the cache exactly the same as the non-cached data.
+            # However that also means that we'll always need the filelists, so
+            # we do:
+            #
+            # filename in iFP (if found return), oprov (if found return),
+            # nprov
+            #
+            # ...this means we'll always get the same _result_ (as we only need
+            # to know if _something_ provides), but our cache will be off on
+            # what does/doesn't provide the file.
+            if filename in self.installedFileProviders:
+                continue
 
-        return ret
+            oprov = self.tsInfo.getOldProvides(filename)
+            if oprov:
+                iFP.setdefault(filename, []).extend([po.pkgtup for po in oprov])
+                continue
+
+            nprov = self.tsInfo.getNewProvides(filename)
+            if nprov:
+                iFP.setdefault(filename, []).extend([po.pkgtup for po in nprov])
+                continue 
+
+            for pkgtup in reverselookup[filename]:
+                po = self.getInstalledPackageObject(pkgtup)
+                ret.append( (po, (filename, 0, '')) )
 
+        self.rpmdb.transactionCacheFileRequires(self.installedFileRequires, 
+                                        self.installedUnresolvedFileRequires,
+                                        self.installedFileProviders,
+                                        ret)
+
+        return ret
 
     def _checkConflicts(self):
         ret = [ ]
-        for po in self.rpmdb.returnPackages():
+        cpkgs = []
+        for po in self.rpmdb.returnConflictPackages():
             if self.tsInfo.getMembersWithState(po.pkgtup, output_states=TS_REMOVE_STATES):
                 continue
+            cpkgs.append(po)
             for conflict in po.returnPrco('conflicts'):
                 (r, f, v) = conflict
                 for conflicting_po in self.tsInfo.getNewProvides(r, f, v):
@@ -985,16 +1035,20 @@ class Depsolve(object):
                                  conflicting_po) )
         for txmbr in self.tsInfo.getMembersWithState(output_states=TS_INSTALL_STATES):
             po = txmbr.po
+            done = False
             for conflict in txmbr.po.returnPrco('conflicts'):
+                if not done:
+                    cpkgs.append(txmbr.po)
+                    done = True
                 (r, f, v) = conflict
                 for conflicting_po in self.tsInfo.getProvides(r, f, v):
                     if conflicting_po.pkgtup[0] == po.pkgtup[0] and conflicting_po.pkgtup[2:] == po.pkgtup[2:]:
                         continue
                     ret.append( (po, self._prco_req_nfv2req(r, f, v),
                                  conflicting_po) )
+        self.rpmdb.transactionCacheConflictPackages(cpkgs)
         return ret
 
-
     def isPackageInstalled(self, pkgname):
         lst = self.tsInfo.matchNaevr(name = pkgname)
         for txmbr in lst:
diff --git a/yum/rpmsack.py b/yum/rpmsack.py
index 151e33e..a5f4042 100644
--- a/yum/rpmsack.py
+++ b/yum/rpmsack.py
@@ -97,7 +97,10 @@ class RPMDBPackageSack(PackageSackBase):
                            rpm.RPMTAG_OBSOLETEFLAGS)
             }
 
-    def __init__(self, root='/', releasever=None):
+    # Do we want to cache rpmdb data in a file, for later use?
+    __cache_rpmdb__ = True
+
+    def __init__(self, root='/', releasever=None, cachedir=None):
         self.root = root
         self._idx2pkg = {}
         self._name2pkg = {}
@@ -107,6 +110,13 @@ class RPMDBPackageSack(PackageSackBase):
         self._get_pro_cache = {}
         self._get_req_cache  = {}
         self._loaded_gpg_keys = False
+        if cachedir is None:
+            cachedir = misc.getCacheDir()
+        self.setCacheDir(cachedir)
+        self._have_cached_rpmdbv_data = None
+        self._cached_conflicts_data = None
+        # Store the result of what happens, if a transaction completes.
+        self._trans_cache_store = {}
         self.ts = None
         self.releasever = releasever
         self.auto_close = False # this forces a self.ts.close() after
@@ -153,6 +163,13 @@ class RPMDBPackageSack(PackageSackBase):
             'conflicts' : { },
             'obsoletes' : { },
             }
+        self._have_cached_rpmdbv_data = None
+        self._cached_conflicts_data = None
+
+    def setCacheDir(self, cachedir):
+        """ Sets the internal cachedir value for the rpmdb, to be the
+            "rpmdb-cache" directory from this parent. """
+        self._cachedir = cachedir + "/rpmdb-cache/"
 
     def readOnlyTS(self):
         if not self.ts:
@@ -367,6 +384,103 @@ class RPMDBPackageSack(PackageSackBase):
             pkgobjlist = pkgobjlist[0] + pkgobjlist[1]
         return pkgobjlist
 
+    def _uncached_returnConflictPackages(self):
+        if self._cached_conflicts_data is None:
+            ret = []
+            for pkg in self.returnPackages():
+                if len(pkg.conflicts):
+                    ret.append(pkg)
+            self._cached_conflicts_data = ret
+        return self._cached_conflicts_data
+
+    def _write_conflicts_new(self, pkgs, rpmdbv):
+        if not os.access(self._cachedir, os.W_OK):
+            return
+
+        conflicts_fname = self._cachedir + '/conflicts'
+        fo = open(conflicts_fname + '.tmp', 'w')
+        fo.write("%s\n" % rpmdbv)
+        fo.write("%u\n" % len(pkgs))
+        for pkg in sorted(pkgs):
+            for var in pkg.pkgtup:
+                fo.write("%s\n" % var)
+        fo.close()
+        os.rename(conflicts_fname + '.tmp', conflicts_fname)
+
+    def _write_conflicts(self, pkgs):
+        rpmdbv = self.simpleVersion(main_only=True)[0]
+        self._write_conflicts_new(pkgs, rpmdbv)
+
+    def _read_conflicts(self):
+        if not self.__cache_rpmdb__:
+            return None
+
+        def _read_str(fo):
+            return fo.readline()[:-1]
+
+        conflict_fname = self._cachedir + '/conflicts'
+        if not os.path.exists(conflict_fname):
+            return None
+
+        fo = open(conflict_fname)
+        frpmdbv = fo.readline()
+        rpmdbv = self.simpleVersion(main_only=True)[0]
+        if not frpmdbv or rpmdbv != frpmdbv[:-1]:
+            return None
+
+        ret = []
+        try:
+            # Read the conflicts...
+            pkgtups_num = int(_read_str(fo))
+            while pkgtups_num > 0:
+                pkgtups_num -= 1
+
+                # n, a, e, v, r
+                pkgtup = (_read_str(fo), _read_str(fo),
+                          _read_str(fo), _read_str(fo), _read_str(fo))
+                int(pkgtup[2]) # Check epoch is valid
+                ret.extend(self.searchPkgTuple(pkgtup))
+            if fo.readline() != '': # Should be EOF
+                return None
+        except ValueError:
+            return None
+
+        self._cached_conflicts_data = ret
+        return self._cached_conflicts_data
+
+    def transactionCacheConflictPackages(self, pkgs):
+        if self.__cache_rpmdb__:
+            self._trans_cache_store['conflicts'] = pkgs
+
+    def returnConflictPackages(self):
+        """ Return a list of packages that have conflicts. """
+        pkgs = self._read_conflicts()
+        if pkgs is None:
+            pkgs = self._uncached_returnConflictPackages()
+            if self.__cache_rpmdb__:
+                self._write_conflicts(pkgs)
+
+        return pkgs
+
+    def transactionResultVersion(self, rpmdbv):
+        """ We are going to do a transaction, and the parameter will be the
+            rpmdb version when we finish. The idea being we can update all
+            our rpmdb caches for that rpmdb version. """
+
+        if not self.__cache_rpmdb__:
+            self._trans_cache_store = {}
+            return
+
+        if 'conflicts' in self._trans_cache_store:
+            pkgs = self._trans_cache_store['conflicts']
+            self._write_conflicts_new(pkgs, rpmdbv)
+
+        if 'file-requires' in self._trans_cache_store:
+            data = self._trans_cache_store['file-requires']
+            self._write_file_requires(rpmdbv, data)
+
+        self._trans_cache_store = {}
+
     def returnGPGPubkeyPackages(self):
         """ Return packages of the gpg-pubkeys ... hacky. """
         ts = self.readOnlyTS()
@@ -377,6 +491,198 @@ class RPMDBPackageSack(PackageSackBase):
             ret.append(self._makePackageObject(hdr, mi.instance()))
         return ret
 
+    def _read_file_requires(self):
+        def _read_str(fo):
+            return fo.readline()[:-1]
+
+        assert self.__cache_rpmdb__
+        if not os.path.exists(self._cachedir + '/file-requires'):
+            return None, None
+
+        rpmdbv = self.simpleVersion(main_only=True)[0]
+        fo = open(self._cachedir + '/file-requires')
+        frpmdbv = fo.readline()
+        if not frpmdbv or rpmdbv != frpmdbv[:-1]:
+            return None, None
+
+        iFR = {}
+        iFP = {}
+        try:
+            # Read the requires...
+            pkgtups_num = int(_read_str(fo))
+            while pkgtups_num > 0:
+                pkgtups_num -= 1
+
+                # n, a, e, v, r
+                pkgtup = (_read_str(fo), _read_str(fo),
+                          _read_str(fo), _read_str(fo), _read_str(fo))
+                int(pkgtup[2]) # Check epoch is valid
+
+                files_num = int(_read_str(fo))
+                while files_num > 0:
+                    files_num -= 1
+
+                    fname = _read_str(fo)
+
+                    iFR.setdefault(pkgtup, []).append(fname)
+
+            # Read the provides...
+            files_num = int(_read_str(fo))
+            while files_num > 0:
+                files_num -= 1
+                fname = _read_str(fo)
+                pkgtups_num = int(_read_str(fo))
+                while pkgtups_num > 0:
+                    pkgtups_num -= 1
+
+                    # n, a, e, v, r
+                    pkgtup = (_read_str(fo), _read_str(fo),
+                              _read_str(fo), _read_str(fo), _read_str(fo))
+                    int(pkgtup[2]) # Check epoch is valid
+
+                    iFP.setdefault(fname, []).append(pkgtup)
+
+            if fo.readline() != '': # Should be EOF
+                return None, None
+        except ValueError:
+            return None, None
+
+        return iFR, iFP
+
+    def fileRequiresData(self):
+        """ Get a cached copy of the fileRequiresData for
+            depsolving/checkFileRequires, note the giant comment in that
+            function about how we don't keep this perfect for the providers of
+            the requires. """
+        if self.__cache_rpmdb__:
+            iFR, iFP = self._read_file_requires()
+            if iFR is not None:
+                return iFR, set(), iFP
+
+        installedFileRequires = {}
+        installedUnresolvedFileRequires = set()
+        resolved = set()
+        for pkg in self.returnPackages():
+            for name, flag, evr in pkg.requires:
+                if not name.startswith('/'):
+                    continue
+                installedFileRequires.setdefault(pkg.pkgtup, []).append(name)
+                if name not in resolved:
+                    dep = self.getProvides(name, flag, evr)
+                    resolved.add(name)
+                    if not dep:
+                        installedUnresolvedFileRequires.add(name)
+
+        fileRequires = set()
+        for fnames in installedFileRequires.itervalues():
+            fileRequires.update(fnames)
+        installedFileProviders = {}
+        for fname in fileRequires:
+            pkgtups = [pkg.pkgtup for pkg in self.getProvides(fname)]
+            installedFileProviders[fname] = pkgtups
+
+        ret =  (installedFileRequires, installedUnresolvedFileRequires,
+                installedFileProviders)
+        if self.__cache_rpmdb__:
+            rpmdbv = self.simpleVersion(main_only=True)[0]
+            self._write_file_requires(rpmdbv, ret)
+
+        return ret
+
+    def transactionCacheFileRequires(self, installedFileRequires,
+                                     installedUnresolvedFileRequires,
+                                     installedFileProvides,
+                                     problems):
+        if not self.__cache_rpmdb__:
+            return
+
+        if installedUnresolvedFileRequires or problems:
+            return
+
+        data = (installedFileRequires,
+                installedUnresolvedFileRequires,
+                installedFileProvides)
+
+        self._trans_cache_store['file-requires'] = data
+
+    def _write_file_requires(self, rpmdbversion, data):
+        if not os.access(self._cachedir, os.W_OK):
+            return
+
+        (installedFileRequires,
+         installedUnresolvedFileRequires,
+         installedFileProvides) = data
+
+        fo = open(self._cachedir + '/file-requires.tmp', 'w')
+        fo.write("%s\n" % rpmdbversion)
+
+        fo.write("%u\n" % len(installedFileRequires))
+        for pkgtup in sorted(installedFileRequires):
+            for var in pkgtup:
+                fo.write("%s\n" % var)
+            filenames = set(installedFileRequires[pkgtup])
+            fo.write("%u\n" % len(filenames))
+            for fname in sorted(filenames):
+                fo.write("%s\n" % fname)
+
+        fo.write("%u\n" % len(installedFileProvides))
+        for fname in sorted(installedFileProvides):
+            fo.write("%s\n" % fname)
+
+            pkgtups = set(installedFileProvides[fname])
+            fo.write("%u\n" % len(pkgtups))
+            for pkgtup in sorted(pkgtups):
+                for var in pkgtup:
+                    fo.write("%s\n" % var)
+        fo.close()
+        os.rename(self._cachedir + '/file-requires.tmp',
+                  self._cachedir + '/file-requires')
+
+    def _get_cached_simpleVersion_main(self):
+        """ Return the cached string of the main rpmdbv. """
+        if self._have_cached_rpmdbv_data is not None:
+            return self._have_cached_rpmdbv_data
+
+        if not self.__cache_rpmdb__:
+            return None
+
+        #  This test is "obvious" and the only thing to come out of:
+        # http://lists.rpm.org/pipermail/rpm-maint/2007-November/001719.html
+        # ...if anything gets implemented, we should change.
+        rpmdbvfname = self._cachedir + "/version"
+        rpmdbfname  = "/var/lib/rpm/Packages"
+
+        if os.path.exists(rpmdbvfname) and os.path.exists(rpmdbfname):
+            # See if rpmdb has "changed" ...
+            nmtime = os.path.getmtime(rpmdbvfname)
+            omtime = os.path.getmtime(rpmdbfname)
+            if omtime <= nmtime:
+                rpmdbv = open(rpmdbvfname).readline()[:-1]
+                self._have_cached_rpmdbv_data  = rpmdbv
+        return self._have_cached_rpmdbv_data
+
+    def _put_cached_simpleVersion_main(self, rpmdbv):
+        self._have_cached_rpmdbv_data  = str(rpmdbv)
+
+        if not self.__cache_rpmdb__:
+            return
+
+        rpmdbvfname = self._cachedir + "/version"
+        if not os.access(self._cachedir, os.W_OK):
+            if os.path.exists(self._cachedir):
+                return
+
+            try:
+                os.makedirs(self._cachedir)
+            except (IOError, OSError), e:
+                return
+
+        fo = open(rpmdbvfname + ".tmp", "w")
+        fo.write(self._have_cached_rpmdbv_data)
+        fo.write('\n')
+        fo.close()
+        os.rename(rpmdbvfname + ".tmp", rpmdbvfname)
+
     def simpleVersion(self, main_only=False, groups={}):
         """ Return a simple version for all installed packages. """
         def _up_revs(irepos, repoid, rev, pkg, csum):
@@ -387,6 +693,11 @@ class RPMDBPackageSack(PackageSackBase):
                 rpsv = irevs.setdefault(rev, PackageSackVersion())
                 rpsv.update(pkg, csum)
 
+        if main_only and not groups:
+            rpmdbv = self._get_cached_simpleVersion_main()
+            if rpmdbv is not None:
+                return [rpmdbv, {}]
+
         main = PackageSackVersion()
         irepos = {}
         main_grps = {}
@@ -420,6 +731,9 @@ class RPMDBPackageSack(PackageSackBase):
                 if pkg.name in groups[group]:
                     _up_revs(irepos_grps[group], repoid, rev, pkg, csum)
 
+        if self._have_cached_rpmdbv_data is None:
+            self._put_cached_simpleVersion_main(main)
+
         if groups:
             return [main, irepos, main_grps, irepos_grps]
         return [main, irepos]
diff --git a/yum/transactioninfo.py b/yum/transactioninfo.py
index c2fc804..c7d5b8c 100644
--- a/yum/transactioninfo.py
+++ b/yum/transactioninfo.py
@@ -26,7 +26,7 @@ to rpm.
 """
 
 from constants import *
-from packageSack import PackageSack
+from packageSack import PackageSack, PackageSackVersion
 from packages import YumInstalledPackage
 from sqlitesack import YumAvailablePackageSqlite
 import Errors
@@ -558,6 +558,42 @@ class TransactionData:
         result.update(self.getNewRequires(name, flag, version))
         return result
 
+    def futureRpmDBVersion(self):
+        """ Return a simple version for the future rpmdb. Works like
+            rpmdb.simpleVersion(main_only=True)[0], but for the state the rpmdb
+            will be in after the transaction. """
+        pkgs = self.rpmdb.returnPackages()
+        _reinstalled_pkgtups = {}
+        for txmbr in self.getMembersWithState(None, TS_INSTALL_STATES):
+            # reinstalls have to use their "new" checksum data, in case it's
+            # different.
+            if hasattr(txmbr, 'reinstall') and txmbr.reinstall:
+                _reinstalled_pkgtups[txmbr.po.pkgtup] = txmbr.po
+            pkgs.append(txmbr.po)
+
+        main = PackageSackVersion()
+        for pkg in sorted(pkgs):
+            if pkg.repoid != 'installed':
+                # Paste from PackageSackBase.simpleVersion()
+                csum = pkg.returnIdSum()
+                main.update(pkg, csum)
+                continue
+
+            # Installed pkg, see if it's about to die
+            if self.getMembersWithState(pkg.pkgtup, TS_REMOVE_STATES):
+                continue
+            # ...or die and be risen again (Zombie!)
+            if pkg.pkgtup in _reinstalled_pkgtups:
+                continue
+
+            # Paste from rpmdb.simpleVersion()
+            ydbi = pkg.yumdb_info
+            csum = None
+            if 'checksum_type' in ydbi and 'checksum_data' in ydbi:
+                csum = (ydbi.checksum_type, ydbi.checksum_data)
+            main.update(pkg, csum)
+        return main
+
 class ConditionalTransactionData(TransactionData):
     """A transaction data implementing conditional package addition"""
     def __init__(self):
@@ -615,6 +651,7 @@ class SortableTransactionData(TransactionData):
         self._sorted.reverse()
         return self._sorted
 
+
 class TransactionMember:
     """Class to describe a Transaction Member (a pkg to be installed/
        updated/erased)."""
diff --git a/yumcommands.py b/yumcommands.py
index b809216..f123c16 100644
--- a/yumcommands.py
+++ b/yumcommands.py
@@ -1099,20 +1099,29 @@ class VersionCommand(YumCommand):
 
         verbose = base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3)
         groups = {}
-        gconf = yum.config.readVersionGroupsConfig()
+        if vcmd in ('nogroups', 'nogroups-installed', 'nogroups-available',
+                    'nogroups-all'):
+            gconf = []
+            if vcmd == 'nogroups':
+                vcmd = 'installed'
+            else:
+                vcmd = vcmd[len('nogroups-'):]
+        else:
+            gconf = yum.config.readVersionGroupsConfig()
+
         for group in gconf:
             groups[group] = set(gconf[group].pkglist)
             if gconf[group].run_with_packages:
                 groups[group].update(base.run_with_package_names)
 
-        if vcmd in ('grouplist'):
+        if vcmd == 'grouplist':
             print _(" Yum version groups:")
             for group in sorted(groups):
                 print "   ", group
 
             return 0, ['version grouplist']
 
-        if vcmd in ('groupinfo'):
+        if vcmd == 'groupinfo':
             for group in groups:
                 if group not in extcmds[1:]:
                     continue
commit 366c3e6bb9ebd7ad3f121bdf2c089b0132d51604
Author: James Antill <james@and.org>
Date:   Fri Dec 4 08:27:01 2009 -0500

     Fixes for clean functions, BZ 544173
    
     When nothing to be done, set removed.
     When doing "clean all" fix the var. name.
     When doing anything but clean rpmdb, fix the return.
     Allow rpmdb as a valid arg.
     Fix getFileList() with ext == ''

diff --git a/cli.py b/cli.py
index f93a706..2c6c9a4 100644
--- a/cli.py
+++ b/cli.py
@@ -897,7 +897,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput):
             rpmcode, rpmresults = self.cleanRpmDB()
             self.plugins.run('clean')
             
-            code = hdrcode + pkgcode + xmlcode + dbcode + rpmdb
+            code = hdrcode + pkgcode + xmlcode + dbcode + rpmcode
             results = (hdrresults + pkgresults + xmlresults + dbresults +
                        rpmresults)
             for msg in results:
diff --git a/yum/__init__.py b/yum/__init__.py
index affa92b..1c82144 100644
--- a/yum/__init__.py
+++ b/yum/__init__.py
@@ -1714,15 +1714,15 @@ class YumBase(depsolve.Depsolve):
 
     def _cleanFiles(self, exts, pathattr, filetype):
         filelist = []
-        removed = 0
         for ext in exts:
             for repo in self.repos.listEnabled():
                 path = getattr(repo, pathattr)
                 if os.path.exists(path) and os.path.isdir(path):
                     filelist = misc.getFileList(path, ext, filelist)
-        self._cleanFilelist(filetype, filelist)
+        return self._cleanFilelist(filetype, filelist)
 
     def _cleanFilelist(self, filetype, filelist):
+        removed = 0
         for item in filelist:
             try:
                 misc.unlink_f(item)
diff --git a/yum/misc.py b/yum/misc.py
index 642f9a2..f221f1d 100644
--- a/yum/misc.py
+++ b/yum/misc.py
@@ -336,7 +336,7 @@ def getFileList(path, ext, filelist):
         if os.path.isdir(path + '/' + d):
             filelist = getFileList(path + '/' + d, ext, filelist)
         else:
-            if d[-extlen:].lower() == '%s' % (ext):
+            if not ext or d[-extlen:].lower() == '%s' % (ext):
                 newpath = os.path.normpath(path + '/' + d)
                 filelist.append(newpath)
                     
diff --git a/yumcommands.py b/yumcommands.py
index 83d9d00..4fe2fe1 100644
--- a/yumcommands.py
+++ b/yumcommands.py
@@ -85,7 +85,7 @@ def checkGroupArg(base, basecmd, extcmds):
 
 def checkCleanArg(base, basecmd, extcmds):
     VALID_ARGS = ('headers', 'packages', 'metadata', 'dbcache', 'plugins',
-                  'expire-cache', 'all')
+                  'expire-cache', 'rpmdb', 'all')
 
     if len(extcmds) == 0:
         base.logger.critical(_('Error: clean requires an option: %s') % (
commit dbcdcd605d233c555f3ce3861152a2378bf9e622
Author: James Antill <james@and.org>
Date:   Thu Dec 10 09:46:00 2009 -0500

    Fix when we have file require(s) from just to be installed pkgs. The mash bug

diff --git a/yum/depsolve.py b/yum/depsolve.py
index b5953c2..d46452c 100644
--- a/yum/depsolve.py
+++ b/yum/depsolve.py
@@ -1009,7 +1009,11 @@ class Depsolve(object):
                 continue 
 
             for pkgtup in reverselookup[filename]:
-                po = self.getInstalledPackageObject(pkgtup)
+                po = self.tsInfo.getMembersWithState(pkgtup, TS_INSTALL_STATES)
+                if po:
+                    po = po[0] # Should only have one
+                else:
+                    po = self.getInstalledPackageObject(pkgtup)
                 ret.append( (po, (filename, 0, '')) )
 
         self.rpmdb.transactionCacheFileRequires(self.installedFileRequires, 
commit 67882783e6add4c5c9b717872554c23fc0ade913
Author: James Antill <james@and.org>
Date:   Thu Dec 10 18:28:28 2009 -0500

    getMembers returns txmbrs not POs ... *sigh*

diff --git a/yum/depsolve.py b/yum/depsolve.py
index d46452c..a09bd4b 100644
--- a/yum/depsolve.py
+++ b/yum/depsolve.py
@@ -1011,7 +1011,7 @@ class Depsolve(object):
             for pkgtup in reverselookup[filename]:
                 po = self.tsInfo.getMembersWithState(pkgtup, TS_INSTALL_STATES)
                 if po:
-                    po = po[0] # Should only have one
+                    po = po[0].po # Should only have one
                 else:
                     po = self.getInstalledPackageObject(pkgtup)
                 ret.append( (po, (filename, 0, '')) )