[yum-commits] 17 commits - cli.py docs/yum.8 docs/yum.conf.5 output.py yumcommands.py yum/config.py yum/depsolve.py yum/igroups.py yum/__init__.py yummain.py yum/packages.py

James Antill james at osuosl.org
Wed Feb 8 20:46:16 UTC 2012


 cli.py          |   11 +-
 docs/yum.8      |   23 +++++
 docs/yum.conf.5 |   31 +++++++
 output.py       |   80 ++++++++++++++++--
 yum/__init__.py |  241 ++++++++++++++++++++++++++++++++++++++++++++++++--------
 yum/config.py   |    6 +
 yum/depsolve.py |   48 ++++++++++-
 yum/igroups.py  |  141 ++++++++++++++++++++++++++++++++
 yum/packages.py |   10 ++
 yumcommands.py  |   82 +++++++++++++++++--
 yummain.py      |    6 +
 11 files changed, 623 insertions(+), 56 deletions(-)

New commits:
commit c9f853880606564e028a7403a3580cdba2ac411f
Merge: 39c6147 182d2eb
Author: James Antill <james at and.org>
Date:   Wed Feb 8 15:46:07 2012 -0500

    Merge branch 'master' of ssh://yum.baseurl.org/srv/projects/yum/git/yum
    
    * 'master' of ssh://yum.baseurl.org/srv/projects/yum/git/yum:
      make --setopt take globs for the repo setopts - deal w/bug 755077

commit 39c6147be017d1114d92339a263e092c1a5c1a0e
Author: James Antill <james at and.org>
Date:   Tue Feb 7 16:01:27 2012 -0500

    Create "strong requires", and use that for recheck on upgrade. BZ 785690

diff --git a/yum/depsolve.py b/yum/depsolve.py
index 6b386c2..a8f9acb 100644
--- a/yum/depsolve.py
+++ b/yum/depsolve.py
@@ -1003,7 +1003,7 @@ class Depsolve(object):
         oldreqs = []
         if not self.conf.recheck_installed_requires:
             for oldpo in txmbr.updates:
-                oldreqs.extend(oldpo.returnPrco('requires'))
+                oldreqs.extend(oldpo.returnPrco('strong_requires'))
         oldreqs = set(oldreqs)
 
         ret = []
diff --git a/yum/packages.py b/yum/packages.py
index 6bc909e..8dec8bf 100644
--- a/yum/packages.py
+++ b/yum/packages.py
@@ -1409,6 +1409,12 @@ class YumHeaderPackage(YumAvailablePackage):
                 continue
 
             lst = hdr[getattr(rpm, 'RPMTAG_%sFLAGS' % tag)]
+            if tag == 'REQUIRE':
+                #  Rpm is a bit magic here, and if pkgA requires(pre/post): foo
+                # it will then let you remove foo _after_ pkgA has been
+                # installed. So we need to mark those deps. as "weak".
+                bits = rpm.RPMSENSE_SCRIPT_PRE | rpm.RPMSENSE_SCRIPT_POST
+                weakreqs = [bool(flag & bits) for flag in lst]
             flag = map(rpmUtils.miscutils.flagToString, lst)
             flag = map(misc.share_data, flag)
 
@@ -1419,6 +1425,10 @@ class YumHeaderPackage(YumAvailablePackage):
 
             prcotype = tag2prco[tag]
             self.prco[prcotype] = map(misc.share_data, zip(name,flag,vers))
+            if tag == 'REQUIRE':
+                weakreqs = zip(weakreqs, self.prco[prcotype])
+                strongreqs = [wreq[1] for wreq in weakreqs if not wreq[0]]
+                self.prco['strong_requires'] = strongreqs
     
     def tagByName(self, tag):
         warnings.warn("tagByName() will go away in a furture version of Yum.\n",
commit c527d54f84e9662a60dde6496265971f853426bb
Author: James Antill <james at and.org>
Date:   Tue Feb 7 14:22:42 2012 -0500

    Add a recheck_installed_requires config. option. Sort of helps BZ 785690.

diff --git a/docs/yum.conf.5 b/docs/yum.conf.5
index e766cb0..59bd779 100644
--- a/docs/yum.conf.5
+++ b/docs/yum.conf.5
@@ -647,6 +647,13 @@ installed dependencies and check for an update.
 Boolean (1, 0, True, False, yes,no) Defaults to False
 
 .IP
+\fBrecheck_installed_requires \fR
+When upgrading a package do we recheck any requirements that existed in the old
+package. Turning this on shouldn't do anything but slow yum depsolving down,
+however using rpm --nodeps etc. can break the rpmdb and then this will help.
+Boolean (1, 0, True, False, yes,no) Defaults to False
+
+.IP
 \fBreset_nice \fR
 If set to true then yum will try to reset the nice value to zero, before
 running an rpm transaction. Defaults to False.
diff --git a/yum/config.py b/yum/config.py
index 982c0c5..cdb0c2b 100644
--- a/yum/config.py
+++ b/yum/config.py
@@ -856,6 +856,9 @@ class YumConf(StartupConf):
                                          'commands'),
                                      mapper={'cmds'          : 'commands',
                                              'default' :'single-user-commands'})
+
+    recheck_installed_requires = BoolOption(False)
+
     _reposlist = []
 
     def dump(self):
diff --git a/yum/depsolve.py b/yum/depsolve.py
index de01582..6b386c2 100644
--- a/yum/depsolve.py
+++ b/yum/depsolve.py
@@ -998,9 +998,12 @@ class Depsolve(object):
 
         # if this is an update, we should check what the old
         # requires were to make things faster
+        #  Note that if the rpmdb is broken, this gets annoying. So we provide
+        # a way to turn it off.
         oldreqs = []
-        for oldpo in txmbr.updates:
-            oldreqs.extend(oldpo.returnPrco('requires'))
+        if not self.conf.recheck_installed_requires:
+            for oldpo in txmbr.updates:
+                oldreqs.extend(oldpo.returnPrco('requires'))
         oldreqs = set(oldreqs)
 
         ret = []
commit 0e4ae881c9bf88280eee4359e0e2dc302aabe471
Author: James Antill <james at and.org>
Date:   Tue Feb 7 14:18:51 2012 -0500

    Doc. updates for groups as objects.

diff --git a/docs/yum.8 b/docs/yum.8
index 6cc0411..527f6b9 100644
--- a/docs/yum.8
+++ b/docs/yum.8
@@ -231,6 +231,29 @@ yum-list-data plugins to get/use the data the other way around (i.e. what
 groups own packages need updating). If you pass the \-v option, to enable verbose
 mode, then the package names are matched against installed/available packages
 similar to the list command.
+
+"\fBgroup summary\fP" is used to give a quick summary of how many groups
+are installed and available.
+
+"\fBgroup mark\fP" and "\fBgroup unmark\fP" are used when groups are configured
+in group_command=objects mode. These commands then allow you to alter yum's idea
+of which groups are installed, and the packages that belong to them.
+
+"\fBgroup mark install\fP" mark the group as installed. When
+installed "\fByum upgrade\fP" and "\fByum group upgrade\fP" will installing new
+packages for the group.
+
+"\fBgroup mark remove\fP" the opposite of mark install.
+
+"\fBgroup mark packages\fP" takes a group id (which must be installed) and marks
+any given installed packages (which aren't members of a group) as members of
+the group. Note that the data from the repositories does not need to specify
+the packages as a member of the group.
+
+"\fBgroup mark packages-force\fP" works like mark packages, but doesn't care if
+the packages are already members of another group.
+
+"\fBgroup unmark packages\fP" remove a package as a member from any groups.
 .IP
 .IP "\fBshell\fP"
 Is used to enter the 'yum shell', when a filename is specified the contents of
diff --git a/docs/yum.conf.5 b/docs/yum.conf.5
index fffde2a..e766cb0 100644
--- a/docs/yum.conf.5
+++ b/docs/yum.conf.5
@@ -232,7 +232,9 @@ Default is: default, mandatory
 List of the following: simple, compat, objects. Tells yum what to do for
 group install/upgrade/remove commands.
 
-Simple acts like you did yum group cmd $(repoquery --group --list group).
+Simple acts like you did yum group cmd $(repoquery --group --list group), so
+it is vrery easy to reason about what will happen. Alas. this is often not what
+people want to happen.
 
 Compat. works much like simple, except that when you run "group upgrade" it
 actually runs "group install" (this means that you get any new packages added
commit fdce328a9869801c917a51012f0fd1d42f47c3a4
Author: James Antill <james at and.org>
Date:   Wed Feb 1 14:59:47 2012 -0500

     Search for other installed packages, for available lists in "all". BZ 786116.
    
     This is needed so we get available/old_available/reinstall correct, but
    we still can't put the other installed packages in the "install" list
    (because then list all foo-3* will show foo-2 packages, which is bad).
    This means that the colouring is still "wrong", but meh.

diff --git a/yum/__init__.py b/yum/__init__.py
index 0ace29d..537300b 100644
--- a/yum/__init__.py
+++ b/yum/__init__.py
@@ -2574,10 +2574,22 @@ class YumBase(depsolve.Depsolve):
                     key = (pkg.name, pkg.arch)
                     if pkg.pkgtup in dinst:
                         reinstall_available.append(pkg)
-                    elif key not in ndinst or pkg.verGT(ndinst[key]):
-                        available.append(pkg)
                     else:
-                        old_available.append(pkg)
+                        # if (self.allowedMultipleInstalls(pkg) or
+                        #     key not in ndinst):
+                        #  Might be because pattern specified a version, so
+                        # we need to do a search for name/arch to find any
+                        # installed. Alas. calling allowedMultipleInstalls()
+                        # is much slower than calling searchNevra(). *Sigh*
+                        ipkgs = self.rpmdb.searchNevra(pkg.name,
+                                                       arch=pkg.arch)
+                        if ipkgs:
+                            ndinst[key] = sorted(ipkgs)[-1]
+
+                        if key not in ndinst or pkg.verGT(ndinst[key]):
+                            available.append(pkg)
+                        else:
+                            old_available.append(pkg)
 
         # produce the updates list of tuples
         elif pkgnarrow == 'updates':
commit f146ca0d47f3a6dcb0a161833529008112f4af7c
Author: James Antill <james at and.org>
Date:   Wed Jan 25 13:03:08 2012 -0500

    Show versions for provides.

diff --git a/output.py b/output.py
index f21b790..37c14cf 100755
--- a/output.py
+++ b/output.py
@@ -1288,7 +1288,15 @@ class YumOutput:
                 item = self._enc(item)
                 can_overflow = False
             else:
-                key = _("Other       : ")
+                provs = []
+                for prov in po.provides:
+                    if prov[0] == item:
+                        provs.append(prov)
+                if provs:
+                    key = _("Provides    : ")
+                    item = yum.misc.prco_tuple_to_string(sorted(provs)[0])
+                else:
+                    key = _("Other       : ")
 
             if matchfor:
                 item = self._sub_highlight(item, highlight, matchfor,
commit 9bb8b6df8acd3907435841d9e4b69cf0d89ecfc1
Author: James Antill <james at and.org>
Date:   Tue Jan 24 15:11:03 2012 -0500

    Add documentation for group_command config.

diff --git a/docs/yum.conf.5 b/docs/yum.conf.5
index d6fe824..fffde2a 100644
--- a/docs/yum.conf.5
+++ b/docs/yum.conf.5
@@ -228,6 +228,28 @@ of packages in groups will be installed when 'groupinstall' is called.
 Default is: default, mandatory
 
 .IP
+\fBgroup_command\fR
+List of the following: simple, compat, objects. Tells yum what to do for
+group install/upgrade/remove commands.
+
+Simple acts like you did yum group cmd $(repoquery --group --list group).
+
+Compat. works much like simple, except that when you run "group upgrade" it
+actually runs "group install" (this means that you get any new packages added
+to the group, but you also get packages added that were there before and you
+didn't want).
+
+Objects makes groups act like a real object, seperate from the packages they
+contain. Yum keeps track of the groups you have installed, so "group upgrade"
+will install new packages for the group but not install old ones. It also knows
+about group members that are installed but weren't installed as part of the
+group, and won't remove those on "group remove".
+Running "yum upgrade" will also run "yum group upgrade" (thus. adding new
+packages for all groups).
+
+Default is: compat
+
+.IP
 \fBinstallroot \fR
 Specifies an alternative installroot, relative to which all packages will be
 installed. 
commit 22f165cebc2f1ee402c7699065866449bf1dc727
Author: James Antill <james at and.org>
Date:   Fri Jan 20 16:45:00 2012 -0500

    Fix bad indentation on merge fix.

diff --git a/yum/__init__.py b/yum/__init__.py
index cc64a90..0ace29d 100644
--- a/yum/__init__.py
+++ b/yum/__init__.py
@@ -4009,12 +4009,12 @@ class YumBase(depsolve.Depsolve):
                     self.verbose_logger.debug(_('Checking for virtual provide or file-provide for %s'), 
                         arg)
 
-                        mypkgs = self.pkgSack.searchProvides(arg)
-                        if not misc.re_glob(arg):
-                            mypkgs = self.bestPackagesFromList(mypkgs,
-                                                               single_name=True,
-                                                               req=arg)
-                        pkgs.extend(mypkgs)
+                    mypkgs = self.pkgSack.searchProvides(arg)
+                    if not misc.re_glob(arg):
+                        mypkgs = self.bestPackagesFromList(mypkgs,
+                                                           single_name=True,
+                                                           req=arg)
+                    pkgs.extend(mypkgs)
             else:
                 nevra_dict = self._nevra_kwarg_parse(kwargs)
 
commit 89929ac3f0ba73f254d663d17f7f4989271899e3
Author: James Antill <james at and.org>
Date:   Fri Jan 20 16:12:50 2012 -0500

    Add a config default entry for group_command, so we can change it easily.

diff --git a/yum/config.py b/yum/config.py
index 06dfeb0..982c0c5 100644
--- a/yum/config.py
+++ b/yum/config.py
@@ -48,6 +48,7 @@ __repo_gpgcheck_default__ = False
 __main_multilib_policy_default__ = 'all'
 __main_failovermethod_default__ = 'roundrobin'
 __main_installonly_limit_default__ = 0
+__group_command_default__ = 'compat'
 
 class Option(object):
     """
@@ -775,7 +776,8 @@ class YumConf(StartupConf):
     enable_group_conditionals = BoolOption(True)
     groupremove_leaf_only = BoolOption(False)
     group_package_types = ListOption(['mandatory', 'default'])
-    group_command = SelectionOption('compat', ('compat', 'objects', 'simple'))
+    group_command = SelectionOption(__group_command_default__,
+                                    ('compat', 'objects', 'simple'))
     
     timeout = FloatOption(30.0) # FIXME: Should use variation of SecondsOption
 
commit cb519289b4fb423ab023c689c640b2b41c73437a
Merge: 3f82ceb 85745b0
Author: James Antill <james at and.org>
Date:   Fri Jan 20 16:10:42 2012 -0500

    Merge install provides conflicts.

diff --cc yum/__init__.py
index bd30546,2634670..cc64a90
--- a/yum/__init__.py
+++ b/yum/__init__.py
@@@ -4009,21 -3851,12 +4009,12 @@@ class YumBase(depsolve.Depsolve)
                      self.verbose_logger.debug(_('Checking for virtual provide or file-provide for %s'), 
                          arg)
  
-                     try:
-                         mypkgs = self.returnPackagesByDep(arg)
-                     except yum.Errors.YumBaseError, e:
-                         self.logger.critical(_('No Match for argument: %s') % to_unicode(arg))
-                     else:
-                         # install MTA* == fail, because provides don't do globs
-                         # install /usr/kerberos/bin/* == success (and we want
-                         #                                all of the pkgs)
-                         if mypkgs and not misc.re_glob(arg):
 -                    mypkgs = self.pkgSack.searchProvides(arg)
 -                    if not misc.re_glob(arg):
 -                        # install /usr/kerberos/bin/* == want all pkgs
 -                        mypkgs = self.bestPackagesFromList(mypkgs, single_name=True)
 -                    pkgs.extend(mypkgs)
 -
++                        mypkgs = self.pkgSack.searchProvides(arg)
++                        if not misc.re_glob(arg):
 +                            mypkgs = self.bestPackagesFromList(mypkgs,
 +                                                               single_name=True,
 +                                                               req=arg)
-                         if mypkgs:
-                             pkgs.extend(mypkgs)
-                         
++                        pkgs.extend(mypkgs)
              else:
                  nevra_dict = self._nevra_kwarg_parse(kwargs)
  
commit 3f82ceb89f73c530db1c83e8b9d034245471e622
Author: James Antill <james at and.org>
Date:   Fri Jan 20 16:02:14 2012 -0500

    Forward port all the UI and core for installed groups.

diff --git a/cli.py b/cli.py
index ac9522b..919218c 100755
--- a/cli.py
+++ b/cli.py
@@ -1647,7 +1647,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput):
         
         return 0, []
         
-    def installGroups(self, grouplist):
+    def installGroups(self, grouplist, upgrade=False):
         """Mark the packages in the given groups for installation.
 
         :param grouplist: a list of names or wildcards specifying
@@ -1669,7 +1669,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput):
 
             
                 try:
-                    txmbrs = self.selectGroup(group.groupid)
+                    txmbrs = self.selectGroup(group.groupid, upgrade=upgrade)
                 except yum.Errors.GroupsError:
                     self.logger.critical(_('Warning: Group %s does not exist.'), group_string)
                     continue
diff --git a/output.py b/output.py
index 01f0c40..6e38868 100755
--- a/output.py
+++ b/output.py
@@ -1017,27 +1017,58 @@ class YumOutput:
         return ret
 
     def _calcDataPkgColumns(self, data, pkg_names, pkg_names2pkgs,
-                            indent='   '):
+                            indent='   ', igroup_data=None):
         for item in pkg_names:
             if item not in pkg_names2pkgs:
                 continue
             for (apkg, ipkg) in pkg_names2pkgs[item]:
                 pkg = ipkg or apkg
                 envra = utf8_width(str(pkg)) + utf8_width(indent)
+                if igroup_data:
+                    envra += 1
                 rid = len(pkg.ui_from_repo)
                 for (d, v) in (('envra', envra), ('rid', rid)):
                     data[d].setdefault(v, 0)
                     data[d][v] += 1
 
     def _displayPkgsFromNames(self, pkg_names, verbose, pkg_names2pkgs,
-                              indent='   ', columns=None):
+                              indent='   ', columns=None, igroup_data=None):
+
+        def _get_igrp_data(item, indent):
+            if not igroup_data:
+                return indent
+
+            assert item in igroup_data
+            if item not in igroup_data or igroup_data[item] == 'available':
+                indent += '+' # Group up/in will install i
+            elif igroup_data[item] == 'installed':
+                indent += '=' # Installed via. group
+            elif igroup_data[item] == 'blacklisted-installed':
+                if False: # Not sure it's worth listing these...
+                    return None # On the other hand, there's mark-packages
+                indent += ' ' # Installed, not via. group
+            else:
+                assert igroup_data[item] == 'blacklisted-available'
+                if False: # Not sure it's worth listing these...
+                    return None
+                indent += '-' # Not installed, and won't be
+            return indent
+
         if not verbose:
             for item in sorted(pkg_names):
-                print '%s%s' % (indent, item)
+                pindent = _get_igrp_data(item, indent)
+                if pindent is None:
+                    continue
+
+                print '%s%s' % (pindent, item)
         else:
             for item in sorted(pkg_names):
+                pindent = _get_igrp_data(item, indent)
+                if pindent is None:
+                    continue
+
                 if item not in pkg_names2pkgs:
-                    print '%s%s' % (indent, item)
+                    print '%s%s' % (pindent, item)
                     continue
                 for (apkg, ipkg) in sorted(pkg_names2pkgs[item],
                                            key=lambda x: x[1] or x[0]):
@@ -1048,7 +1079,7 @@ class YumOutput:
                     else:
                         highlight = False
                     self.simpleEnvraList(ipkg or apkg, ui_overflow=True,
-                                         indent=indent, highlight=highlight,
+                                         indent=pindent, highlight=highlight,
                                          columns=columns)
     
     def displayPkgsInGroups(self, group):
@@ -1061,9 +1092,25 @@ class YumOutput:
         verb = self.verbose_logger.isEnabledFor(logginglevels.DEBUG_3)
         if verb:
             print _(' Group-Id: %s') % to_unicode(group.groupid)
+
+        igroup_data = self._groupInstalledData(group)
+        igrp_only   = set()
+        for pkg_name in igroup_data:
+            if igroup_data[pkg_name] == 'installed':
+                igrp_only.add(pkg_name)
+        igrp_only.difference_update(group.packages)
+        all_pkgs = group.packages + list(igrp_only)
+
         pkg_names2pkgs = None
         if verb:
-            pkg_names2pkgs = self._group_names2aipkgs(group.packages)
+            pkg_names2pkgs = self._group_names2aipkgs(all_pkgs)
+        else:
+            pkg_names2pkgs = {}
+            for ipkg in self.rpmdb.searchNames(all_pkgs):
+                if ipkg.name not in pkg_names2pkgs:
+                    pkg_names2pkgs[ipkg.name] = []
+                pkg_names2pkgs[ipkg.name].append(ipkg)
+
         if group.ui_description:
             print _(' Description: %s') % to_unicode(group.ui_description)
         if group.langonly:
@@ -1077,7 +1124,8 @@ class YumOutput:
         if verb:
             data = {'envra' : {}, 'rid' : {}}
             for (section_name, pkg_names) in sections:
-                self._calcDataPkgColumns(data, pkg_names, pkg_names2pkgs)
+                self._calcDataPkgColumns(data, pkg_names, pkg_names2pkgs,
+                                         igroup_data=igroup_data)
             data = [data['envra'], data['rid']]
             columns = self.calcColumns(data)
             columns = (-columns[0], -columns[1])
@@ -1086,7 +1134,13 @@ class YumOutput:
             if len(pkg_names) > 0:
                 print section_name
                 self._displayPkgsFromNames(pkg_names, verb, pkg_names2pkgs,
-                                           columns=columns)
+                                           columns=columns,
+                                           igroup_data=igroup_data)
+        if igrp_only:
+            print _(' Installed Packages:')
+            self._displayPkgsFromNames(igrp_only, verb, pkg_names2pkgs,
+                                       columns=columns,
+                                       igroup_data=igroup_data)
 
     def depListOutput(self, results):
         """Format and output a list of findDeps results
diff --git a/yum/__init__.py b/yum/__init__.py
index e6f3e57..bd30546 100644
--- a/yum/__init__.py
+++ b/yum/__init__.py
@@ -73,6 +73,7 @@ import logginglevels
 import yumRepo
 import callbacks
 import yum.history
+import yum.igroups
 
 import warnings
 warnings.simplefilter("ignore", Errors.YumFutureDeprecationWarning)
@@ -181,6 +182,7 @@ class YumBase(depsolve.Depsolve):
         self._up = None
         self._comps = None
         self._history = None
+        self._igroups = None
         self._pkgSack = None
         self._lockfile = None
         self._tags = None
@@ -223,6 +225,9 @@ class YumBase(depsolve.Depsolve):
         if self._history is not None:
             self.history.close()
 
+        if self._igroups is not None:
+            self.igroups.close()
+
         if self._repos:
             self._repos.close()
 
@@ -949,6 +954,14 @@ class YumBase(depsolve.Depsolve):
                                                    releasever=self.conf.yumvar['releasever'])
         return self._history
     
+    def _getIGroups(self):
+        """auto create the installed groups object that to access/change the
+           installed groups information. """
+        if self._igroups is None:
+            pdb_path = self.conf.persistdir + "/groups"
+            self._igroups = yum.igroups.InstalledGroups(db_path=pdb_path)
+        return self._igroups
+
     # properties so they auto-create themselves with defaults
     repos = property(fget=lambda self: self._getRepos(),
                      fset=lambda self, value: setattr(self, "_repos", value),
@@ -986,6 +999,11 @@ class YumBase(depsolve.Depsolve):
                        fdel=lambda self: setattr(self, "_history", None),
                        doc="Yum History Object")
 
+    igroups = property(fget=lambda self: self._getIGroups(),
+                       fset=lambda self, value: setattr(self, "_igroups",value),
+                       fdel=lambda self: setattr(self, "_igroups", None),
+                       doc="Yum Installed Groups Object")
+
     pkgtags = property(fget=lambda self: self._getTags(),
                        fset=lambda self, value: setattr(self, "_tags",value),
                        fdel=lambda self: setattr(self, "_tags", None),
@@ -1671,6 +1689,8 @@ class YumBase(depsolve.Depsolve):
             if hasattr(cb, 'verify_txmbr'):
                 vTcb = cb.verify_txmbr
             self.verifyTransaction(resultobject, vTcb)
+            if self.conf.group_command == 'objects':
+                self.igroups.save()
         return resultobject
 
     def verifyTransaction(self, resultobject=None, txmbr_cb=None):
@@ -1748,6 +1768,10 @@ class YumBase(depsolve.Depsolve):
                     if md:
                         po.yumdb_info.from_repo_timestamp = str(md.timestamp)
 
+                if hasattr(txmbr, 'group_member'):
+                    # FIXME:
+                    po.yumdb_info.group_member = txmbr.group_member
+
                 loginuid = misc.getloginuid()
                 if txmbr.updates or txmbr.downgrades or txmbr.reinstall:
                     if txmbr.updates:
@@ -1758,6 +1782,8 @@ class YumBase(depsolve.Depsolve):
                         opo = po
                     if 'installed_by' in opo.yumdb_info:
                         po.yumdb_info.installed_by = opo.yumdb_info.installed_by
+                    if 'group_member' in opo.yumdb_info:
+                        po.yumdb_info.group_member = opo.yumdb_info.group_member
                     if loginuid is not None:
                         po.yumdb_info.changed_by = str(loginuid)
                 elif loginuid is not None:
@@ -3079,6 +3105,58 @@ class YumBase(depsolve.Depsolve):
             
         return matches
 
+    def _groupInstalledData(self, group):
+        """ Return a dict of
+             pkg_name =>
+             (installed, available,
+             backlisted-installed, blacklisted-available). """
+        ret = {}
+        if not group or self.conf.group_command != 'objects':
+            return ret
+
+        pkg_names = {}
+        if group.groupid in self.igroups.groups:
+            pkg_names = self.igroups.groups[group.groupid].pkg_names
+
+        for pkg_name in set(group.packages + list(pkg_names)):
+            ipkgs = self.rpmdb.searchNames([pkg_name])
+            if pkg_name not in pkg_names and not ipkgs:
+                ret[pkg_name] = 'available'
+                continue
+
+            if not ipkgs:
+                ret[pkg_name] = 'blacklisted-available'
+                continue
+
+            for ipkg in ipkgs:
+                # Multiarch, if any are installed for the group we count "both"
+                if ipkg.yumdb_info.get('group_member', '') != group.groupid:
+                    continue
+                ret[pkg_name] = 'installed'
+                break
+            else:
+                ret[pkg_name] = 'blacklisted-installed'
+
+        return ret
+
+    def _groupReturnGroups(self, patterns=None, ignore_case=True):
+        igrps = None
+        if patterns is None:
+            grps = self.comps.groups
+            if self.conf.group_command == 'objects':
+                igrps = self.igroups.groups.values()
+            return igrps, grps
+
+        pats = ",".join(patterns)
+        cs   = not ignore_case
+        grps = self.comps.return_groups(pats, case_sensitive=cs)
+        #  Because we want name matches too, and we don't store group names
+        # we need to add the groupid's we've found:
+        if self.conf.group_command == 'objects':
+            pats += "," + ",".join([grp.groupid for grp in grps])
+            igrps = self.igroups.return_groups(pats, case_sensitive=cs)
+        return igrps, grps
+
     def doGroupLists(self, uservisible=0, patterns=None, ignore_case=True):
         """Return two lists of groups: installed groups and available
         groups.
@@ -3097,13 +3175,23 @@ class YumBase(depsolve.Depsolve):
         if self.comps.compscount == 0:
             raise Errors.GroupsError, _('No group data available for configured repositories')
         
-        if patterns is None:
-            grps = self.comps.groups
-        else:
-            grps = self.comps.return_groups(",".join(patterns),
-                                            case_sensitive=not ignore_case)
+        igrps, grps = self._groupReturnGroups(patterns, ignore_case)
+
+        if igrps is not None:
+            digrps = {}
+            for igrp in igrps:
+                digrps[igrp.gid] = igrp
+            igrps = digrps
+
         for grp in grps:
-            if grp.installed:
+            if igrps is None:
+                grp_installed = grp.installed
+            else:
+                grp_installed = grp.groupid in igrps
+                if grp_installed:
+                    del igrps[grp.groupid]
+
+            if grp_installed:
                 if uservisible:
                     if grp.user_visible:
                         installed.append(grp)
@@ -3116,9 +3204,21 @@ class YumBase(depsolve.Depsolve):
                 else:
                     available.append(grp)
             
+        if igrps is None:
+            return sorted(installed), sorted(available)
+
+        for igrp in igrps.values():
+            #  These are installed groups that aren't in comps anymore. so we
+            # create fake comps groups for them.
+            grp = comps.Group()
+            grp.installed = True
+            grp.name = grp.groupid
+            for pkg_name in igrp.pkg_names:
+                grp.mandatory_packages[pkg_name] = 1
+            installed.append(grp)
+
         return sorted(installed), sorted(available)
     
-    
     def groupRemove(self, grpid):
         """Mark all the packages in the given group to be removed.
 
@@ -3134,13 +3234,20 @@ class YumBase(depsolve.Depsolve):
             raise Errors.GroupsError, _("No Group named %s exists") % to_unicode(grpid)
 
         for thisgroup in thesegroups:
+            igroup_data = self._groupInstalledData(thisgroup)
+
             thisgroup.toremove = True
             pkgs = thisgroup.packages
             for pkg in thisgroup.packages:
+                if pkg in igroup_data and igroup_data[pkg] != 'installed':
+                    continue
+
                 txmbrs = self.remove(name=pkg, silence_warnings=True)
                 txmbrs_used.extend(txmbrs)
                 for txmbr in txmbrs:
                     txmbr.groups.append(thisgroup.groupid)
+            if igroup_data:
+                self.igroups.del_group(thisgroup.groupid)
             
         return txmbrs_used
 
@@ -3172,7 +3279,8 @@ class YumBase(depsolve.Depsolve):
                             self.tsInfo.remove(txmbr.po.pkgtup)
         
         
-    def selectGroup(self, grpid, group_package_types=[], enable_group_conditionals=None):
+    def selectGroup(self, grpid, group_package_types=[],
+                    enable_group_conditionals=None, upgrade=False):
         """Mark all the packages in the given group to be installed.
 
         :param grpid: the name of the group containing the packages to
@@ -3212,12 +3320,47 @@ class YumBase(depsolve.Depsolve):
             if 'optional' in package_types:
                 pkgs.extend(thisgroup.optional_packages)
 
+            igroup_data = self._groupInstalledData(thisgroup)
+            igrp = None
+            if igroup_data:
+                if thisgroup.groupid in self.igroups.groups:
+                    igrp = self.igroups.groups[thisgroup.groupid]
+                else:
+                    self.igroups.add_group(thisgroup.groupid,thisgroup.packages)
+            pkgs.extend(list(igroup_data.keys()))
+
             old_txmbrs = len(txmbrs_used)
             for pkg in pkgs:
+                if self.conf.group_command == 'objects':
+                    assert pkg in igroup_data
+                    if (pkg not in igroup_data or
+                        igroup_data[pkg].startswith('blacklisted')):
+                        # (upgrade and igroup_data[pkg] == 'available')):
+                        msg = _('Skipping package %s from group %s'),
+                        self.verbose_logger.log(logginglevels.DEBUG_2,
+                                                msg, pkg, thisgroup.groupid)
+                        continue
+
                 self.verbose_logger.log(logginglevels.DEBUG_2,
                     _('Adding package %s from group %s'), pkg, thisgroup.groupid)
+
+                if igrp is not None:
+                    igrp.pkg_names.add(pkg)
+                    self.igroups.changed = True
+
+                txmbrs = []
                 try:
-                    txmbrs = self.install(name=pkg, pkg_warning_level='debug2')
+                    if (upgrade and
+                        (self.conf.group_command == 'simple' or
+                         (igroup_data and igroup_data[pkg] == 'installed'))):
+                        txmbrs = self.update(name = pkg)
+                    elif igroup_data and igroup_data[pkg] == 'installed':
+                        pass # Don't upgrade on install.
+                    else:
+                        txmbrs = self.install(name = pkg,
+                                              pkg_warning_level='debug2')
+                        for txmbr in txmbrs:
+                            txmbr.group_member = thisgroup.groupid
                 except Errors.InstallError, e:
                     self.verbose_logger.debug(_('No package named %s available to be installed'),
                         pkg)
@@ -3231,6 +3374,7 @@ class YumBase(depsolve.Depsolve):
                 group_conditionals = enable_group_conditionals
 
             count_cond_test = 0
+            # FIXME: What do we do about group conditionals when group==objects
             if group_conditionals:
                 for condreq, cond in thisgroup.conditional_packages.iteritems():
                     if self.isPackageInstalled(cond):
@@ -3715,20 +3859,24 @@ class YumBase(depsolve.Depsolve):
             if next == slow:
                 return None
 
-    def _at_groupinstall(self, pattern):
-        " Do groupinstall via. leading @ on the cmd line, for install/update."
+    def _at_groupinstall(self, pattern, upgrade=False):
+        " Do groupinstall via. leading @ on the cmd line, for install."
         assert pattern[0] == '@'
         group_string = pattern[1:]
         tx_return = []
         for group in self.comps.return_groups(group_string):
             try:
-                txmbrs = self.selectGroup(group.groupid)
+                txmbrs = self.selectGroup(group.groupid, upgrade=upgrade)
                 tx_return.extend(txmbrs)
             except yum.Errors.GroupsError:
                 self.logger.critical(_('Warning: Group %s does not exist.'), group_string)
                 continue
         return tx_return
-        
+
+    def _at_groupupgrade(self, pattern):
+        " Do group upgrade via. leading @ on the cmd line, for update."
+        return self._at_groupinstall(pattern, upgrade=True)
+
     def _at_groupremove(self, pattern):
         " Do groupremove via. leading @ on the cmd line, for remove."
         assert pattern[0] == '@'
@@ -4117,7 +4265,7 @@ class YumBase(depsolve.Depsolve):
            be run if it will update the given package to the given
            version.  For example, if the package foo-1-2 is installed,::
 
-             updatePkgs(["foo-1-2], update_to=False)
+             updatePkgs(["foo-1-2"], update_to=False)
            will work identically to::
             
              updatePkgs(["foo"])
@@ -4169,7 +4317,12 @@ class YumBase(depsolve.Depsolve):
                     if new is None:
                         continue
                     tx_return.extend(self.update(po=new))
-            
+
+            # Upgrade the installed groups, as part of generic "yum upgrade"
+            if self.conf.group_command == 'objects':
+                for igrp in self.igroups.groups:
+                    tx_return.extend(self._at_groupupgrade(igrp))
+
             return tx_return
 
         # complications
@@ -4191,7 +4344,7 @@ class YumBase(depsolve.Depsolve):
                 return self._minus_deselect(kwargs['pattern'])
 
             if kwargs['pattern'] and kwargs['pattern'][0] == '@':
-                return self._at_groupinstall(kwargs['pattern'])
+                return self._at_groupupgrade(kwargs['pattern'])
 
             arg = kwargs['pattern']
             if not update_to:
diff --git a/yum/config.py b/yum/config.py
index 6c09ee9..06dfeb0 100644
--- a/yum/config.py
+++ b/yum/config.py
@@ -775,6 +775,7 @@ class YumConf(StartupConf):
     enable_group_conditionals = BoolOption(True)
     groupremove_leaf_only = BoolOption(False)
     group_package_types = ListOption(['mandatory', 'default'])
+    group_command = SelectionOption('compat', ('compat', 'objects', 'simple'))
     
     timeout = FloatOption(30.0) # FIXME: Should use variation of SecondsOption
 
diff --git a/yumcommands.py b/yumcommands.py
index fef5c59..6977850 100644
--- a/yumcommands.py
+++ b/yumcommands.py
@@ -818,25 +818,40 @@ class GroupsCommand(YumCommand):
 
         if cmd in ('install', 'remove',
                    'mark-install', 'mark-remove',
-                   'mark-members', 'info', 'mark-members-sync'):
+                   'info',
+                   'mark-packages', 'mark-packages-force', 'unmark-packages',
+                   'mark-packages-sync', 'mark-packages-sync-force'):
             checkGroupArg(base, cmd, extcmds)
 
         if cmd in ('install', 'remove', 'upgrade',
                    'mark-install', 'mark-remove',
-                   'mark-members', 'mark-members-sync'):
+                   'mark-packages', 'mark-packages-force', 'unmark-packages',
+                   'mark-packages-sync', 'mark-packages-sync-force'):
             checkRootUID(base)
 
         if cmd in ('install', 'upgrade'):
             checkGPGKey(base)
 
-        cmds = ('list', 'info', 'remove', 'install', 'upgrade', 'summary',
-                'mark-install', 'mark-remove',
-                'mark-members', 'mark-members-sync')
+        cmds = set(('list', 'info', 'remove', 'install', 'upgrade', 'summary'))
+        if base.conf.group_command == 'objects':
+            ocmds = ('mark-install', 'mark-remove',
+                     'mark-packages', 'mark-packages-force', 'unmark-packages',
+                     'mark-packages-sync', 'mark-packages-sync-force')
+            cmds.update(ocmds)
+
         if cmd not in cmds:
             base.logger.critical(_('Invalid groups sub-command, use: %s.'),
                                  ", ".join(cmds))
             raise cli.CliError
 
+        if base.conf.group_command != 'objects':
+            pass
+        elif not os.path.exists(base.igroups.filename):
+            base.logger.critical(_("There is no installed groups file."))
+        elif not os.access(base.igroups.filename, os.R_OK):
+            base.logger.critical(_("You don't have access to the groups DB."))
+            raise cli.CliError
+
     def doCommand(self, base, basecmd, extcmds):
         """Execute this command.
 
@@ -870,6 +885,60 @@ class GroupsCommand(YumCommand):
             if cmd == 'remove':
                 return base.removeGroups(extcmds)
 
+            if cmd == 'mark-install':
+                for strng in extcmds:
+                    for group in base.comps.return_groups(strng):
+                        base.igroups.add_group(group.groupid, group.packages)
+                base.igroups.save()
+                return 0, ['Marked install: ' + ','.join(extcmds)]
+
+            if cmd in ('mark-packages', 'mark-packages-force'):
+                if len(extcmds) < 2:
+                    return 1, ['No group or package given']
+                igrps, grps = base._groupReturnGroups([extcmds[0]],
+                                                      ignore_case=False)
+                if igrps is not None and len(igrps) != 1:
+                    return 1, ['No group matched']
+                grp = igrps[0]
+                force = cmd == 'mark-packages-force'
+                for pkg in base.rpmdb.returnPackages(patterns=extcmds[1:]):
+                    if not force and 'group_member' in pkg.yumdb_info:
+                        continue
+                    pkg.yumdb_info.group_member = grp.gid
+                    grp.pkg_names.add(pkg.name)
+                    base.igroups.changed = True
+                base.igroups.save()
+                return 0, ['Marked packages: ' + ','.join(extcmds[1:])]
+
+            if cmd == 'unmark-packages':
+                for pkg in base.rpmdb.returnPackages(patterns=extcmds):
+                    if 'group_member' in pkg.yumdb_info:
+                        del pkg.yumdb_info.group_member
+                return 0, ['UnMarked packages: ' + ','.join(extcmds)]
+
+            if cmd in ('mark-packages-sync', 'mark-packages-sync-force'):
+                igrps, grps = base._groupReturnGroups(extcmds,ignore_case=False)
+                if not igrps:
+                    return 1, ['No group matched']
+                force = cmd == 'mark-packages-sync-force'
+                for grp in igrps:
+                    for pkg in base.rpmdb.searchNames(grp.pkg_names):
+                        if not force and 'group_member' in pkg.yumdb_info:
+                            continue
+                        pkg.yumdb_info.group_member = grp.gid
+                if force:
+                    return 0, ['Marked packages-sync-force: '+','.join(extcmds)]
+                else:
+                    return 0, ['Marked packages-sync: ' + ','.join(extcmds)]
+
+            if cmd == 'mark-remove':
+                for strng in extcmds:
+                    for group in base.comps.return_groups(strng):
+                        base.igroups.del_group(group.groupid)
+                base.igroups.save()
+                return 0, ['Marked remove: ' + ','.join(extcmds)]
+
+
         except yum.Errors.YumBaseError, e:
             return 1, [str(e)]
 
@@ -887,6 +956,8 @@ class GroupsCommand(YumCommand):
 
         if cmd in ('list', 'info', 'remove', 'summary'):
             return False
+        if cmd.startswith('mark') or cmd.startswith('unmark'):
+            return False
         return True
 
     def needTsRemove(self, base, basecmd, extcmds):
commit bfa4adfaa0ae0d2debc581b30b3707995993cabf
Author: James Antill <james at and.org>
Date:   Tue Dec 21 12:31:49 2010 -0500

    Add first attempt of the installed groups module.

diff --git a/yum/igroups.py b/yum/igroups.py
new file mode 100644
index 0000000..625ee66
--- /dev/null
+++ b/yum/igroups.py
@@ -0,0 +1,141 @@
+#! /usr/bin/python -tt
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Copyright 2010 Red Hat
+#
+# James Antill <james at fedoraproject.org>
+
+import os
+import fnmatch
+import re
+
+class InstalledGroup(object):
+    def __init__(self, gid):
+        self.gid       = gid
+        self.pkg_names = set()
+
+    def __cmp__(self, other):
+        if other is None:
+            return 1
+        return cmp(self.gid, other.gid)
+
+    def _additions(self, pkg_names):
+        pkg_names = set(pkg_names)
+        return sorted(pkg_names.difference(self.pkg_names))
+
+    def _removals(self, pkg_names):
+        pkg_names = set(pkg_names)
+        return sorted(pkg_names.difference(self.pkg_names))
+
+
+class InstalledGroups(object):
+    def __init__(self, db_path):
+        self.filename = db_path + "/installed"
+        self.groups   = {}
+        self.changed  = False
+
+        if not os.access(self.filename, os.R_OK):
+            return
+
+        def _read_str(fo):
+            return fo.readline()[:-1]
+
+        fo = open(self.filename)
+        ver = int(_read_str(fo))
+        if ver != 1:
+            return
+
+        groups_num = int(_read_str(fo))
+        while groups_num > 0:
+            groups_num -= 1
+
+            grp = InstalledGroup(_read_str(fo))
+            self.groups[grp.gid] = grp
+
+            num = int(_read_str(fo))
+            while num > 0:
+                num -= 1
+                grp.pkg_names.add(_read_str(fo))
+
+    def close(self):
+        pass
+
+    def save(self, force=False):
+        if not force and not self.changed:
+            return False
+
+        db_path = os.path.dirname(self.filename)
+        if not os.path.exists(db_path):
+            try:
+                os.makedirs(db_path)
+            except (IOError, OSError), e:
+                # some sort of useful thing here? A warning?
+                return False
+
+        if not os.access(db_path, os.W_OK):
+            return False
+
+        fo = open(self.filename + '.tmp', 'w')
+
+        fo.write("1\n") # version
+        fo.write("%u\n" % len(self.groups))
+        for grp in sorted(self.groups.values()):
+            fo.write("%s\n" % grp.gid)
+            fo.write("%u\n" % len(grp.pkg_names))
+            for pkgname in sorted(grp.pkg_names):
+                fo.write("%s\n" % pkgname)
+        fo.close()
+        os.rename(self.filename + '.tmp', self.filename)
+        self.changed = False
+
+    def add_group(self, groupid, pkg_names):
+        self.changed = True
+
+        if groupid not in self.groups:
+            self.groups[groupid] = InstalledGroup(groupid)
+        grp = self.groups[groupid]
+
+        for pkg_name in pkg_names:
+            grp.pkg_names.add(pkg_name)
+
+    def del_group(self, groupid):
+        self.changed = True
+
+        if groupid in self.groups:
+            del self.groups[groupid]
+
+    def return_groups(self, group_pattern, case_sensitive=False):
+        returns = {}
+
+        for item in group_pattern.split(','):
+            item = item.strip()
+            if item in self.groups:
+                thisgroup = self.groups[item]
+                returns[thisgroup.gid] = thisgroup
+                continue
+            
+            if case_sensitive:
+                match = re.compile(fnmatch.translate(item)).match
+            else:
+                match = re.compile(fnmatch.translate(item), flags=re.I).match
+
+            done = False
+            for group in self.groups.values():
+                if match(group.gid):
+                    done = True
+                    returns[group.gid] = group
+                    break
+
+        return returns.values()
commit 49fe31448059c79de63920dc3bf54fea729aa39a
Author: James Antill <james at and.org>
Date:   Tue Jan 17 16:03:57 2012 -0500

     Try comparing provides versions, if they all match. BZ 782345.
    
     When comparing packages due to a provides match, if all the winning packages
    provide an = versioned number ... then we sort via. that and promote
    that as the winner.
     Note that it _only_ works when you specify _just_ the provide name,
    with no wildcards and each currently winning package has a single '='
    versioned provide for that name.
    
     This happens before requirements lookups and shortest name, but after
    everything else.
     Basically when we have:
    
    pkgA: Provides: foo = 1.2
    pkgB: Provides: foo = 3.6
    
    ...we'll now pick pkgB, before looking at requirements or shortest name.
    
     Test case in BZ.
    
     This could easily be considered "wrong" for things like:
    
    pkgA: Provides: foo-ABI = 1234
    pkgB: Provides: foo-ABI = abcd
    
    ...and maybe other cases I can't think of, but hopefully we have more
    of (mostly, even) the former.
    
     Things to consider:
    
    1. Do we want to do it after requirements checks, or maybe before where
    we do it now?
    
    2. Spew all over the API. Is it worth it?
    
    3. Do we want to pass the req. down into the plugin callback?

diff --git a/yum/__init__.py b/yum/__init__.py
index 994a840..e6f3e57 100644
--- a/yum/__init__.py
+++ b/yum/__init__.py
@@ -3461,7 +3461,8 @@ class YumBase(depsolve.Depsolve):
             raise Errors.YumBaseError, _('No Package found for %s') % errstring
         
         ps = ListPackageSack(pkglist)
-        result = self._bestPackageFromList(ps.returnNewestByNameArch())
+        result = self._bestPackageFromList(ps.returnNewestByNameArch(),
+                                           req=errstring)
         if result is None:
             raise Errors.YumBaseError, _('No Package found for %s') % errstring
         
@@ -3523,18 +3524,25 @@ class YumBase(depsolve.Depsolve):
             raise Errors.YumBaseError, _('No Package found for %s') % errstring
         
         ps = ListPackageSack(pkglist)
-        result = self._bestPackageFromList(ps.returnNewestByNameArch())
+        result = self._bestPackageFromList(ps.returnNewestByNameArch(),
+                                           req=errstring)
         if result is None:
             raise Errors.YumBaseError, _('No Package found for %s') % errstring
         
         return result
 
-    def _bestPackageFromList(self, pkglist):
+    def _bestPackageFromList(self, pkglist, req=None):
         """take list of package objects and return the best package object.
            If the list is empty, return None. 
            
            Note: this is not aware of multilib so make sure you're only
-           passing it packages of a single arch group."""
+           passing it packages of a single arch group.
+
+           :param pkglist: the list of packages to return the best
+             packages from
+           :param req: the requirement from the user
+           :return: a list of the best packages from *pkglist*
+        """
         
         
         if len(pkglist) == 0:
@@ -3543,10 +3551,11 @@ class YumBase(depsolve.Depsolve):
         if len(pkglist) == 1:
             return pkglist[0]
 
-        bestlist = self._compare_providers(pkglist, None)
+        bestlist = self._compare_providers(pkglist, reqpo=None, req=req)
         return bestlist[0][0]
 
-    def bestPackagesFromList(self, pkglist, arch=None, single_name=False):
+    def bestPackagesFromList(self, pkglist, arch=None, single_name=False,
+                             req=None):
         """Return the best packages from a list of packages.  This
         function is multilib aware, so that it will not compare
         multilib to singlelib packages.
@@ -3556,6 +3565,7 @@ class YumBase(depsolve.Depsolve):
         :param arch: packages will be selected that are compatible
            with the architecture specified by *arch*
         :param single_name: whether to return a single package name
+        :param req: the requirement from the user
         :return: a list of the best packages from *pkglist*
         """
         returnlist = []
@@ -3574,9 +3584,9 @@ class YumBase(depsolve.Depsolve):
                 singleLib.append(po)
                 
         # we now have three lists.  find the best package(s) of each
-        multi = self._bestPackageFromList(multiLib)
-        single = self._bestPackageFromList(singleLib)
-        no = self._bestPackageFromList(noarch)
+        multi = self._bestPackageFromList(multiLib, req=req)
+        single = self._bestPackageFromList(singleLib, req=req)
+        no = self._bestPackageFromList(noarch, req=req)
 
         if single_name and multi and single and multi.name != single.name:
             # Sinlge _must_ match multi, if we want a single package name
@@ -3590,7 +3600,7 @@ class YumBase(depsolve.Depsolve):
         # if there's a noarch and it's newer than the multilib, we want
         # just the noarch.  otherwise, we want multi + single
         elif multi:
-            best = self._bestPackageFromList([multi,no])
+            best = self._bestPackageFromList([multi,no], req=req)
             if best.arch == "noarch":
                 returnlist.append(no)
             else:
@@ -3598,7 +3608,7 @@ class YumBase(depsolve.Depsolve):
                 if single: returnlist.append(single)
         # similar for the non-multilib case
         elif single:
-            best = self._bestPackageFromList([single,no])
+            best = self._bestPackageFromList([single,no], req=req)
             if best.arch == "noarch":
                 returnlist.append(no)
             else:
@@ -3861,7 +3871,8 @@ class YumBase(depsolve.Depsolve):
                         #                                all of the pkgs)
                         if mypkgs and not misc.re_glob(arg):
                             mypkgs = self.bestPackagesFromList(mypkgs,
-                                                               single_name=True)
+                                                               single_name=True,
+                                                               req=arg)
                         if mypkgs:
                             pkgs.extend(mypkgs)
                         
diff --git a/yum/depsolve.py b/yum/depsolve.py
index 720188c..de01582 100644
--- a/yum/depsolve.py
+++ b/yum/depsolve.py
@@ -31,6 +31,7 @@ from transactioninfo import TransactionMember
 import rpm
 
 from packageSack import ListPackageSack
+from packages import PackageEVR
 from constants import *
 import logginglevels
 import Errors
@@ -1248,7 +1249,7 @@ class Depsolve(object):
         return True
     _isPackageInstalled = isPackageInstalled
 
-    def _compare_providers(self, pkgs, reqpo):
+    def _compare_providers(self, pkgs, reqpo, req=None):
         """take the list of pkgs and score them based on the requesting package
            return a dictionary of po=score"""
         self.verbose_logger.log(logginglevels.DEBUG_4,
@@ -1292,6 +1293,24 @@ class Depsolve(object):
                 return None
             return x
 
+        def _pkg2prov_version(pkg, provname):
+            ''' This converts a package into a specific version tuple for the
+            required provide. The provide _must_ be '=' and epoch=None and
+            release=None == '0'.
+               If there is 0 or 2+ matches, return None.
+               If the req does not == the provide name, return None. '''
+            ret = None
+            for prov in pkg.provides:
+                (n, f, (e, v, r)) = prov
+                if n != provname:
+                    continue
+                if f != 'EQ':
+                    continue
+                if ret is not None:
+                    return None
+                ret = (e or '0', v, r or '0')
+            return ret
+
         #  Actual start of _compare_providers().
 
         # Do a NameArch filtering, based on repo. __cmp__
@@ -1414,6 +1433,26 @@ class Depsolve(object):
                         _('common prefix of %s between %s and %s' % (cpl, po, reqpo)))
                 
                     pkgresults[po] += cpl*2
+
+        if req is not None:
+            bestnum = max(pkgresults.values())
+            prov_depsolve = {}
+            for po in pkgs:
+                if pkgresults[po] != bestnum:
+                    continue
+                evr = _pkg2prov_version(po, req)
+                if evr is None:
+                    prov_depsolve = {}
+                    break
+                prov_depsolve[po] = evr
+            if len(prov_depsolve) > 1:
+                self.verbose_logger.log(logginglevels.DEBUG_4,
+                                        _('provides vercmp: %s') % str(req))
+                newest = sorted(prov_depsolve,
+                                key = lambda x: PackageEVR(*prov_depsolve[x]))[-1]
+                self.verbose_logger.log(logginglevels.DEBUG_4,
+                                        _(' Winner: %s') % newest)
+                pkgresults[newest] += 1
                 
         #  If we have more than one "best", see what would happen if we picked
         # each package ... ie. what things do they require that _aren't_ already
commit e1b960b49ca657fb4d284d711ec0d21c24c4d68c
Merge: 9d50f62 1dd68f8
Author: James Antill <james at and.org>
Date:   Thu Dec 22 10:38:46 2011 -0500

    Merge branch 'master' of ssh://yum.baseurl.org/srv/projects/yum/git/yum
    
    * 'master' of ssh://yum.baseurl.org/srv/projects/yum/git/yum: (7 commits)
      Fix two-word groups foo completion.
      ...

commit 9d50f6292ba8204d9b162bd205195b4629d88381
Author: James Antill <james at and.org>
Date:   Tue Dec 20 14:44:46 2011 -0500

    With no arg. to clean, give the error and then fail. BZ 735333

diff --git a/yumcommands.py b/yumcommands.py
index 36b19b3..2cd0b96 100644
--- a/yumcommands.py
+++ b/yumcommands.py
@@ -142,6 +142,7 @@ def checkCleanArg(base, basecmd, extcmds):
     if len(extcmds) == 0:
         base.logger.critical(_('Error: clean requires an option: %s') % (
             ", ".join(VALID_ARGS)))
+        raise cli.CliError
 
     for cmd in extcmds:
         if cmd not in VALID_ARGS:
commit 002998fdbca17aac62403e3e3186637b134010bd
Author: James Antill <james at and.org>
Date:   Tue Dec 20 14:35:22 2011 -0500

    Add getcwd() check as well as open(".") check. BZ 711358.

diff --git a/yummain.py b/yummain.py
index 12582d2..e1a9702 100755
--- a/yummain.py
+++ b/yummain.py
@@ -111,6 +111,12 @@ def main(args):
             os.chdir("/")
     else:
         f.close()
+    try:
+        os.getcwd()
+    except OSError, e:
+        if e.errno == errno.ENOENT:
+            logger.critical(_('No getcwd() access in current directory, moving to /'))
+            os.chdir("/")
 
     lockerr = ""
     while True:
commit 0f034091e4388026b10ca0ec5c486d0313ca71b8
Author: James Antill <james at and.org>
Date:   Tue Dec 13 15:43:30 2011 -0500

    Have users always use own dirs.

diff --git a/cli.py b/cli.py
index d5dcfaf..ac9522b 100755
--- a/cli.py
+++ b/cli.py
@@ -1885,11 +1885,10 @@ class YumOptionParser(OptionParser):
             if opts.assumeno:
                 self.base.conf.assumeno  = 1
 
-            #  Instead of going cache-only for a non-root user, try to use a
-            # user writable cachedir. If that fails fall back to cache-only.
-            if opts.cacheonly:
+            #  Treat users like root as much as possible:
+            if not self.base.setCacheDir():
                 self.base.conf.cache = 1
-            elif not self.base.setCacheDir():
+            if opts.cacheonly:
                 self.base.conf.cache = 1
 
             if opts.obsoletes:


More information about the Yum-commits mailing list