Blame SOURCES/BZ-1062959-add-fs-command.patch

d2a170
commit b6f5e550048705c0979f0e5bb88f0b83d5750daf
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Wed Feb 12 15:59:20 2014 -0500
d2a170
d2a170
    Fix yum repo-pkgs <blah> install exist-other-repo.
d2a170
d2a170
diff --git a/yum/__init__.py b/yum/__init__.py
d2a170
index 0604d63..622bc14 100644
d2a170
--- a/yum/__init__.py
d2a170
+++ b/yum/__init__.py
d2a170
@@ -4714,6 +4714,8 @@ much more problems).
d2a170
                         arg)
d2a170
 
d2a170
                     mypkgs = self.returnPackagesByDep(arg)
d2a170
+                    if repoid:
d2a170
+                        mypkgs = misc.filter_pkgs_repoid(mypkgs, repoid)
d2a170
                     if not misc.re_glob(arg):
d2a170
                         mypkgs = self.bestPackagesFromList(mypkgs,
d2a170
                                                            single_name=True,
d2a170
commit 26128173b362474456e8f0642073ecb0322ed031
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Wed Feb 12 16:06:00 2014 -0500
d2a170
d2a170
    Add override_install_langs configuration, for container install needs.
d2a170
d2a170
diff --git a/docs/yum.conf.5 b/docs/yum.conf.5
d2a170
index c66eba8..bf3c442 100644
d2a170
--- a/docs/yum.conf.5
d2a170
+++ b/docs/yum.conf.5
d2a170
@@ -329,6 +329,12 @@ context of an rpm transaction set you're best leaving it alone. Default is
d2a170
 an empty list.
d2a170
 
d2a170
 .IP
d2a170
+\fBoverride_install_langs\fR
d2a170
+This is a way to override rpm's _install_langs macro. without having to change
d2a170
+it within rpm's macro file.
d2a170
+Default is nothing (so does nothing).
d2a170
+
d2a170
+.IP
d2a170
 \fBrecent\fR
d2a170
 Number of days back to look for `recent' packages added to a repository.
d2a170
 Used by the \fBlist recent\fR command. Default is `7'.
d2a170
diff --git a/yum/config.py b/yum/config.py
d2a170
index c38d574..09a4dcc 100644
d2a170
--- a/yum/config.py
d2a170
+++ b/yum/config.py
d2a170
@@ -768,6 +768,7 @@ class YumConf(StartupConf):
d2a170
             'kernel-hugemem', 'kernel-enterprise', 'kernel-bigmem',
d2a170
             'kernel-devel', 'kernel-PAE', 'kernel-PAE-debug'])
d2a170
     tsflags = ListOption()
d2a170
+    override_install_langs = Option()
d2a170
 
d2a170
     assumeyes = BoolOption(False)
d2a170
     assumeno  = BoolOption(False)
d2a170
diff --git a/yum/depsolve.py b/yum/depsolve.py
d2a170
index 8a675eb..1840b43 100644
d2a170
--- a/yum/depsolve.py
d2a170
+++ b/yum/depsolve.py
d2a170
@@ -180,6 +180,13 @@ class Depsolve(object):
d2a170
     def initActionTs(self):
d2a170
         """Set up the transaction set that will be used for all the work."""
d2a170
         
d2a170
+        # LOL, override rpm transaction macro.
d2a170
+        # Must be done before rpmtsCreate()
d2a170
+        if self.conf.override_install_langs:
d2a170
+            old_install_langs = rpm.expandMacro("%_install_langs")
d2a170
+            rpm.expandMacro("%define _install_langs " +
d2a170
+                            self.conf.override_install_langs)
d2a170
+
d2a170
         self._ts = rpmUtils.transaction.TransactionWrapper(self.conf.installroot)
d2a170
         ts_flags_to_rpm = { 'noscripts': rpm.RPMTRANS_FLAG_NOSCRIPTS,
d2a170
                             'notriggers': rpm.RPMTRANS_FLAG_NOTRIGGERS,
d2a170
commit 99540142d78484327716643daab03581fef6ee4b
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Wed Feb 12 16:06:56 2014 -0500
d2a170
d2a170
    Install yumdb keys for install_langs and tsflags values.
d2a170
d2a170
diff --git a/yum/__init__.py b/yum/__init__.py
d2a170
index 622bc14..3b6ed82 100644
d2a170
--- a/yum/__init__.py
d2a170
+++ b/yum/__init__.py
d2a170
@@ -1915,6 +1915,13 @@ much more problems).
d2a170
                 txmbr_cb(txmbr, count)
d2a170
             return count
d2a170
         
d2a170
+        oil = self.conf.override_install_langs
d2a170
+        if not oil:
d2a170
+            oil = rpm.expandMacro("%_install_langs")
d2a170
+        if oil == 'all':
d2a170
+            oil = ''
d2a170
+        elif oil:
d2a170
+            oil = ":".join(sorted(oil.split(':')))
d2a170
         vt_st = time.time()
d2a170
         self.plugins.run('preverifytrans')
d2a170
         count = 0
d2a170
@@ -1942,6 +1949,17 @@ much more problems).
d2a170
                     if var == 'arch':     continue
d2a170
                     # Skip uuid?
d2a170
                     setattr(po.yumdb_info, 'var_' + var, self.conf.yumvar[var])
d2a170
+                if oil:
d2a170
+                    po.yumdb_info.ts_install_langs = oil
d2a170
+                if 'nocontexts' in self.conf.tsflags:
d2a170
+                    po.yumdb_info.tsflag_nocontexts = 'true'
d2a170
+                if 'nodocs' in self.conf.tsflags:
d2a170
+                    po.yumdb_info.tsflag_nodocs = 'true'
d2a170
+                if 'noscripts' in self.conf.tsflags:
d2a170
+                    po.yumdb_info.tsflag_noscripts = 'true'
d2a170
+                if 'notriggers' in self.conf.tsflags:
d2a170
+                    po.yumdb_info.tsflag_notriggers = 'true'
d2a170
+
d2a170
                 if hasattr(self, 'args') and self.args:
d2a170
                     po.yumdb_info.command_line = ' '.join(self.args)
d2a170
                 elif hasattr(self, 'cmds') and self.cmds:
d2a170
commit 598b2f64f06dc625fca3e5a3b9ef0000f973eb7f
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Wed Feb 12 16:07:40 2014 -0500
d2a170
d2a170
    Add _writeRawConfigFile() so we can change yum.conf values.
d2a170
d2a170
diff --git a/yum/config.py b/yum/config.py
d2a170
index 09a4dcc..f213fc1 100644
d2a170
--- a/yum/config.py
d2a170
+++ b/yum/config.py
d2a170
@@ -1301,6 +1301,48 @@ def writeRawRepoFile(repo,only=None):
d2a170
     fp.write(str(ini))
d2a170
     fp.close()
d2a170
 
d2a170
+# Copied from yum-config-manager ... how we alter yu.conf ... used in "yum fs"
d2a170
+def _writeRawConfigFile(filename, section_id, yumvar,
d2a170
+                        cfgoptions, items, optionobj,
d2a170
+                        only=None):
d2a170
+    """
d2a170
+    From writeRawRepoFile, but so we can alter [main] too.
d2a170
+    """
d2a170
+    ini = INIConfig(open(filename))
d2a170
+
d2a170
+    osection_id = section_id
d2a170
+    # b/c repoids can have $values in them we need to map both ways to figure
d2a170
+    # out which one is which
d2a170
+    if section_id not in ini._sections:
d2a170
+        for sect in ini._sections.keys():
d2a170
+            if varReplace(sect, yumvar) == section_id:
d2a170
+                section_id = sect
d2a170
+
d2a170
+    # Updated the ConfigParser with the changed values
d2a170
+    cfgOptions = cfgoptions(osection_id)
d2a170
+    for name,value in items():
d2a170
+        if value is None: # Proxy
d2a170
+            continue
d2a170
+
d2a170
+        if only is not None and name not in only:
d2a170
+            continue
d2a170
+
d2a170
+        option = optionobj(name)
d2a170
+        ovalue = option.tostring(value)
d2a170
+        #  If the value is the same, but just interpreted ... when we don't want
d2a170
+        # to keep the interpreted values.
d2a170
+        if (name in ini[section_id] and
d2a170
+            ovalue == varReplace(ini[section_id][name], yumvar)):
d2a170
+            ovalue = ini[section_id][name]
d2a170
+
d2a170
+        if name not in cfgOptions and option.default == value:
d2a170
+            continue
d2a170
+
d2a170
+        ini[section_id][name] = ovalue
d2a170
+    fp =file(filename, "w")
d2a170
+    fp.write(str(ini))
d2a170
+    fp.close()
d2a170
+
d2a170
 #def main():
d2a170
 #    mainconf = readMainConfig(readStartupConfig('/etc/yum/yum.conf', '/'))
d2a170
 #    print mainconf.cachedir
d2a170
commit 6a415f0b18c0533dfdc97fdab3306ec8ccebb52d
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Wed Feb 12 16:55:45 2014 -0500
d2a170
d2a170
    Add fs sub-command and docs. Mostly for small installroot creation.
d2a170
d2a170
diff --git a/cli.py b/cli.py
d2a170
index eed63a2..7173688 100755
d2a170
--- a/cli.py
d2a170
+++ b/cli.py
d2a170
@@ -112,6 +112,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput):
d2a170
         self.registerCommand(yumcommands.UpdateinfoCommand())
d2a170
         self.registerCommand(yumcommands.UpdateMinimalCommand())
d2a170
         self.registerCommand(yumcommands.FSSnapshotCommand())
d2a170
+        self.registerCommand(yumcommands.FSCommand())
d2a170
 
d2a170
     def registerCommand(self, command):
d2a170
         """Register a :class:`yumcommands.YumCommand` so that it can be called by
d2a170
diff --git a/docs/yum.8 b/docs/yum.8
d2a170
index c9b529e..31c1c88 100644
d2a170
--- a/docs/yum.8
d2a170
+++ b/docs/yum.8
d2a170
@@ -86,6 +86,8 @@ gnome\-packagekit application\&.
d2a170
 .br
d2a170
 .I \fR * fssnapshot [summary | list | have-space | create | delete]
d2a170
 .br
d2a170
+.I \fR * fs [filters | refilter | refilter-cleanup | du]
d2a170
+.br
d2a170
 .I \fR * check
d2a170
 .br 
d2a170
 .I \fR * help [command] 
d2a170
@@ -718,6 +720,36 @@ then you can create and delete snapshots using:
d2a170
 Configuration Options: \fBfssnap_automatic_pre\fP, \fBfssnap_automatic_post\fP, \fBfssnap_automatic_keep\fP, \fBfssnap_percentage\fP, \fBfssnap_devices\fP
d2a170
 
d2a170
 .IP
d2a170
+.IP "\fBfs\fP"
d2a170
+This command has a few sub-commands to act on the filesystem data of the host,
d2a170
+mainly for removing languages/documentation for minimal installs:
d2a170
+
d2a170
+.br 
d2a170
+.I \fR yum fs filters
d2a170
+
d2a170
+.br 
d2a170
+.I \fR yum fs filter languages en:es
d2a170
+
d2a170
+.br 
d2a170
+.I \fR yum fs filter documentation
d2a170
+
d2a170
+.br 
d2a170
+.I \fR yum fs refilter [package(s)]
d2a170
+
d2a170
+.br 
d2a170
+.I \fR yum fs refilter-cleanup [package(s)]
d2a170
+
d2a170
+.br 
d2a170
+.I \fR yum fs du [path]
d2a170
+
d2a170
+the first 3 being a simple interface to change yum.conf altering the tsflags
d2a170
+and override_install_langs configurations. The refilter command is an optimized
d2a170
+way of calling "yum reinstall" to reinstall the packages with the new filters
d2a170
+applied. The refilter-cleanup command is needed because rpm doesn't actually
d2a170
+remove the files on reinstall, as it should. And the du command is included so
d2a170
+you can easily see the space used/saved.
d2a170
+
d2a170
+.IP
d2a170
 .IP "\fBcheck\fP"
d2a170
 Checks the local rpmdb and produces information on any problems it finds. You
d2a170
 can pass the check command the arguments "dependencies", "duplicates", "obsoletes" or "provides",
d2a170
diff --git a/docs/yum.conf.5 b/docs/yum.conf.5
d2a170
index bf3c442..4ec7689 100644
d2a170
--- a/docs/yum.conf.5
d2a170
+++ b/docs/yum.conf.5
d2a170
@@ -327,12 +327,14 @@ with newer rpm versions.
d2a170
 You can set all/any of them. However, if you don't know what these do in the
d2a170
 context of an rpm transaction set you're best leaving it alone. Default is
d2a170
 an empty list.
d2a170
+Also see the "yum fs" command, for excluding docs.
d2a170
 
d2a170
 .IP
d2a170
 \fBoverride_install_langs\fR
d2a170
 This is a way to override rpm's _install_langs macro. without having to change
d2a170
 it within rpm's macro file.
d2a170
 Default is nothing (so does nothing).
d2a170
+Also see the "yum fs" command.
d2a170
 
d2a170
 .IP
d2a170
 \fBrecent\fR
d2a170
diff --git a/yumcommands.py b/yumcommands.py
d2a170
index 4214383..03450c4 100644
d2a170
--- a/yumcommands.py
d2a170
+++ b/yumcommands.py
d2a170
@@ -4291,3 +4291,457 @@ class FSSnapshotCommand(YumCommand):
d2a170
             print msg % (len(snaps), base.format_number(used), len(dev_oris))
d2a170
 
d2a170
         return 0, [basecmd + ' ' + subcommand + ' done']
d2a170
+
d2a170
+
d2a170
+class FSCommand(YumCommand):
d2a170
+    def getNames(self):
d2a170
+        return ['fs']
d2a170
+
d2a170
+    def getUsage(self):
d2a170
+        return "[]"
d2a170
+
d2a170
+    def getSummary(self):
d2a170
+        return _("Creates filesystem snapshots, or lists/deletes current snapshots.")
d2a170
+
d2a170
+    def doCheck(self, base, basecmd, extcmds):
d2a170
+        """Verify that conditions are met so that this command can run.
d2a170
+        These include that the program is being run by the root user,
d2a170
+        that there are enabled repositories with gpg keys, and that
d2a170
+        this command is called with appropriate arguments.
d2a170
+
d2a170
+        :param base: a :class:`yum.Yumbase` object
d2a170
+        :param basecmd: the name of the command
d2a170
+        :param extcmds: the command line arguments passed to *basecmd*
d2a170
+        """
d2a170
+        checkRootUID(base)
d2a170
+
d2a170
+    def _fs_pkg_walk(self, pkgs, prefix, modified=False, verbose=False):
d2a170
+
d2a170
+        pfr = {'norm' : {},
d2a170
+               'mod' : {},
d2a170
+               'ghost' : {},
d2a170
+               'miss' : {},
d2a170
+               'not' : {}
d2a170
+               }
d2a170
+
d2a170
+        def quick_match(pkgs):
d2a170
+            for pkg in pkgs:
d2a170
+                for fname in pkg.filelist + pkg.dirlist:
d2a170
+                    if not fname.startswith(prefix):
d2a170
+                        continue
d2a170
+                    pfr['norm'][fname] = pkg
d2a170
+                for fname in pkg.ghostlist:
d2a170
+                    if not fname.startswith(prefix):
d2a170
+                        continue
d2a170
+                    pfr['ghost'][fname] = pkg
d2a170
+            return pfr
d2a170
+
d2a170
+        def _quick_match_iter(pkgs):
d2a170
+            # Walking the fi information is much slower than filelist/dirlist
d2a170
+            for pkg in pkgs:
d2a170
+                found = False
d2a170
+                for fname in pkg.dirlist:
d2a170
+                    if fname.startswith(prefix):
d2a170
+                        yield pkg
d2a170
+                        found = True
d2a170
+                        break
d2a170
+                if found:
d2a170
+                    continue
d2a170
+                for fname in pkg.filelist:
d2a170
+                    if fname.startswith(prefix):
d2a170
+                        yield pkg
d2a170
+                        found = True
d2a170
+                        break
d2a170
+                if found:
d2a170
+                    continue
d2a170
+                for fname in pkg.ghostlist:
d2a170
+                    if fname.startswith(prefix):
d2a170
+                        yield pkg
d2a170
+                        break
d2a170
+
d2a170
+        def verify_match(pkgs):
d2a170
+            _pfs = []
d2a170
+            def scoop_pfs(pfs):
d2a170
+                _pfs.append(pfs)
d2a170
+
d2a170
+                if not modified:
d2a170
+                    return []
d2a170
+
d2a170
+                return pfs
d2a170
+
d2a170
+            if prefix != '/':
d2a170
+                pkgs = _quick_match_iter(pkgs)
d2a170
+            for pkg in pkgs:
d2a170
+                _pfs = []
d2a170
+                probs = pkg.verify(patterns=[prefix+'*'], fake_problems=False,
d2a170
+                                   callback=scoop_pfs)
d2a170
+
d2a170
+                for pf in _pfs[0]:
d2a170
+                    if pf.filename in probs:
d2a170
+                        pfr['mod'][pf.filename] = pkg
d2a170
+                    elif pf.rpmfile_state == 'not installed':
d2a170
+                        pfr['not'][pf.filename] = pkg
d2a170
+                    elif 'ghost' in pf.rpmfile_types:
d2a170
+                        pfr['ghost'][pf.filename] = pkg
d2a170
+                    elif 'missing ok' in pf.rpmfile_types:
d2a170
+                        pfr['miss'][pf.filename] = pkg
d2a170
+                    else:
d2a170
+                        pfr['norm'][pf.filename] = pkg
d2a170
+            return pfr
d2a170
+
d2a170
+        # return quick_match(pkgs)
d2a170
+        return verify_match(pkgs)
d2a170
+
d2a170
+    def _fs_du(self, base, extcmds):
d2a170
+        def _dir_prefixes(path):
d2a170
+            while path != '/':
d2a170
+                path = os.path.dirname(path)
d2a170
+                yield path
d2a170
+
d2a170
+        def loc_num(x):
d2a170
+            """ String of a number in the readable "locale" format. """
d2a170
+            return locale.format("%d", int(x), True)
d2a170
+
d2a170
+        data = {'pkgs_size' : {},
d2a170
+                'pkgs_not_size' : {},
d2a170
+                'pkgs_ghost_size' : {},
d2a170
+                'pkgs_miss_size' : {},
d2a170
+                'pkgs_mod_size' : {},
d2a170
+
d2a170
+                'pres_size' : {},
d2a170
+                'data_size' : {},
d2a170
+                'data_not_size' : {},
d2a170
+
d2a170
+                'pkgs_count' : 0,
d2a170
+                'pkgs_not_count' : 0,
d2a170
+                'pkgs_ghost_count' : 0,
d2a170
+                'pkgs_miss_count' : 0,
d2a170
+                'pkgs_mod_count' : 0,
d2a170
+
d2a170
+                'data_count' : 0} # data_not_count == pkgs_not_count
d2a170
+
d2a170
+        def _add_size(d, v, size):
d2a170
+            if v not in d:
d2a170
+                d[v] = 0
d2a170
+            d[v] += size
d2a170
+
d2a170
+        def deal_with_file(fpath, need_prefix=True):
d2a170
+            size = os.path.getsize(fpath)
d2a170
+            if fpath in pfr['norm']:
d2a170
+                data['pkgs_count'] += size
d2a170
+                _add_size(data['pkgs_size'], pfr['norm'][fpath], size)
d2a170
+            elif fpath in pfr['ghost']:
d2a170
+                data['pkgs_ghost_count'] += size
d2a170
+                _add_size(data['pkgs_ghost_size'], pfr['ghost'][fpath], size)
d2a170
+            elif fpath in pfr['not']:
d2a170
+                data['pkgs_not_count'] += size
d2a170
+                _add_size(data['pkgs_not_size'], pfr['not'][fpath], size)
d2a170
+                data['data_not_size'][fpath] = size
d2a170
+            elif fpath in pfr['miss']:
d2a170
+                data['pkgs_miss_count'] += size
d2a170
+                _add_size(data['pkgs_miss_size'], pfr['miss'][fpath], size)
d2a170
+            elif fpath in pfr['mod']:
d2a170
+                data['pkgs_mod_count'] += size
d2a170
+                _add_size(data['pkgs_mod_size'], pfr['mod'][fpath], size)
d2a170
+            elif need_prefix and False:
d2a170
+                for fpre_path in _dir_prefixes(fpath):
d2a170
+                    if fpre_path not in pkg_files:
d2a170
+                        continue
d2a170
+                    _add_size(data['pres_size'], pkg_files[fpre_path], size)
d2a170
+                    break
d2a170
+                data['data_count'] += size
d2a170
+                data['data_size'][fpath] = size
d2a170
+            else:
d2a170
+                data['data_count'] += size
d2a170
+                data['data_size'][fpath] = size
d2a170
+
d2a170
+        prefix = "."
d2a170
+        if extcmds:
d2a170
+            prefix = extcmds[0]
d2a170
+            extcmds = extcmds[1:]
d2a170
+
d2a170
+        if not os.path.exists(prefix):
d2a170
+            return 1, [_('No such file or directory: ' + prefix)]
d2a170
+
d2a170
+        max_show_len = 4
d2a170
+        if extcmds:
d2a170
+            try:
d2a170
+                max_show_len = int(extcmds[0])
d2a170
+            except:
d2a170
+                pass
d2a170
+
d2a170
+        verbose = base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3)
d2a170
+
d2a170
+        pfr = self._fs_pkg_walk(base.rpmdb, prefix, verbose=verbose)
d2a170
+
d2a170
+        base.closeRpmDB() # C-c ftw.
d2a170
+
d2a170
+        num = 0
d2a170
+        if os.path.isfile(prefix):
d2a170
+            num += 1
d2a170
+            deal_with_file(prefix)
d2a170
+
d2a170
+        for root, dirs, files in os.walk(prefix):
d2a170
+            for fname in files:
d2a170
+                num += 1
d2a170
+                fpath = os.path.normpath(root + '/' + fname)
d2a170
+                if os.path.islink(fpath):
d2a170
+                    continue
d2a170
+
d2a170
+                deal_with_file(fpath, need_prefix=verbose)
d2a170
+
d2a170
+        # output
d2a170
+        print "Files            :", loc_num(num)
d2a170
+        tot = 0
d2a170
+        tot += data['pkgs_count']
d2a170
+        tot += data['pkgs_ghost_count']
d2a170
+        tot += data['pkgs_not_count']
d2a170
+        tot += data['pkgs_miss_count']
d2a170
+        tot += data['pkgs_mod_count']
d2a170
+        tot += data['data_count']
d2a170
+        print "Total size       :", base.format_number(tot)
d2a170
+        if not tot:
d2a170
+            return
d2a170
+
d2a170
+        num = data['pkgs_count']
d2a170
+        if not verbose:
d2a170
+            num += data['pkgs_ghost_count']
d2a170
+            num += data['pkgs_miss_count']
d2a170
+            num += data['pkgs_mod_count']
d2a170
+        print "       Pkgs size :", "%-5s" % base.format_number(num),
d2a170
+        print "(%3.0f%%)" % ((num * 100.0) / tot)
d2a170
+        if verbose:
d2a170
+            for (title, num) in ((_(" Ghost pkgs size :"),
d2a170
+                                  data['pkgs_ghost_count']),
d2a170
+                                 (_(" Not pkgs size :"),
d2a170
+                                  data['pkgs_not_count']),
d2a170
+                                 (_(" Miss pkgs size :"),
d2a170
+                                  data['pkgs_miss_count']),
d2a170
+                                 (_(" Mod. pkgs size :"),
d2a170
+                                  data['pkgs_mod_count'])):
d2a170
+                if not num:
d2a170
+                    continue
d2a170
+                print title, "%-5s" % base.format_number(num),
d2a170
+                print "(%3.0f%%)" % ((num * 100.0) / tot)
d2a170
+        num = data['data_count']
d2a170
+        if not verbose:
d2a170
+            num += data['pkgs_not_count']
d2a170
+        print _("       Data size :"), "%-5s" % base.format_number(num),
d2a170
+        print "(%3.0f%%)" % ((num * 100.0) / tot)
d2a170
+        if verbose:
d2a170
+            print ''
d2a170
+            print _("Pkgs       :"), loc_num(len(data['pkgs_size']))
d2a170
+            print _("Ghost Pkgs :"), loc_num(len(data['pkgs_ghost_size']))
d2a170
+            print _("Not Pkgs   :"), loc_num(len(data['pkgs_not_size']))
d2a170
+            print _("Miss. Pkgs :"), loc_num(len(data['pkgs_miss_size']))
d2a170
+            print _("Mod. Pkgs  :"), loc_num(len(data['pkgs_mod_size']))
d2a170
+
d2a170
+        def _pkgs(p_size, msg):
d2a170
+            tot = min(max_show_len, len(p_size))
d2a170
+            if tot:
d2a170
+                print ''
d2a170
+                print msg % tot
d2a170
+            num = 0
d2a170
+            for pkg in sorted(p_size, key=lambda x: p_size[x], reverse=True):
d2a170
+                num += 1
d2a170
+                print _("%*d. %60s %-5s") % (len(str(tot)), num, pkg,
d2a170
+                                             base.format_number(p_size[pkg]))
d2a170
+                if num >= tot:
d2a170
+                    break
d2a170
+
d2a170
+        if verbose:
d2a170
+            _pkgs(data['pkgs_size'], _('Top %d packages:'))
d2a170
+            _pkgs(data['pkgs_ghost_size'], _('Top %d ghost packages:'))
d2a170
+            _pkgs(data['pkgs_not_size'], _('Top %d not. packages:'))
d2a170
+            _pkgs(data['pkgs_miss_size'], _('Top %d miss packages:'))
d2a170
+            _pkgs(data['pkgs_mod_size'], _('Top %d mod. packages:'))
d2a170
+            _pkgs(data['pres_size'], _('Top %d prefix packages:'))
d2a170
+        else:
d2a170
+            tmp = {}
d2a170
+            tmp.update(data['pkgs_size'])
d2a170
+            for d in data['pkgs_ghost_size']:
d2a170
+                _add_size(tmp, d, data['pkgs_ghost_size'][d])
d2a170
+            for d in data['pkgs_miss_size']:
d2a170
+                _add_size(tmp, d, data['pkgs_miss_size'][d])
d2a170
+            for d in data['pkgs_mod_size']:
d2a170
+                _add_size(tmp, d, data['pkgs_mod_size'][d])
d2a170
+            _pkgs(tmp, _('Top %d packages:'))
d2a170
+
d2a170
+        print ''
d2a170
+        if verbose:
d2a170
+            data_size = data['data_size']
d2a170
+        else:
d2a170
+            data_size = {}
d2a170
+            data_size.update(data['data_size'])
d2a170
+            data_size.update(data['data_not_size'])
d2a170
+
d2a170
+        tot = min(max_show_len, len(data_size))
d2a170
+        if tot:
d2a170
+            print _('Top %d non-package files:') % tot
d2a170
+        num = 0
d2a170
+        for fname in sorted(data_size,
d2a170
+                            key=lambda x: data_size[x],
d2a170
+                            reverse=True):
d2a170
+            num += 1
d2a170
+            dsznum = data_size[fname]
d2a170
+            print _("%*d. %60s %-5s") % (len(str(tot)), num, fname,
d2a170
+                                         base.format_number(dsznum))
d2a170
+            if num >= tot:
d2a170
+                break
d2a170
+
d2a170
+    def _fs_filters(self, base, extcmds):
d2a170
+        writeRawConfigFile = yum.config._writeRawConfigFile
d2a170
+
d2a170
+        if not extcmds:
d2a170
+            oil = base.conf.override_install_langs
d2a170
+            if not oil:
d2a170
+                oil = "rpm: " + rpm.expandMacro("%_install_langs")
d2a170
+            print "File system filters:"
d2a170
+            print "  Nodocs:", 'nodocs' in base.conf.tsflags
d2a170
+            print "  Languages:", oil
d2a170
+        elif extcmds[0] in ('docs', 'nodocs',
d2a170
+                            'documentation', 'nodocumentation'):
d2a170
+            c_f = 'nodocs' in base.conf.tsflags
d2a170
+            n_f = extcmds[0].startswith('no')
d2a170
+            if n_f == c_f:
d2a170
+                return
d2a170
+
d2a170
+            nts = base.conf.tsflags
d2a170
+            if n_f:
d2a170
+                nts = nts + ['nodocs']
d2a170
+            else:
d2a170
+                nts = [x for x in nts if x != 'nodocs']
d2a170
+            base.conf.tsflags = " ".join(nts)
d2a170
+
d2a170
+            fn = '/etc/yum/yum.conf'
d2a170
+            if not os.path.exists(fn):
d2a170
+                # Try the old default
d2a170
+                fn = '/etc/yum.conf'
d2a170
+            ybc = base.conf
d2a170
+            writeRawConfigFile(fn, 'main', ybc.yumvar,
d2a170
+                               ybc.cfg.options, ybc.iteritems,
d2a170
+                               ybc.optionobj,
d2a170
+                               only=['tsflags'])
d2a170
+        elif extcmds[0] in ('langs', 'nolangs', 'lang', 'nolang',
d2a170
+                            'languages', 'nolanguages',
d2a170
+                            'language', 'nolanguage'):
d2a170
+            if extcmds[0].startswith('no') or len(extcmds) < 2 or 'all' in extcmds:
d2a170
+                val = 'all'
d2a170
+            else:
d2a170
+                val = ":".join(extcmds[1:])
d2a170
+
d2a170
+            if val == base.conf.override_install_langs:
d2a170
+                return
d2a170
+
d2a170
+            base.conf.override_install_langs = val
d2a170
+
d2a170
+            fn = '/etc/yum/yum.conf'
d2a170
+            if not os.path.exists(fn):
d2a170
+                # Try the old default
d2a170
+                fn = '/etc/yum.conf'
d2a170
+            ybc = base.conf
d2a170
+            writeRawConfigFile(fn, 'main', ybc.yumvar,
d2a170
+                               ybc.cfg.options, ybc.iteritems,
d2a170
+                               ybc.optionobj,
d2a170
+                               only=['override_install_langs'])
d2a170
+        else:
d2a170
+            return 1, [_('Not a valid sub-command of fs filter')]
d2a170
+
d2a170
+    def _fs_refilter(self, base, extcmds):
d2a170
+        c_f = 'nodocs' in base.conf.tsflags
d2a170
+        # FIXME: C&P from init.
d2a170
+        oil = base.conf.override_install_langs
d2a170
+        if not oil:
d2a170
+            oil = rpm.expandMacro("%_install_langs")
d2a170
+        if oil == 'all':
d2a170
+            oil = ''
d2a170
+        elif oil:
d2a170
+            oil = ":".join(sorted(oil.split(':')))
d2a170
+
d2a170
+        found = False
d2a170
+        num = 0
d2a170
+        for pkg in base.rpmdb.returnPackages(patterns=extcmds):
d2a170
+            if False: pass
d2a170
+            elif oil != pkg.yumdb_info.get('ts_install_langs', ''):
d2a170
+                txmbrs = base.reinstall(po=pkg)
d2a170
+                num += len(txmbrs)
d2a170
+            elif c_f != ('true' == pkg.yumdb_info.get('tsflag_nodocs')):
d2a170
+                txmbrs = base.reinstall(po=pkg)
d2a170
+                num += len(txmbrs)
d2a170
+            else:
d2a170
+                found = True
d2a170
+
d2a170
+        if num:
d2a170
+            return 2,P_('%d package to reinstall','%d packages to reinstall',
d2a170
+                        num)
d2a170
+
d2a170
+        if not found:
d2a170
+            return 1, [_('No valid packages: %s') % " ".join(extcmds)]
d2a170
+
d2a170
+    def _fs_refilter_cleanup(self, base, extcmds):
d2a170
+        pkgs = base.rpmdb.returnPackages(patterns=extcmds)
d2a170
+
d2a170
+        verbose = base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3)
d2a170
+
d2a170
+        pfr = self._fs_pkg_walk(pkgs, "/", verbose=verbose, modified=True)
d2a170
+
d2a170
+        base.closeRpmDB() # C-c ftw.
d2a170
+
d2a170
+        for fname in sorted(pfr['not']):
d2a170
+            print _('Removing:'), fname
d2a170
+            misc.unlink_f(fname)
d2a170
+
d2a170
+    def _fs_diff(self, base, extcmds):
d2a170
+        pass
d2a170
+    def _fs_status(self, base, extcmds):
d2a170
+        pass
d2a170
+
d2a170
+    def doCommand(self, base, basecmd, extcmds):
d2a170
+        """Execute this command.
d2a170
+
d2a170
+        :param base: a :class:`yum.Yumbase` object
d2a170
+        :param basecmd: the name of the command
d2a170
+        :param extcmds: the command line arguments passed to *basecmd*
d2a170
+        :return: (exit_code, [ errors ])
d2a170
+
d2a170
+        exit_code is::
d2a170
+
d2a170
+            0 = we're done, exit
d2a170
+            1 = we've errored, exit with error string
d2a170
+            2 = we've got work yet to do, onto the next stage
d2a170
+        """
d2a170
+        if extcmds and extcmds[0] in ('filters', 'filter',
d2a170
+                                      'refilter', 'refilter-cleanup',
d2a170
+                                      'du', 'status', 'diff'):
d2a170
+            subcommand = extcmds[0]
d2a170
+            extcmds = extcmds[1:]
d2a170
+        else:
d2a170
+            subcommand = 'filters'
d2a170
+
d2a170
+        if False: pass
d2a170
+
d2a170
+        elif subcommand == 'du':
d2a170
+            ret = self._fs_du(base, extcmds)
d2a170
+
d2a170
+        elif subcommand in ('filter', 'filters'):
d2a170
+            ret = self._fs_filters(base, extcmds)
d2a170
+
d2a170
+        elif subcommand == 'refilter':
d2a170
+            ret = self._fs_refilter(base, extcmds)
d2a170
+
d2a170
+        elif subcommand == 'refilter-cleanup':
d2a170
+            ret = self._fs_refilter_cleanup(base, extcmds)
d2a170
+
d2a170
+        elif False and subcommand == 'diff':
d2a170
+            ret = self._fs_diff(base, extcmds)
d2a170
+
d2a170
+        elif False and subcommand == 'status':
d2a170
+            ret = self._fs_status(base, extcmds)
d2a170
+
d2a170
+        else:
d2a170
+            return 1, [_('Not a valid sub-command of %s') % basecmd]
d2a170
+
d2a170
+        if ret is not None:
d2a170
+            return ret
d2a170
+
d2a170
+        return 0, [basecmd + ' ' + subcommand + ' done']
d2a170
commit 5dd124173de6c9ac1ce53b507f9aaa9cae6d030a
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Wed Feb 12 16:56:33 2014 -0500
d2a170
d2a170
    Optimize pkg.verify() for "fs du" and "fs refilter".
d2a170
d2a170
diff --git a/yum/packages.py b/yum/packages.py
d2a170
index cc1f1e3..1b3061b 100644
d2a170
--- a/yum/packages.py
d2a170
+++ b/yum/packages.py
d2a170
@@ -1779,6 +1779,7 @@ class YUMVerifyPackage:
d2a170
         self._files = {}
d2a170
 
d2a170
 
d2a170
+_last_fnmatch = {}
d2a170
 class _RPMVerifyPackage(YUMVerifyPackage):
d2a170
     def __init__(self, po, fi, def_csum_type, patterns, all):
d2a170
         YUMVerifyPackage.__init__(self, po)
d2a170
@@ -1791,18 +1792,30 @@ class _RPMVerifyPackage(YUMVerifyPackage):
d2a170
         (fi, def_csum_type, patterns, all) = self._presetup
d2a170
         del self._presetup
d2a170
 
d2a170
+        global _last_fnmatch
d2a170
+        _this_fnmatch = {}
d2a170
         for ft in fi:
d2a170
             fn = ft[0]
d2a170
             if patterns:
d2a170
                 matched = False
d2a170
                 for p in patterns:
d2a170
-                    if fnmatch.fnmatch(fn, p):
d2a170
+                    if p in _last_fnmatch:
d2a170
+                        match = _last_fnmatch[p]
d2a170
+                    elif p in _this_fnmatch:
d2a170
+                        match = _this_fnmatch[p]
d2a170
+                    else:
d2a170
+                        match = misc.compile_pattern(p)
d2a170
+                    _this_fnmatch[p] = match
d2a170
+
d2a170
+                    if match(fn):
d2a170
                         matched = True
d2a170
                         break
d2a170
                 if not matched: 
d2a170
                     continue
d2a170
 
d2a170
             self.add(_RPMVerifyPackageFile(fi, ft, def_csum_type, all))
d2a170
+        if _this_fnmatch:
d2a170
+            _last_fnmatch = _this_fnmatch
d2a170
 
d2a170
     def __contains__(self, *args, **kwargs):
d2a170
         self._setup()
d2a170
@@ -1834,7 +1847,8 @@ class YumInstalledPackage(YumHeaderPackage):
d2a170
             self.yumdb_info = yumdb.get_package(self)
d2a170
 
d2a170
     def verify(self, patterns=[], deps=False, script=False,
d2a170
-               fake_problems=True, all=False, fast=False, callback=None):
d2a170
+               fake_problems=True, all=False, fast=False, callback=None,
d2a170
+               failfast=False):
d2a170
         """verify that the installed files match the packaged checksum
d2a170
            optionally verify they match only if they are in the 'pattern' list
d2a170
            returns a tuple """
d2a170
@@ -1973,6 +1987,8 @@ class YumInstalledPackage(YumHeaderPackage):
d2a170
                 verify_digest = pf.verify_digest
d2a170
                 if fast and not problems and (my_st_size == pf.size):
d2a170
                     verify_digest = False
d2a170
+                if failfast and problems:
d2a170
+                    verify_digest = False
d2a170
                 if not pf.digest:
d2a170
                     verify_digest = False
d2a170
 
d2a170
diff --git a/yumcommands.py b/yumcommands.py
d2a170
index 03450c4..c5abfba 100644
d2a170
--- a/yumcommands.py
d2a170
+++ b/yumcommands.py
d2a170
@@ -4374,7 +4374,7 @@ class FSCommand(YumCommand):
d2a170
             for pkg in pkgs:
d2a170
                 _pfs = []
d2a170
                 probs = pkg.verify(patterns=[prefix+'*'], fake_problems=False,
d2a170
-                                   callback=scoop_pfs)
d2a170
+                                   callback=scoop_pfs, failfast=True)
d2a170
 
d2a170
                 for pf in _pfs[0]:
d2a170
                     if pf.filename in probs:
d2a170
commit 6e4a68cf40283f3a110c049d5e81db886ef593e1
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Wed Feb 12 17:56:42 2014 -0500
d2a170
d2a170
    Add the fs status/diff commands.
d2a170
d2a170
diff --git a/docs/yum.8 b/docs/yum.8
d2a170
index 31c1c88..6794581 100644
d2a170
--- a/docs/yum.8
d2a170
+++ b/docs/yum.8
d2a170
@@ -742,12 +742,19 @@ mainly for removing languages/documentation for minimal installs:
d2a170
 .br 
d2a170
 .I \fR yum fs du [path]
d2a170
 
d2a170
+.br 
d2a170
+.I \fR yum fs status [path]
d2a170
+
d2a170
+.br 
d2a170
+.I \fR yum fs diff [path]
d2a170
+
d2a170
+
d2a170
 the first 3 being a simple interface to change yum.conf altering the tsflags
d2a170
 and override_install_langs configurations. The refilter command is an optimized
d2a170
 way of calling "yum reinstall" to reinstall the packages with the new filters
d2a170
 applied. The refilter-cleanup command is needed because rpm doesn't actually
d2a170
-remove the files on reinstall, as it should. And the du command is included so
d2a170
-you can easily see the space used/saved.
d2a170
+remove the files on reinstall, as it should. And the du/status/diff commands are
d2a170
+included so you can easily see the space used/saved and any other changes.
d2a170
 
d2a170
 .IP
d2a170
 .IP "\fBcheck\fP"
d2a170
diff --git a/yumcommands.py b/yumcommands.py
d2a170
index c5abfba..b4e172c 100644
d2a170
--- a/yumcommands.py
d2a170
+++ b/yumcommands.py
d2a170
@@ -21,6 +21,7 @@ Classes for subcommands of the yum command line interface.
d2a170
 """
d2a170
 
d2a170
 import os
d2a170
+import sys
d2a170
 import cli
d2a170
 from yum import logginglevels
d2a170
 from yum import _, P_
d2a170
@@ -4692,9 +4693,112 @@ class FSCommand(YumCommand):
d2a170
             misc.unlink_f(fname)
d2a170
 
d2a170
     def _fs_diff(self, base, extcmds):
d2a170
-        pass
d2a170
+        def deal_with_file(fpath):
d2a170
+            if fpath in pfr['norm']:
d2a170
+                pass
d2a170
+            elif fpath in pfr['ghost']:
d2a170
+                pass
d2a170
+            elif fpath in pfr['not']:
d2a170
+                print >>sys.stderr, _('Not installed:'), fpath
d2a170
+            elif fpath in pfr['miss']:
d2a170
+                pass
d2a170
+            elif fpath in pfr['mod']:
d2a170
+                pkg = apkgs[pfr['mod'][fpath].pkgtup]
d2a170
+                # Hacky ... but works.
d2a170
+                sys.stdout.flush()
d2a170
+                extract_cmd = "cd %s; rpm2cpio %s | cpio --quiet -id .%s"
d2a170
+                extract_cmd =  extract_cmd % (tmpdir, pkg.localPkg(), fpath)
d2a170
+                os.system(extract_cmd)
d2a170
+                diff_cmd = "diff -ru %s %s" % (tmpdir + fpath, fpath)
d2a170
+                print diff_cmd
d2a170
+                sys.stdout.flush()
d2a170
+                os.system(diff_cmd)
d2a170
+            else:
d2a170
+                print >>sys.stderr, _('Not packaged?:'), fpath
d2a170
+
d2a170
+        prefix = "."
d2a170
+        if extcmds:
d2a170
+            prefix = extcmds[0]
d2a170
+            extcmds = extcmds[1:]
d2a170
+
d2a170
+        pkgs = base.rpmdb.returnPackages(patterns=extcmds)
d2a170
+
d2a170
+        verbose = base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3)
d2a170
+
d2a170
+        pfr = self._fs_pkg_walk(pkgs, prefix, verbose=verbose, modified=True)
d2a170
+
d2a170
+        base.closeRpmDB() # C-c ftw.
d2a170
+
d2a170
+        apkgs = {}
d2a170
+        downloadpkgs = []
d2a170
+        for ipkg in set(pfr['mod'].values()):
d2a170
+            for apkg in base.pkgSack.searchPkgTuple(ipkg.pkgtup):
d2a170
+                iyi = ipkg.yumdb_info
d2a170
+                if ('checksum_type' in iyi and
d2a170
+                    'checksum_data' in iyi and
d2a170
+                    iyi.checksum_type == apkg.checksum_type and
d2a170
+                    iyi.checksum_data == apkg.pkgId):
d2a170
+                    apkgs[ipkg.pkgtup] = apkg
d2a170
+                    downloadpkgs.append(apkg)
d2a170
+                    break
d2a170
+            if ipkg.pkgtup not in apkgs:
d2a170
+                raise yum.Errors.YumBaseError, _("Can't find package: %s") %ipkg
d2a170
+
d2a170
+        if downloadpkgs:
d2a170
+            tmpdir = tempfile.mkdtemp()
d2a170
+            problems = base.downloadPkgs(downloadpkgs, callback_total=base.download_callback_total_cb) 
d2a170
+            if len(problems) > 0:
d2a170
+                errstring = ''
d2a170
+                errstring += _('Error downloading packages:\n')
d2a170
+                for key in problems:
d2a170
+                    errors = yum.misc.unique(problems[key])
d2a170
+                    for error in errors:
d2a170
+                        errstring += '  %s: %s\n' % (key, error)
d2a170
+                raise yum.Errors.YumBaseError, errstring
d2a170
+
d2a170
+        for root, dirs, files in os.walk(prefix):
d2a170
+            for fname in files:
d2a170
+                fpath = os.path.normpath(root + '/' + fname)
d2a170
+                if os.path.islink(fpath):
d2a170
+                    continue
d2a170
+
d2a170
+                deal_with_file(fpath)
d2a170
+
d2a170
     def _fs_status(self, base, extcmds):
d2a170
-        pass
d2a170
+        def deal_with_file(fpath):
d2a170
+            if fpath in pfr['norm']:
d2a170
+                pass
d2a170
+            elif fpath in pfr['ghost']:
d2a170
+                pass
d2a170
+            elif fpath in pfr['not']:
d2a170
+                print _('Not installed:'), fpath
d2a170
+            elif fpath in pfr['miss']:
d2a170
+                pass
d2a170
+            elif fpath in pfr['mod']:
d2a170
+                print _('Modified:'), fpath
d2a170
+            else:
d2a170
+                print _('Not packaged?:'), fpath
d2a170
+
d2a170
+        prefix = "."
d2a170
+        if extcmds:
d2a170
+            prefix = extcmds[0]
d2a170
+            extcmds = extcmds[1:]
d2a170
+
d2a170
+        pkgs = base.rpmdb.returnPackages(patterns=extcmds)
d2a170
+
d2a170
+        verbose = base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3)
d2a170
+
d2a170
+        pfr = self._fs_pkg_walk(pkgs, prefix, verbose=verbose, modified=True)
d2a170
+
d2a170
+        base.closeRpmDB() # C-c ftw.
d2a170
+
d2a170
+        for root, dirs, files in os.walk(prefix):
d2a170
+            for fname in files:
d2a170
+                fpath = os.path.normpath(root + '/' + fname)
d2a170
+                if os.path.islink(fpath):
d2a170
+                    continue
d2a170
+
d2a170
+                deal_with_file(fpath)
d2a170
 
d2a170
     def doCommand(self, base, basecmd, extcmds):
d2a170
         """Execute this command.
d2a170
@@ -4732,10 +4836,10 @@ class FSCommand(YumCommand):
d2a170
         elif subcommand == 'refilter-cleanup':
d2a170
             ret = self._fs_refilter_cleanup(base, extcmds)
d2a170
 
d2a170
-        elif False and subcommand == 'diff':
d2a170
+        elif subcommand == 'diff':
d2a170
             ret = self._fs_diff(base, extcmds)
d2a170
 
d2a170
-        elif False and subcommand == 'status':
d2a170
+        elif subcommand == 'status':
d2a170
             ret = self._fs_status(base, extcmds)
d2a170
 
d2a170
         else:
d2a170
commit df42263f375b032b73d9732a8b051e57a35973ab
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Wed Feb 12 18:17:31 2014 -0500
d2a170
d2a170
    Move exactarchlist_default to ditro. variable.
d2a170
d2a170
diff --git a/yum/config.py b/yum/config.py
d2a170
index f213fc1..69f8e2e 100644
d2a170
--- a/yum/config.py
d2a170
+++ b/yum/config.py
d2a170
@@ -50,6 +50,10 @@ __main_multilib_policy_default__ = 'all'
d2a170
 __main_failovermethod_default__ = 'roundrobin'
d2a170
 __main_installonly_limit_default__ = 0
d2a170
 __group_command_default__ = 'compat'
d2a170
+__exactarchlist_default__ = ['kernel', 'kernel-smp',
d2a170
+                             'kernel-hugemem', 'kernel-enterprise',
d2a170
+                             'kernel-bigmem',
d2a170
+                             'kernel-devel', 'kernel-PAE', 'kernel-PAE-debug']
d2a170
 
d2a170
 class Option(object):
d2a170
     """
d2a170
@@ -764,9 +768,7 @@ class YumConf(StartupConf):
d2a170
                                           names_of_0=["0", "<off>"])
d2a170
     kernelpkgnames = ListOption(['kernel','kernel-smp', 'kernel-enterprise',
d2a170
             'kernel-bigmem', 'kernel-BOOT', 'kernel-PAE', 'kernel-PAE-debug'])
d2a170
-    exactarchlist = ListOption(['kernel', 'kernel-smp',
d2a170
-            'kernel-hugemem', 'kernel-enterprise', 'kernel-bigmem',
d2a170
-            'kernel-devel', 'kernel-PAE', 'kernel-PAE-debug'])
d2a170
+    exactarchlist = ListOption(__exactarchlist_default__)
d2a170
     tsflags = ListOption()
d2a170
     override_install_langs = Option()
d2a170
 
d2a170
commit 7e890388473519d8374e228783150f4e11dff3e0
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Wed Feb 12 18:59:42 2014 -0500
d2a170
d2a170
    Add override_install_langs to test.FakeConf.
d2a170
d2a170
diff --git a/test/testbase.py b/test/testbase.py
d2a170
index 9d331c6..e2a1d05 100644
d2a170
--- a/test/testbase.py
d2a170
+++ b/test/testbase.py
d2a170
@@ -66,6 +66,7 @@ class FakeConf(object):
d2a170
         self.reposdir = '/tmp/XXXX'
d2a170
         self.diskspacecheck = True
d2a170
         self.depsolve_loop_limit = 10
d2a170
+        self.override_install_langs = ''
d2a170
 
d2a170
 class FakeSack:
d2a170
     """ Fake PackageSack to use with FakeRepository"""
d2a170
commit 57e62d0ab105727689a2cbc690ff5d62137be676
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Fri Feb 14 14:58:31 2014 -0500
d2a170
d2a170
    Auto-enable repos. that contain packages we need in fs diff.
d2a170
d2a170
diff --git a/yumcommands.py b/yumcommands.py
d2a170
index b4e172c..475d982 100644
d2a170
--- a/yumcommands.py
d2a170
+++ b/yumcommands.py
d2a170
@@ -4732,8 +4732,14 @@ class FSCommand(YumCommand):
d2a170
         apkgs = {}
d2a170
         downloadpkgs = []
d2a170
         for ipkg in set(pfr['mod'].values()):
d2a170
+            iyi = ipkg.yumdb_info
d2a170
+            if 'from_repo' in iyi: # Updates-testing etc.
d2a170
+                if iyi.from_repo in base.repos.repos:
d2a170
+                    repo = base.repos.getRepo(iyi.from_repo)
d2a170
+                    if not repo.isEnabled():
d2a170
+                        base.repos.enableRepo(repo.id)
d2a170
+
d2a170
             for apkg in base.pkgSack.searchPkgTuple(ipkg.pkgtup):
d2a170
-                iyi = ipkg.yumdb_info
d2a170
                 if ('checksum_type' in iyi and
d2a170
                     'checksum_data' in iyi and
d2a170
                     iyi.checksum_type == apkg.checksum_type and
d2a170
commit 81b9dc2fc39ed6960f061cdc1c84f37aada0fd0a
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Fri Feb 14 15:02:58 2014 -0500
d2a170
d2a170
    Cleanup tmpdir for fs diff.
d2a170
d2a170
diff --git a/yumcommands.py b/yumcommands.py
d2a170
index 475d982..c93faa1 100644
d2a170
--- a/yumcommands.py
d2a170
+++ b/yumcommands.py
d2a170
@@ -33,6 +33,7 @@ import fnmatch
d2a170
 import time
d2a170
 from yum.i18n import utf8_width, utf8_width_fill, to_unicode, exception2msg
d2a170
 import tempfile
d2a170
+import shutil
d2a170
 import glob
d2a170
 
d2a170
 import yum.config
d2a170
@@ -4770,6 +4771,9 @@ class FSCommand(YumCommand):
d2a170
 
d2a170
                 deal_with_file(fpath)
d2a170
 
d2a170
+        if downloadpkgs:
d2a170
+            shutil.rmtree(tmpdir)
d2a170
+
d2a170
     def _fs_status(self, base, extcmds):
d2a170
         def deal_with_file(fpath):
d2a170
             if fpath in pfr['norm']:
d2a170
commit f932ddc40d8452c1a7d46d7c7fd8eb90b6f5cac2
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Fri Feb 21 16:15:01 2014 -0500
d2a170
d2a170
    Add spec requires for fs sub-command.
d2a170
d2a170
diff --git a/yum.spec b/yum.spec
d2a170
index 93cfa14..854baf3 100644
d2a170
--- a/yum.spec
d2a170
+++ b/yum.spec
d2a170
@@ -98,6 +98,9 @@ Requires: pygpgme
d2a170
 Requires: pyliblzma
d2a170
 # Not really a suggests anymore, due to metadata using it.
d2a170
 Requires: pyxattr
d2a170
+# Suggests, needed for yum fs diff
d2a170
+Requires: diffutils
d2a170
+Requires: cpio
d2a170
 
d2a170
 Conflicts: rpm >= 5-0
d2a170
 # Zif is a re-implementation of yum in C, however:
d2a170
commit 00aec000813db1b45a7b38e1aa396b1bb8764eb7
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Fri Feb 21 16:19:28 2014 -0500
d2a170
d2a170
    Copy packages in/out of an installroot, for no downloads creating containers.
d2a170
d2a170
diff --git a/yum/__init__.py b/yum/__init__.py
d2a170
index 37ab468..dc468cb 100644
d2a170
--- a/yum/__init__.py
d2a170
+++ b/yum/__init__.py
d2a170
@@ -45,6 +45,7 @@ import logging
d2a170
 import logging.config
d2a170
 import operator
d2a170
 import tempfile
d2a170
+import shutil
d2a170
 
d2a170
 import yum.i18n
d2a170
 # This is required to make gaftonmode work...
d2a170
@@ -689,6 +690,12 @@ class YumBase(depsolve.Depsolve):
d2a170
         if hasattr(self, 'prerepoconf'):
d2a170
             self.conf # touch the config class first
d2a170
 
d2a170
+            if (self.conf.installroot != '/' and
d2a170
+                not hasattr(self, '_old_cachedir')):
d2a170
+                # Try loading cache from outside...
d2a170
+                ir = len(self.conf.installroot)
d2a170
+                self._old_cachedir = self.conf.cachedir[ir:]
d2a170
+
d2a170
             self.getReposFromConfig()
d2a170
 
d2a170
         #  For rhnplugin, and in theory other stuff, calling
d2a170
@@ -2398,6 +2405,9 @@ much more problems).
d2a170
                 self.verbose_logger.warn(_("ignoring a dupe of %s") % po)
d2a170
                 return True
d2a170
             beenthere.add(local)
d2a170
+            if downloadonly and not os.path.exists(local):
d2a170
+              # Check before we munge the name...
d2a170
+              po.repo._preload_pkg_from_system_cache(po)
d2a170
             if os.path.exists(local):
d2a170
                 if self.verifyPkg(local, po, False):
d2a170
                     self.verbose_logger.debug(_("using local copy of %s") % po)
d2a170
@@ -2442,6 +2452,22 @@ much more problems).
d2a170
                 format_number(rpmsize), format_number(deltasize), 100 - deltasize*100.0/rpmsize)
d2a170
 
d2a170
         if downloadonly:
d2a170
+            if hasattr(self, '_old_cachedir'):
d2a170
+              # Try to link/copy them out, if we have somewhere to put them.
d2a170
+
d2a170
+              for po in pkglist:
d2a170
+                if not po.localpath.startswith(self.conf.cachedir):
d2a170
+                  continue
d2a170
+
d2a170
+                end = po.localpath[len(self.conf.cachedir):]
d2a170
+                try:
d2a170
+                  os.link(po.localpath, self._old_cachedir + end)
d2a170
+                except:
d2a170
+                  try:
d2a170
+                    shutil.copy2(po.localpath, self._old_cachedir + end)
d2a170
+                  except:
d2a170
+                    pass
d2a170
+
d2a170
             # close DBs, unlock
d2a170
             self.repos.close()
d2a170
             self.closeRpmDB()
d2a170
commit fcec4d88fa18c30e1aeabf724bec11dcfb1e2655
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Fri Feb 21 16:16:23 2014 -0500
d2a170
d2a170
     A few cleanups for the fs sub-command:
d2a170
    
d2a170
    . Add checks for diff/cpio/rpm2cpio.
d2a170
    . Add missing import for rpm, when override_install_langs isn't set.
d2a170
    . Allow users to run some of the commands.
d2a170
    . Map fs snap to fssnap command.
d2a170
    . Add translations and add messages when you alter the filters.
d2a170
    . Save config. file inside the chroot.
d2a170
d2a170
diff --git a/yumcommands.py b/yumcommands.py
d2a170
index 52b8c90..4385a34 100644
d2a170
--- a/yumcommands.py
d2a170
+++ b/yumcommands.py
d2a170
@@ -23,6 +23,7 @@ Classes for subcommands of the yum command line interface.
d2a170
 import os
d2a170
 import sys
d2a170
 import cli
d2a170
+import rpm
d2a170
 from yum import logginglevels
d2a170
 from yum import _, P_
d2a170
 from yum import misc
d2a170
@@ -34,6 +35,7 @@ import time
d2a170
 from yum.i18n import utf8_width, utf8_width_fill, to_unicode, exception2msg
d2a170
 import tempfile
d2a170
 import shutil
d2a170
+import distutils.spawn
d2a170
 import glob
d2a170
 
d2a170
 import yum.config
d2a170
@@ -4334,6 +4336,14 @@ class FSCommand(YumCommand):
d2a170
         :param basecmd: the name of the command
d2a170
         :param extcmds: the command line arguments passed to *basecmd*
d2a170
         """
d2a170
+        if extcmds and extcmds[0] in ('du', 'status', 'diff'):
d2a170
+            # Anyone can go for it...
d2a170
+            return
d2a170
+
d2a170
+        if len(extcmds) == 1 and extcmds[0] in ('filters', 'filter'):
d2a170
+            # Can look, but not touch.
d2a170
+            return
d2a170
+
d2a170
         checkRootUID(base)
d2a170
 
d2a170
     def _fs_pkg_walk(self, pkgs, prefix, modified=False, verbose=False):
d2a170
@@ -4611,22 +4621,48 @@ class FSCommand(YumCommand):
d2a170
                 break
d2a170
 
d2a170
     def _fs_filters(self, base, extcmds):
d2a170
-        writeRawConfigFile = yum.config._writeRawConfigFile
d2a170
+        def _save(confkey):
d2a170
+            writeRawConfigFile = yum.config._writeRawConfigFile
d2a170
+
d2a170
+            # Always create installroot, so we can change it.
d2a170
+            if not os.path.exists(base.conf.installroot + '/etc/yum'):
d2a170
+                os.makedirs(base.conf.installroot + '/etc/yum')
d2a170
+
d2a170
+            fn = base.conf.installroot+'/etc/yum/yum.conf'
d2a170
+            if not os.path.exists(fn):
d2a170
+                # Try the old default
d2a170
+                nfn = base.conf.installroot+'/etc/yum.conf'
d2a170
+                if not os.path.exists(nfn):
d2a170
+                    shutil.copy2(base.conf.config_file_path, fn)
d2a170
+            ybc = base.conf
d2a170
+            writeRawConfigFile(fn, 'main', ybc.yumvar,
d2a170
+                               ybc.cfg.options, ybc.iteritems,
d2a170
+                               ybc.optionobj,
d2a170
+                               only=[confkey])
d2a170
 
d2a170
         if not extcmds:
d2a170
             oil = base.conf.override_install_langs
d2a170
             if not oil:
d2a170
                 oil = "rpm: " + rpm.expandMacro("%_install_langs")
d2a170
-            print "File system filters:"
d2a170
-            print "  Nodocs:", 'nodocs' in base.conf.tsflags
d2a170
-            print "  Languages:", oil
d2a170
+            print _("File system filters:")
d2a170
+            print _("  Nodocs:"), 'nodocs' in base.conf.tsflags
d2a170
+            print _("  Languages:"), oil
d2a170
         elif extcmds[0] in ('docs', 'nodocs',
d2a170
                             'documentation', 'nodocumentation'):
d2a170
             c_f = 'nodocs' in base.conf.tsflags
d2a170
-            n_f = extcmds[0].startswith('no')
d2a170
+            n_f = not extcmds[0].startswith('no')
d2a170
             if n_f == c_f:
d2a170
+                if n_f:
d2a170
+                    print _("Already enabled documentation filter.")
d2a170
+                else:
d2a170
+                    print _("Already disabled documentation filter.")
d2a170
                 return
d2a170
 
d2a170
+            if n_f:
d2a170
+                print _("Enabling documentation filter.")
d2a170
+            else:
d2a170
+                print _("Disabling documentation filter.")
d2a170
+
d2a170
             nts = base.conf.tsflags
d2a170
             if n_f:
d2a170
                 nts = nts + ['nodocs']
d2a170
@@ -4634,15 +4670,8 @@ class FSCommand(YumCommand):
d2a170
                 nts = [x for x in nts if x != 'nodocs']
d2a170
             base.conf.tsflags = " ".join(nts)
d2a170
 
d2a170
-            fn = '/etc/yum/yum.conf'
d2a170
-            if not os.path.exists(fn):
d2a170
-                # Try the old default
d2a170
-                fn = '/etc/yum.conf'
d2a170
-            ybc = base.conf
d2a170
-            writeRawConfigFile(fn, 'main', ybc.yumvar,
d2a170
-                               ybc.cfg.options, ybc.iteritems,
d2a170
-                               ybc.optionobj,
d2a170
-                               only=['tsflags'])
d2a170
+            _save('tsflags')
d2a170
+
d2a170
         elif extcmds[0] in ('langs', 'nolangs', 'lang', 'nolang',
d2a170
                             'languages', 'nolanguages',
d2a170
                             'language', 'nolanguage'):
d2a170
@@ -4652,19 +4681,21 @@ class FSCommand(YumCommand):
d2a170
                 val = ":".join(extcmds[1:])
d2a170
 
d2a170
             if val == base.conf.override_install_langs:
d2a170
+                if val:
d2a170
+                    print _("Already filtering languages to: %s") % val
d2a170
+                else:
d2a170
+                    print _("Already disabled language filter.")
d2a170
                 return
d2a170
 
d2a170
+            if val:
d2a170
+                print _("Setting language filter to: %s") % val
d2a170
+            else:
d2a170
+                print _("Disabling language filter.")
d2a170
+
d2a170
             base.conf.override_install_langs = val
d2a170
 
d2a170
-            fn = '/etc/yum/yum.conf'
d2a170
-            if not os.path.exists(fn):
d2a170
-                # Try the old default
d2a170
-                fn = '/etc/yum.conf'
d2a170
-            ybc = base.conf
d2a170
-            writeRawConfigFile(fn, 'main', ybc.yumvar,
d2a170
-                               ybc.cfg.options, ybc.iteritems,
d2a170
-                               ybc.optionobj,
d2a170
-                               only=['override_install_langs'])
d2a170
+            _save('override_install_langs')
d2a170
+
d2a170
         else:
d2a170
             return 1, [_('Not a valid sub-command of fs filter')]
d2a170
 
d2a170
@@ -4736,6 +4767,14 @@ class FSCommand(YumCommand):
d2a170
             else:
d2a170
                 print >>sys.stderr, _('Not packaged?:'), fpath
d2a170
 
d2a170
+        if not distutils.spawn.find_executable("diff"):
d2a170
+            raise yum.Errors.YumBaseError, _("Can't find diff command")
d2a170
+        # These just shouldn't happen...
d2a170
+        if not distutils.spawn.find_executable("cpio"):
d2a170
+            raise yum.Errors.YumBaseError, _("Can't find cpio command")
d2a170
+        if not distutils.spawn.find_executable("rpm2cpio"):
d2a170
+            raise yum.Errors.YumBaseError, _("Can't find rpm2cpio command")
d2a170
+
d2a170
         prefix = "."
d2a170
         if extcmds:
d2a170
             prefix = extcmds[0]
d2a170
@@ -4845,7 +4884,7 @@ class FSCommand(YumCommand):
d2a170
         """
d2a170
         if extcmds and extcmds[0] in ('filters', 'filter',
d2a170
                                       'refilter', 'refilter-cleanup',
d2a170
-                                      'du', 'status', 'diff'):
d2a170
+                                      'du', 'status', 'diff', 'snap'):
d2a170
             subcommand = extcmds[0]
d2a170
             extcmds = extcmds[1:]
d2a170
         else:
d2a170
@@ -4871,6 +4910,9 @@ class FSCommand(YumCommand):
d2a170
         elif subcommand == 'status':
d2a170
             ret = self._fs_status(base, extcmds)
d2a170
 
d2a170
+        elif subcommand == 'snap':
d2a170
+            ret = FSSnapshotCommand().doCommand(base, 'fs snap', args)
d2a170
+
d2a170
         else:
d2a170
             return 1, [_('Not a valid sub-command of %s') % basecmd]
d2a170
 
d2a170
commit 77c85efcb09f0121d6a611d92e1fc6a237179656
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Sun Feb 23 20:57:06 2014 -0500
d2a170
d2a170
    Choose yum.conf correctly for fs filter saving.
d2a170
d2a170
diff --git a/yumcommands.py b/yumcommands.py
d2a170
index 4385a34..ef84c1f 100644
d2a170
--- a/yumcommands.py
d2a170
+++ b/yumcommands.py
d2a170
@@ -4632,7 +4632,9 @@ class FSCommand(YumCommand):
d2a170
             if not os.path.exists(fn):
d2a170
                 # Try the old default
d2a170
                 nfn = base.conf.installroot+'/etc/yum.conf'
d2a170
-                if not os.path.exists(nfn):
d2a170
+                if os.path.exists(nfn):
d2a170
+                    fn = nfn
d2a170
+                else:
d2a170
                     shutil.copy2(base.conf.config_file_path, fn)
d2a170
             ybc = base.conf
d2a170
             writeRawConfigFile(fn, 'main', ybc.yumvar,
d2a170
commit 02a2d73afe6ea19ae17cbab2192c1d7e12be5ec2
d2a170
Author: James Antill <james@and.org>
d2a170
Date:   Mon Mar 24 16:17:19 2014 -0400
d2a170
d2a170
    No error for refilter cleanup, rm dirs. and eat all errors. BZ 1062959.
d2a170
d2a170
diff --git a/yumcommands.py b/yumcommands.py
d2a170
index 75b3ce2..c76e192 100644
d2a170
--- a/yumcommands.py
d2a170
+++ b/yumcommands.py
d2a170
@@ -37,6 +37,7 @@ import tempfile
d2a170
 import shutil
d2a170
 import distutils.spawn
d2a170
 import glob
d2a170
+import errno
d2a170
 
d2a170
 import yum.config
d2a170
 from yum import updateinfo
d2a170
@@ -4742,7 +4743,16 @@ class FSCommand(YumCommand):
d2a170
 
d2a170
         for fname in sorted(pfr['not']):
d2a170
             print _('Removing:'), fname
d2a170
-            misc.unlink_f(fname)
d2a170
+            try: # Ignore everything, unlink_f() doesn't.
d2a170
+                os.unlink(fname)
d2a170
+            except OSError, e:
d2a170
+                if e.errno == errno.EISDIR:
d2a170
+                    try:
d2a170
+                        os.rmdir(fname)
d2a170
+                    except:
d2a170
+                        pass
d2a170
+            except:
d2a170
+                pass
d2a170
 
d2a170
     def _fs_diff(self, base, extcmds):
d2a170
         def deal_with_file(fpath):