diff --git a/BZ-908870-MD-files-bad.patch b/BZ-908870-MD-files-bad.patch new file mode 100644 index 0000000..c8d79a8 --- /dev/null +++ b/BZ-908870-MD-files-bad.patch @@ -0,0 +1,241 @@ +commit 83fcbe745c3ee8f5f0fa29626a86c824db059b22 +Author: James Antill +Date: Thu Feb 7 12:58:07 2013 -0500 + + Fix problems with mirrors like wtfnix.com, delete bad MD files. + +diff --git a/yum/yumRepo.py b/yum/yumRepo.py +index dfcf8f9..efbc42a 100644 +--- a/yum/yumRepo.py ++++ b/yum/yumRepo.py +@@ -940,6 +941,7 @@ Insufficient space in download directory %s + range=(start, end), + ) + except URLGrabError, e: ++ self._del_dl_file(local, size) + errstr = "failed to retrieve %s from %s\nerror was %s" % (relative, self, e) + if self.mirrorurls: + errstr +="\n You could try running: yum clean expire-cache" +@@ -961,6 +963,7 @@ Insufficient space in download directory %s + **kwargs + ) + except URLGrabError, e: ++ self._del_dl_file(local, size) + errstr = "failure: %s from %s: %s" % (relative, self, e) + errors = getattr(e, 'errors', None) + raise Errors.NoMoreMirrorsRepoError(errstr, errors) +@@ -1652,6 +1655,18 @@ Insufficient space in download directory %s + raise URLGrabError(-1, 'repomd.xml does not match metalink for %s' % + self) + ++ def _del_dl_file(self, local, size): ++ """ Delete a downloaded file if it's the correct size. """ ++ ++ sd = misc.stat_f(local) ++ if not sd: # File doesn't exist... ++ return ++ ++ if size and sd.st_size < size: ++ return # Still more to get... ++ ++ # Is the correct size, or too big ... delete it so we'll try again. ++ misc.unlink_f(local) + + def checkMD(self, fn, mdtype, openchecksum=False): + """check the metadata type against its checksum""" +@@ -1681,7 +1696,7 @@ Insufficient space in download directory %s + if size is not None: + size = int(size) + +- if fast: ++ if fast and skip_old_DBMD_check: + fsize = misc.stat_f(file) + if fsize is None: # File doesn't exist... + return None +@@ -1756,16 +1771,21 @@ Insufficient space in download directory %s + + try: + def checkfunc(obj): +- self.checkMD(obj, mdtype) ++ try: ++ self.checkMD(obj, mdtype) ++ except URLGrabError: ++ # Don't share MD among mirrors, in theory we could use: ++ # self._del_dl_file(local, int(thisdata.size)) ++ # ...but this is safer. ++ misc.unlink_f(obj.filename) ++ raise + self.retrieved[mdtype] = 1 + text = "%s/%s" % (self, mdtype) + if thisdata.size is None: + reget = None + else: + reget = 'simple' +- if os.path.exists(local): +- if os.stat(local).st_size >= int(thisdata.size): +- misc.unlink_f(local) ++ self._del_dl_file(local, int(thisdata.size)) + local = self._getFile(relative=remote, + local=local, + copy_local=1, +commit c148eb10b798270b3d15087433c8efb2a79a69d0 +Author: James Antill +Date: Mon Feb 18 16:17:06 2013 -0500 + + Use xattr data as well as file size for "fast checksumming". + +diff --git a/yum/yumRepo.py b/yum/yumRepo.py +index efbc42a..8c38093 100644 +--- a/yum/yumRepo.py ++++ b/yum/yumRepo.py +@@ -52,15 +52,54 @@ import stat + import errno + import tempfile + +-# If you want yum to _always_ check the MD .sqlite files then set this to +-# False (this doesn't affect .xml files or .sqilte files derived from them). +-# With this as True yum will only check when a new repomd.xml or +-# new MD is downloaded. +-# Note that with atomic MD, we can't have old MD lying around anymore so +-# the only way we need this check is if someone does something like: +-# cp primary.sqlite /var/cache/yum/blah +-# ...at which point you lose. +-skip_old_DBMD_check = True ++# This is unused now, probably nothing uses it but it was global/public. ++skip_old_DBMD_check = False ++ ++try: ++ import xattr ++ if not hasattr(xattr, 'get') or not hasattr(xattr, 'set'): ++ xattr = None # This is a "newer" API. ++except ImportError: ++ xattr = None ++ ++# The problem we are trying to solve here is that: ++# ++# 1. We rarely want to be downloading MD/pkgs/etc. ++# 2. We want to check those files are valid (match checksums) when we do ++# download them. ++# 3. We _really_ don't want to checksum all the files everytime we ++# run (100s of MBs). ++# 4. We can continue to download files from bad mirrors, or retry files due to ++# C-c etc. ++# ++# ...we used to solve this by just checking the file size, and assuming the ++# files had been downloaded and checksumed as correct if that matched. But that ++# was error prone on bad mirrors, so now we store the checksum in an ++# xattr ... this does mean that if you can't store xattrs (Eg. NFS) you will ++# rechecksum everything constantly. ++ ++def _xattr_get_chksum(filename, chktype): ++ if not xattr: ++ return None ++ ++ try: ++ ret = xattr.get(filename, 'user.yum.checksum.' + chktype) ++ except: # Documented to be "EnvironmentError", but make sure ++ return None ++ ++ return ret ++ ++def _xattr_set_chksum(filename, chktype, chksum): ++ if not xattr: ++ return None ++ ++ try: ++ xattr.set(filename, 'user.yum.checksum.' + chktype, chksum) ++ except: ++ return False # Data too long. = IOError ... ignore everything. ++ ++ return True ++ + + warnings.simplefilter("ignore", Errors.YumFutureDeprecationWarning) + +@@ -228,7 +267,7 @@ class YumPackageSack(packageSack.PackageSack): + # get rid of all this stuff we don't need now + del repo.cacheHandler + +- def _check_uncompressed_db_gen(self, repo, mdtype, fast=True): ++ def _check_uncompressed_db_gen(self, repo, mdtype): + """return file name of db in gen/ dir if good, None if not""" + + mydbdata = repo.repoXML.getData(mdtype) +@@ -238,7 +277,7 @@ class YumPackageSack(packageSack.PackageSack): + db_un_fn = mdtype + '.sqlite' + + if not repo._checkMD(compressed_fn, mdtype, data=mydbdata, +- check_can_fail=fast, fast=fast): ++ check_can_fail=True): + return None + + ret = misc.repo_gen_decompress(compressed_fn, db_un_fn, +@@ -261,8 +300,7 @@ class YumPackageSack(packageSack.PackageSack): + result = None + + if os.path.exists(db_un_fn): +- if skip_old_DBMD_check and repo._using_old_MD: +- return db_un_fn ++ + + try: + repo.checkMD(db_un_fn, mdtype, openchecksum=True) +@@ -296,7 +334,6 @@ class YumRepository(Repository, config.RepoConf): + # eventually want + self.repoMDFile = 'repodata/repomd.xml' + self._repoXML = None +- self._using_old_MD = None + self._oldRepoMDData = {} + self.cache = 0 + self.mirrorlistparsed = 0 +@@ -1407,7 +1444,6 @@ Insufficient space in download directory %s + self._revertOldRepoXML() + return False + +- self._using_old_MD = caching + if caching: + return False # Skip any work. + +@@ -1673,7 +1709,7 @@ Insufficient space in download directory %s + return self._checkMD(fn, mdtype, openchecksum) + + def _checkMD(self, fn, mdtype, openchecksum=False, +- data=None, check_can_fail=False, fast=False): ++ data=None, check_can_fail=False): + """ Internal function, use .checkMD() from outside yum. """ + + thisdata = data # So the argument name is nicer +@@ -1696,17 +1732,15 @@ Insufficient space in download directory %s + if size is not None: + size = int(size) + +- if fast and skip_old_DBMD_check: ++ l_csum = _xattr_get_chksum(file, r_ctype) ++ if l_csum: + fsize = misc.stat_f(file) +- if fsize is None: # File doesn't exist... +- return None +- if size is None: +- return 1 +- if size == fsize.st_size: +- return 1 +- if check_can_fail: +- return None +- raise URLGrabError(-1, 'Metadata file does not match size') ++ if fsize is not None: # We just got an xattr, so it should be there ++ if size is None and l_csum == r_csum: ++ return 1 ++ if size == fsize.st_size and l_csum == r_csum: ++ return 1 ++ # Anything goes wrong, run the checksums as normal... + + try: # get the local checksum + l_csum = self._checksum(r_ctype, file, datasize=size) +@@ -1716,6 +1750,7 @@ Insufficient space in download directory %s + raise URLGrabError(-3, 'Error performing checksum') + + if l_csum == r_csum: ++ _xattr_set_chksum(file, r_ctype, l_csum) + return 1 + else: + if check_can_fail: diff --git a/arm-arch-detection.patch b/arm-arch-detection.patch deleted file mode 100644 index 0d7dbb3..0000000 --- a/arm-arch-detection.patch +++ /dev/null @@ -1,104 +0,0 @@ -commit 24dde23643f98ed355fcf50fb09807e2b08ea620 -Author: Phil Knirsch -Date: Fri Apr 27 09:09:15 2012 -0400 - - Do arm arch detection in the same way rpm does it. Once we have the rpm API - in place we can switch over to that easily. - - Minor cleanups by James Antill. - -diff --git a/rpmUtils/arch.py b/rpmUtils/arch.py -index 7d67907..2226dc5 100644 ---- a/rpmUtils/arch.py -+++ b/rpmUtils/arch.py -@@ -3,6 +3,8 @@ - - import os - import rpm -+import ctypes -+import struct - - _ppc64_native_is_best = True - -@@ -31,6 +33,7 @@ arches = { - "ia32e": "x86_64", - - # ppc -+ "ppc64p7": "ppc64", - "ppc64pseries": "ppc64", - "ppc64iseries": "ppc64", - "ppc64": "ppc", -@@ -82,6 +85,13 @@ arches = { - "ia64": "noarch", - } - -+# Will contain information parsed from /proc/self/auxv via _parse_auxv(). -+# Should move into rpm really. -+_aux_vector = { -+ "platform": "", -+ "hwcap": 0, -+ } -+ - def legitMultiArchesInSameLib(arch=None): - # this is completely crackrock - if anyone has a better way I - # am all ears -@@ -222,6 +232,32 @@ def _try_read_cpuinfo(): - except: - return [] - -+def _parse_auxv(): -+ """ Read /proc/self/auxv and parse it into global dict for easier access -+ later on, very similar to what rpm does. """ -+ # In case we can't open and read /proc/self/auxv, just return -+ try: -+ data = open("/proc/self/auxv", "rb").read() -+ except: -+ return -+ -+ # Define values from /usr/include/elf.h -+ AT_PLATFORM = 15 -+ AT_HWCAP = 16 -+ fmtlen = struct.calcsize("LL") -+ offset = 0 -+ platform = ctypes.c_char_p() -+ -+ # Parse the data and fill in _aux_vector dict -+ while offset <= len(data) - fmtlen: -+ at_type, at_val = struct.unpack_from("LL", data, offset) -+ if at_type == AT_PLATFORM: -+ platform.value = at_val -+ _aux_vector["platform"] = platform.value -+ if at_type == AT_HWCAP: -+ _aux_vector["hwcap"] = at_val -+ offset = offset + fmtlen -+ - def getCanonX86Arch(arch): - # - if arch == "i586": -@@ -260,6 +296,17 @@ def getCanonPPCArch(arch): - if line.find("machine") != -1: - machine = line.split(':')[1] - break -+ -+ platform = _aux_vector["platform"] -+ if machine is None and not platform: -+ return arch -+ -+ try: -+ if platform.startswith("power") and int(platform[5:]) >= 7: -+ return "ppc64p7" -+ except: -+ pass -+ - if machine is None: - return arch - -@@ -324,6 +371,8 @@ def getCanonArch(skipRpmPlatform = 0): - - arch = os.uname()[4] - -+ _parse_auxv() -+ - if (len(arch) == 4 and arch[0] == "i" and arch[2:4] == "86"): - return getCanonX86Arch(arch) - diff --git a/ui_id-prevent-TB-on-invalid-repos-with-no-URLs.patch b/ui_id-prevent-TB-on-invalid-repos-with-no-URLs.patch new file mode 100644 index 0000000..000ec51 --- /dev/null +++ b/ui_id-prevent-TB-on-invalid-repos-with-no-URLs.patch @@ -0,0 +1,28 @@ +From 40d9d5f4c3a00b2efa6ee0b64fe4d1ec52d46a69 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zden=C4=9Bk=20Pavlas?= +Date: Mon, 29 Oct 2012 09:22:02 +0100 +Subject: [PATCH] ui_id: prevent TB on invalid repos with no URLs. BZ 870691. + +--- + yum/yumRepo.py | 4 +++- + 1 files changed, 3 insertions(+), 1 deletions(-) + +diff --git a/yum/yumRepo.py b/yum/yumRepo.py +index e362c43..414f1d9 100644 +--- a/yum/yumRepo.py ++++ b/yum/yumRepo.py +@@ -381,8 +381,10 @@ class YumRepository(Repository, config.RepoConf): + val = ini['metalink'] + elif 'mirrorlist' in ini: + val = ini['mirrorlist'] +- else: ++ elif 'baseurl' in ini: + val = ini['baseurl'] ++ else: ++ val = '' + ret = self.id + if '$releasever' in val: + ret += '/' +-- +1.7.4.4 + diff --git a/yum-HEAD.patch b/yum-HEAD.patch index 1fa8c87..f70506c 100644 --- a/yum-HEAD.patch +++ b/yum-HEAD.patch @@ -96,7 +96,7 @@ index 2f6154e..2e5a052 100644 diff --git a/cli.py b/cli.py old mode 100644 new mode 100755 -index 6056d38..afa7447 +index 6056d38..cf2c369 --- a/cli.py +++ b/cli.py @@ -25,7 +25,7 @@ import sys @@ -253,6 +253,18 @@ index 6056d38..afa7447 except ValueError, e: self.logger.critical(_('Options Error: %s'), e) sys.exit(1) +@@ -290,9 +324,9 @@ class YumBaseCli(yum.YumBase, output.YumOutput): + self.term.MODE['normal']) + print _(" Installed: %s-%s at %s") %(name, ver, + sm_ui_time(pkg.installtime)) +- print _(" Built : %s at %s") % (pkg.packager, ++ print _(" Built : %s at %s") % (to_unicode(pkg.packager), + sm_ui_time(pkg.buildtime)) +- print _(" Committed: %s at %s") % (pkg.committer, ++ print _(" Committed: %s at %s") % (to_unicode(pkg.committer), + sm_ui_date(pkg.committime)) + sys.exit(0) + @@ -318,9 +352,11 @@ class YumBaseCli(yum.YumBase, output.YumOutput): time.sleep(sleeptime) @@ -372,8 +384,21 @@ index 6056d38..afa7447 stuff_to_download = True po = txmbr.po if po: -@@ -491,7 +538,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -489,9 +536,20 @@ class YumBaseCli(yum.YumBase, output.YumOutput): + else: + self.reportDownloadSize(downloadpkgs, install_only) ++ cfr = self.tsInfo._check_future_rpmdbv ++ if (cfr is not None and ++ self.tsInfo.futureRpmDBVersion() != cfr[1]): ++ msg = _("future rpmdb ver mismatched saved transaction version,") ++ if cfr[2]: ++ msg += _(" ignoring, as requested.") ++ self.logger.critical(_(msg)) ++ else: ++ msg += _(" aborting.") ++ raise yum.Errors.YumBaseError(msg) ++ # confirm with user if self._promptWanted(): - if not self.userconfirm(): @@ -381,7 +406,7 @@ index 6056d38..afa7447 self.verbose_logger.info(_('Exiting on user Command')) return -1 -@@ -609,12 +656,14 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -609,12 +667,14 @@ class YumBaseCli(yum.YumBase, output.YumOutput): return resultobject.return_code def gpgsigcheck(self, pkgs): @@ -401,7 +426,7 @@ index 6056d38..afa7447 for po in pkgs: result, errmsg = self.sigCheckPkg(po) -@@ -623,7 +672,8 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -623,7 +683,8 @@ class YumBaseCli(yum.YumBase, output.YumOutput): continue elif result == 1: @@ -411,7 +436,7 @@ index 6056d38..afa7447 raise yum.Errors.YumBaseError, \ _('Refusing to automatically import keys when running ' \ 'unattended.\nUse "-y" to override.') -@@ -691,12 +741,62 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -691,12 +752,62 @@ class YumBaseCli(yum.YumBase, output.YumOutput): ", ".join(matches)) self.verbose_logger.log(yum.logginglevels.INFO_2, msg) @@ -480,7 +505,7 @@ index 6056d38..afa7447 # get the list of available packages # iterate over the user's list # add packages to Transaction holding class if they match. -@@ -710,11 +810,26 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -710,11 +821,36 @@ class YumBaseCli(yum.YumBase, output.YumOutput): for arg in userlist: if (arg.endswith('.rpm') and (yum.misc.re_remote_url(arg) or os.path.exists(arg))): @@ -495,12 +520,22 @@ index 6056d38..afa7447 + elif basecmd == 'install-n': + txmbrs = self.install(name=arg) + elif basecmd == 'install-na': -+ n,a = arg.split('.') ++ try: ++ n,a = arg.rsplit('.', 1) ++ except: ++ self.verbose_logger.warning(_('Bad %s argument %s.'), ++ basecmd, arg) ++ continue + txmbrs = self.install(name=n, arch=a) + elif basecmd == 'install-nevra': -+ nevr,a = arg.rsplit('.', 2) -+ n,ev,r = nevr.rsplit('-', 3) -+ e,v = ev.split(':', 2) ++ try: ++ nevr,a = arg.rsplit('.', 1) ++ n,ev,r = nevr.rsplit('-', 2) ++ e,v = ev.split(':', 1) ++ except: ++ self.verbose_logger.warning(_('Bad %s argument %s.'), ++ basecmd, arg) ++ continue + txmbrs = self.install(name=n, + epoch=e, version=v, release=r, arch=a) + else: @@ -509,7 +544,7 @@ index 6056d38..afa7447 except yum.Errors.InstallError: self.verbose_logger.log(yum.logginglevels.INFO_2, _('No package %s%s%s available.'), -@@ -723,6 +838,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -723,6 +859,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput): self._maybeYouMeant(arg) else: done = True @@ -517,7 +552,7 @@ index 6056d38..afa7447 if len(self.tsInfo) > oldcount: change = len(self.tsInfo) - oldcount return 2, [P_('%d package to install', '%d packages to install', change) % change] -@@ -732,9 +848,27 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -732,9 +869,27 @@ class YumBaseCli(yum.YumBase, output.YumOutput): return 0, [_('Nothing to do')] def updatePkgs(self, userlist, quiet=0, update_to=False): @@ -548,7 +583,7 @@ index 6056d38..afa7447 # if there is no userlist, then do global update below # this is probably 90% of the calls # if there is a userlist then it's for updating pkgs, not obsoleting -@@ -745,21 +879,18 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -745,21 +900,18 @@ class YumBaseCli(yum.YumBase, output.YumOutput): else: # go through the userlist - look for items that are local rpms. If we find them @@ -579,7 +614,7 @@ index 6056d38..afa7447 if len(self.tsInfo) > oldcount: change = len(self.tsInfo) - oldcount -@@ -770,9 +901,24 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -770,9 +922,24 @@ class YumBaseCli(yum.YumBase, output.YumOutput): # Note that we aren't in __init__ yet for a couple of reasons, but we # probably will get there for 3.2.28. def distroSyncPkgs(self, userlist): @@ -607,7 +642,7 @@ index 6056d38..afa7447 level = 'diff' if userlist and userlist[0] in ('full', 'diff', 'different'): -@@ -831,6 +977,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -831,6 +998,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput): continue nayi = napkg.yumdb_info @@ -615,14 +650,16 @@ index 6056d38..afa7447 for apkg in self.pkgSack.searchPkgTuple(napkg.pkgtup): if ('checksum_type' in nayi and 'checksum_data' in nayi and -@@ -866,10 +1013,19 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -865,15 +1033,54 @@ class YumBaseCli(yum.YumBase, output.YumOutput): + else: return 0, [_('No Packages marked for Distribution Synchronization')] - def erasePkgs(self, userlist): +- def erasePkgs(self, userlist): - """take user commands and populate a transaction wrapper with packages - to be erased/removed""" - - oldcount = len(self.tsInfo) ++ def erasePkgs(self, userlist, pos=False, basecmd='remove'): + """Take user commands and populate a transaction wrapper with + packages to be erased. + @@ -639,7 +676,42 @@ index 6056d38..afa7447 all_rms = [] for arg in userlist: -@@ -884,12 +1040,24 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +- rms = self.remove(pattern=arg) ++ if pos: ++ rms = self.remove(po=arg) ++ if rms: ++ all_rms.extend(rms) ++ continue ++ ++ if False: pass ++ elif basecmd in ('erase-n', 'remove-n'): ++ rms = self.remove(name=arg) ++ elif basecmd in ('erase-na', 'remove-na'): ++ try: ++ n,a = arg.rsplit('.', 1) ++ except: ++ self.verbose_logger.warning(_('Bad %s argument %s.'), ++ basecmd, arg) ++ continue ++ rms = self.remove(name=n, arch=a) ++ elif basecmd in ('erase-nevra', 'remove-nevra'): ++ try: ++ nevr,a = arg.rsplit('.', 1) ++ n,ev,r = nevr.rsplit('-', 2) ++ e,v = ev.split(':', 1) ++ except: ++ self.verbose_logger.warning(_('Bad %s argument %s.'), ++ basecmd, arg) ++ continue ++ rms = self.remove(name=n, epoch=e, version=v, release=r, arch=a) ++ else: ++ assert basecmd in ('erase', 'remove'), basecmd ++ rms = self.remove(pattern=arg) ++ + if not rms: + self._checkMaybeYouMeant(arg, always_output=False, rpmdb_only=True) + all_rms.extend(rms) +@@ -884,12 +1091,24 @@ class YumBaseCli(yum.YumBase, output.YumOutput): return 0, [_('No Packages marked for removal')] def downgradePkgs(self, userlist): @@ -667,7 +739,7 @@ index 6056d38..afa7447 for arg in userlist: if (arg.endswith('.rpm') and (yum.misc.re_remote_url(arg) or os.path.exists(arg))): -@@ -905,26 +1073,44 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -905,26 +1124,44 @@ class YumBaseCli(yum.YumBase, output.YumOutput): self.term.MODE['bold'], arg, self.term.MODE['normal']) self._maybeYouMeant(arg) @@ -716,7 +788,7 @@ index 6056d38..afa7447 except yum.Errors.ReinstallRemoveError: self._checkMaybeYouMeant(arg, always_output=False) except yum.Errors.ReinstallInstallError, e: -@@ -940,15 +1126,31 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -940,15 +1177,31 @@ class YumBaseCli(yum.YumBase, output.YumOutput): except yum.Errors.ReinstallError, e: assert False, "Shouldn't happen, but just in case" self.verbose_logger.log(yum.logginglevels.INFO_2, e) @@ -751,7 +823,7 @@ index 6056d38..afa7447 # read in each package into a YumLocalPackage Object # append it to self.localPackages # check if it can be installed or updated based on nevra versus rpmdb -@@ -972,20 +1174,25 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -972,20 +1225,25 @@ class YumBaseCli(yum.YumBase, output.YumOutput): return 0, [_('Nothing to do')] def returnPkgLists(self, extcmds, installed_available=False): @@ -791,7 +863,7 @@ index 6056d38..afa7447 special = ['available', 'installed', 'all', 'extras', 'updates', 'recent', 'obsoletes'] -@@ -1017,8 +1224,25 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -1017,8 +1275,25 @@ class YumBaseCli(yum.YumBase, output.YumOutput): return ypl def search(self, args): @@ -819,7 +891,7 @@ index 6056d38..afa7447 # call the yum module search function with lists of tags to search # and what to search for -@@ -1108,9 +1332,20 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -1108,9 +1383,20 @@ class YumBaseCli(yum.YumBase, output.YumOutput): return 0, matching def deplist(self, args): @@ -828,13 +900,13 @@ index 6056d38..afa7447 + """Print out a formatted list of dependencies for a list of + packages. This is a cli wrapper method for + :class:`yum.YumBase.findDeps`. -+ + + :param args: a list of names or wildcards specifying packages + that should have their dependenices printed + :return: (exit_code, [ errors ]) + + exit_code is:: - ++ + 0 = we're done, exit + 1 = we've errored, exit with error string + 2 = we've got work yet to do, onto the next stage @@ -842,7 +914,7 @@ index 6056d38..afa7447 pkgs = [] for arg in args: if (arg.endswith('.rpm') and (yum.misc.re_remote_url(arg) or -@@ -1131,10 +1366,19 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -1131,10 +1417,19 @@ class YumBaseCli(yum.YumBase, output.YumOutput): return 0, [] def provides(self, args): @@ -866,7 +938,16 @@ index 6056d38..afa7447 old_sdup = self.conf.showdupesfromrepos # For output, as searchPackageProvides() is always in showdups mode self.conf.showdupesfromrepos = True -@@ -1163,20 +1407,77 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -1147,6 +1442,8 @@ class YumBaseCli(yum.YumBase, output.YumOutput): + paths = set(sys.path + os.environ['PATH'].split(':')) + nargs = [] + for arg in args: ++ if not arg: ++ continue + if yum.misc.re_filename(arg) or yum.misc.re_glob(arg): + continue + for path in paths: +@@ -1163,20 +1460,77 @@ class YumBaseCli(yum.YumBase, output.YumOutput): return 0, [] def resolveDepCli(self, args): @@ -949,39 +1030,225 @@ index 6056d38..afa7447 hdrcode = pkgcode = xmlcode = dbcode = expccode = 0 pkgresults = hdrresults = xmlresults = dbresults = expcresults = [] msg = self.fmtKeyValFill(_('Cleaning repos: '), -@@ -1228,7 +1529,19 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -1228,130 +1582,257 @@ class YumBaseCli(yum.YumBase, output.YumOutput): return code, [] def returnGroupLists(self, userlist): + """Print out a list of groups that match the given names or + wildcards. -+ + +- uservisible=1 + :param extcmds: a list of names or wildcards specifying + groups to list + :return: (exit_code, [ errors ]) - ++ + exit_code is:: + + 0 = we're done, exit + 1 = we've errored, exit with error string + 2 = we've got work yet to do, onto the next stage + """ - uservisible=1 ++ return self._returnGroupLists(userlist) ++ ++ def _returnGroupLists(self, userlist, summary=False): ++ # What data are we showing... ++ wts_map = {'hidden' : 'hidden', ++ 'language' : 'lang', ++ 'languages' : 'lang', ++ 'lang' : 'lang', ++ 'langs' : 'lang', ++ 'environment' : 'env', ++ 'environments' : 'env', ++ 'env' : 'env', ++ 'envs' : 'env', ++ 'package' : 'pkg', ++ 'packages' : 'pkg', ++ 'pkg' : 'pkg', ++ 'pkgs' : 'pkg', ++ 'available' : 'avail', ++ 'avail' : 'avail', ++ 'installed' : 'inst', ++ 'inst' : 'inst', ++ 'id' : 'id', ++ 'ids' : 'id', ++ } ++ verb = self.verbose_logger.isEnabledFor(yum.logginglevels.DEBUG_3) ++ wts = {'hidden' : False, ++ 'lang' : None, ++ 'env' : None, ++ 'pkg' : None, ++ 'inst' : None, ++ 'avail' : None, ++ 'id' : verb} - if len(userlist) > 0: -@@ -1254,7 +1567,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput): - msg += ' (%s)' % group.groupid - if group.langonly: - msg += ' [%s]' % group.langonly +- if len(userlist) > 0: +- if userlist[0] == 'hidden': +- uservisible=0 +- userlist.pop(0) ++ ouserlist = userlist[:] ++ while userlist: ++ arg = userlist[0] ++ val = True ++ if arg.startswith('no'): ++ arg = arg[2:] ++ val = False ++ if arg not in wts_map: ++ break ++ wts[wts_map[arg]] = val ++ userlist.pop(0) + if not userlist: + userlist = None # Match everything... + +- installed, available = self.doGroupLists(uservisible=uservisible, +- patterns=userlist) +- +- if not installed and not available: +- self.logger.error(_('Warning: No groups match: %s'), +- ", ".join(userlist)) +- return 0, [] ++ if wts['inst'] is None and wts['avail'] is None: ++ wts['inst'] = True ++ wts['avail'] = True + +- def _out_grp(sect, group): +- if not done: +- self.verbose_logger.log(yum.logginglevels.INFO_2, sect) +- msg = ' %s' % group.ui_name +- if self.verbose_logger.isEnabledFor(yum.logginglevels.DEBUG_3): +- msg += ' (%s)' % group.groupid +- if group.langonly: +- msg += ' [%s]' % group.langonly - self.verbose_logger.log(yum.logginglevels.INFO_2, '%s', msg) -+ self.verbose_logger.info('%s', msg) ++ if wts['lang'] is None and wts['pkg'] is None and wts['env'] is None: ++ wts['env'] = True ++ wts['pkg'] = True - done = False +- done = False +- for group in installed: +- if group.langonly: continue +- _out_grp(_('Installed Groups:'), group) +- done = True ++ uv = not wts['hidden'] ++ dGL = self.doGroupLists(patterns=userlist, ++ uservisible=uv, return_evgrps=True) + +- done = False +- for group in installed: +- if not group.langonly: continue +- _out_grp(_('Installed Language Groups:'), group) +- done = True ++ installed, available, ievgrps, evgrps = dGL + +- done = False +- for group in available: +- if group.langonly: continue +- _out_grp(_('Available Groups:'), group) +- done = True ++ if not wts['env']: ++ ievgrps = [] ++ evgrps = [] + +- done = False +- for group in available: +- if not group.langonly: continue +- _out_grp(_('Available Language Groups:'), group) +- done = True ++ if not wts['inst']: ++ installed = [] ++ ievgrps = [] ++ if not wts['avail']: ++ available = [] ++ evgrps = [] ++ ++ done = [] ++ def _out_grp(sect, groups): ++ if not groups: ++ return + +- return 0, [_('Done')] ++ done.append(sect) ++ if summary: ++ self.verbose_logger.log(yum.logginglevels.INFO_2, ++ "%s %u", sect, len(groups)) ++ return + +- def returnGroupSummary(self, userlist): ++ self.verbose_logger.log(yum.logginglevels.INFO_2, sect) + +- uservisible=1 +- +- if len(userlist) > 0: +- if userlist[0] == 'hidden': +- uservisible=0 +- userlist.pop(0) +- if not userlist: +- userlist = None # Match everything... ++ for group in groups: ++ msg = ' %s' % group.ui_name ++ if wts['id']: ++ msg += ' (%s)' % group.compsid ++ if group.langonly: ++ msg += ' [%s]' % group.langonly ++ self.verbose_logger.info('%s', msg) + +- installed, available = self.doGroupLists(uservisible=uservisible, +- patterns=userlist) +- +- def _out_grp(sect, num): +- if not num: +- return +- self.verbose_logger.log(yum.logginglevels.INFO_2, '%s %u', sect,num) +- done = 0 ++ _out_grp(_('Installed Environment Groups:'), ievgrps) ++ _out_grp(_('Available Environment Groups:'), evgrps) ++ ++ groups = [] for group in installed: -@@ -1283,7 +1596,20 @@ class YumBaseCli(yum.YumBase, output.YumOutput): - return 0, [_('Done')] + if group.langonly: continue +- done += 1 +- _out_grp(_('Installed Groups:'), done) ++ if not wts['pkg']: continue ++ groups.append(group) ++ _out_grp(_('Installed Groups:'), groups) - def returnGroupSummary(self, userlist): +- done = 0 ++ groups = [] + for group in installed: + if not group.langonly: continue +- done += 1 +- _out_grp(_('Installed Language Groups:'), done) ++ if not wts['lang']: continue ++ groups.append(group) ++ _out_grp(_('Installed Language Groups:'), groups) + +- done = False ++ groups = [] + for group in available: + if group.langonly: continue +- done += 1 +- _out_grp(_('Available Groups:'), done) ++ if not wts['pkg']: continue ++ groups.append(group) ++ _out_grp(_('Available Groups:'), groups) + +- done = False ++ groups = [] + for group in available: + if not group.langonly: continue +- done += 1 +- _out_grp(_('Available Language Groups:'), done) ++ if not wts['lang']: continue ++ groups.append(group) ++ _out_grp(_('Available Language Groups:'), groups) ++ ++ if not done: ++ self.logger.error(_('Warning: No Environments/Groups match: %s'), ++ ", ".join(ouserlist)) ++ return 0, [] + + return 0, [_('Done')] ++ ++ def returnGroupSummary(self, userlist): + """Print a summary of the groups that match the given names or + wildcards. + @@ -991,16 +1258,12 @@ index 6056d38..afa7447 + :return: (exit_code, [ errors ]) + + exit_code is:: - ++ + 0 = we're done, exit + 1 = we've errored, exit with error string + 2 = we've got work yet to do, onto the next stage + """ - uservisible=1 - - if len(userlist) > 0: -@@ -1327,7 +1653,19 @@ class YumBaseCli(yum.YumBase, output.YumOutput): - return 0, [_('Done')] ++ return self._returnGroupLists(userlist, summary=True) def returnGroupInfo(self, userlist): - """returns complete information on a list of groups""" @@ -1019,8 +1282,31 @@ index 6056d38..afa7447 + """ for strng in userlist: group_matched = False - for group in self.comps.return_groups(strng): -@@ -1339,9 +1677,19 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +- for group in self.comps.return_groups(strng): +- self.displayPkgsInGroups(group) +- group_matched = True ++ ++ pkg_grp = True ++ grp_grp = True ++ if strng.startswith('@^'): ++ strng = strng[2:] ++ pkg_grp = False ++ elif strng.startswith('@'): ++ strng = strng[1:] ++ grp_grp = False ++ ++ if grp_grp: ++ for evgroup in self.comps.return_environments(strng): ++ self.displayGrpsInEnvironments(evgroup) ++ group_matched = True ++ if pkg_grp: ++ for group in self.comps.return_groups(strng): ++ self.displayPkgsInGroups(group) ++ group_matched = True + + if not group_matched: +- self.logger.error(_('Warning: Group %s does not exist.'), strng) ++ self.logger.error(_('Warning: Group/Environment %s does not exist.'), strng) return 0, [] @@ -1043,8 +1329,38 @@ index 6056d38..afa7447 pkgs_used = [] for group_string in grouplist: -@@ -1351,7 +1699,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput): ++ ++ grp_grp = True ++ pkg_grp = True ++ if group_string.startswith('@^'): ++ pkg_grp = False ++ group_string = group_string[2:] ++ elif group_string.startswith('@'): ++ grp_grp = False ++ group_string = group_string[1:] ++ + group_matched = False +- for group in self.comps.return_groups(group_string): ++ groups = [] ++ if grp_grp: ++ groups = self.comps.return_environments(group_string) ++ for group in groups: + group_matched = True ++ try: ++ txmbrs = self.selectEnvironment(group.environmentid, ++ upgrade=upgrade) ++ except yum.Errors.GroupsError: ++ self.logger.critical(_('Warning: Environment %s does not exist.'), group_string) ++ continue ++ else: ++ pkgs_used.extend(txmbrs) ++ ++ groups = [] ++ if pkg_grp: ++ groups = self.comps.return_groups(group_string) ++ for group in groups: ++ group_matched = True try: - txmbrs = self.selectGroup(group.groupid) @@ -1052,7 +1368,7 @@ index 6056d38..afa7447 except yum.Errors.GroupsError: self.logger.critical(_('Warning: Group %s does not exist.'), group_string) continue -@@ -1368,8 +1716,18 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -1368,17 +1849,61 @@ class YumBaseCli(yum.YumBase, output.YumOutput): return 2, [P_('%d package to Install', '%d packages to Install', len(pkgs_used)) % len(pkgs_used)] def removeGroups(self, grouplist): @@ -1062,17 +1378,67 @@ index 6056d38..afa7447 + :param grouplist: a list of names or wildcards specifying + groups to be removed + :return: (exit_code, [ errors ]) -+ -+ exit_code is:: ++ exit_code is:: ++ + 0 = we're done, exit + 1 = we've errored, exit with error string + 2 = we've got work yet to do, onto the next stage + """ pkgs_used = [] for group_string in grouplist: - try: -@@ -1389,7 +1747,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +- try: +- txmbrs = self.groupRemove(group_string) +- except yum.Errors.GroupsError: +- self.logger.critical(_('No group named %s exists'), group_string) +- continue +- else: +- pkgs_used.extend(txmbrs) ++ ++ grp_grp = True ++ pkg_grp = True ++ if group_string.startswith('@^'): ++ pkg_grp = False ++ group_string = group_string[2:] ++ elif group_string.startswith('@'): ++ grp_grp = False ++ group_string = group_string[1:] ++ ++ groups = [] ++ if grp_grp: ++ if self.conf.group_command == 'objects': ++ groups = self.igroups.return_environments(group_string) ++ else: ++ groups = self.comps.return_environments(group_string) ++ if not groups: ++ self.logger.critical(_('No Environment named %s exists'), group_string) ++ for group in groups: ++ try: ++ txmbrs = self.environmentRemove(group.environmentid) ++ except yum.Errors.GroupsError: ++ continue ++ else: ++ pkgs_used.extend(txmbrs) ++ ++ groups = [] ++ if pkg_grp: ++ if self.conf.group_command == 'objects': ++ groups = self.igroups.return_groups(group_string) ++ else: ++ groups = self.comps.return_groups(group_string) ++ if not groups: ++ self.logger.critical(_('No group named %s exists'), group_string) ++ for group in groups: ++ try: ++ txmbrs = self.groupRemove(group.groupid) ++ except yum.Errors.GroupsError: ++ continue ++ else: ++ pkgs_used.extend(txmbrs) + + if not pkgs_used: + return 0, [_('No packages to remove from groups')] +@@ -1389,7 +1914,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput): def _promptWanted(self): # shortcut for the always-off/always-on options @@ -1081,7 +1447,7 @@ index 6056d38..afa7447 return False if self.conf.alwaysprompt: return True -@@ -1400,7 +1758,6 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -1400,7 +1925,6 @@ class YumBaseCli(yum.YumBase, output.YumOutput): # package wasn't explictly given on the command line for txmbr in self.tsInfo.getMembers(): if txmbr.isDep or \ @@ -1089,7 +1455,7 @@ index 6056d38..afa7447 txmbr.name not in self.extcmds: return True -@@ -1408,11 +1765,11 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -1408,11 +1932,11 @@ class YumBaseCli(yum.YumBase, output.YumOutput): return False def usage(self): @@ -1103,7 +1469,7 @@ index 6056d38..afa7447 sys.stdout.write(self.optparser.get_usage()) def _installable(self, pkg, ematch=False): -@@ -1468,9 +1825,9 @@ class YumBaseCli(yum.YumBase, output.YumOutput): +@@ -1468,9 +1992,9 @@ class YumBaseCli(yum.YumBase, output.YumOutput): return False class YumOptionParser(OptionParser): @@ -1115,7 +1481,7 @@ index 6056d38..afa7447 def __init__(self,base, **kwargs): # check if this is called with a utils=True/False parameter -@@ -1488,13 +1845,23 @@ class YumOptionParser(OptionParser): +@@ -1488,13 +2012,23 @@ class YumOptionParser(OptionParser): self._addYumBasicOptions() def error(self, msg): @@ -1141,7 +1507,7 @@ index 6056d38..afa7447 try: args = _filtercmdline( ('--noplugins','--version','-q', '-v', "--quiet", "--verbose"), -@@ -1521,7 +1888,15 @@ class YumOptionParser(OptionParser): +@@ -1521,7 +2055,15 @@ class YumOptionParser(OptionParser): return ret def setupYumConfig(self, args=None): @@ -1158,7 +1524,13 @@ index 6056d38..afa7447 if not args: (opts, cmds) = self.parse_args() else: -@@ -1536,13 +1911,14 @@ class YumOptionParser(OptionParser): +@@ -1533,16 +2075,20 @@ class YumOptionParser(OptionParser): + try: + # config file is parsed and moving us forward + # set some things in it. ++ ++ if opts.tolerant or self.base.conf.tolerant: # Make it slower capt. ++ self.base.conf.recheck_installed_requires = True # Handle remaining options if opts.assumeyes: @@ -1178,7 +1550,7 @@ index 6056d38..afa7447 self.base.conf.cache = 1 if opts.obsoletes: -@@ -1610,10 +1986,6 @@ class YumOptionParser(OptionParser): +@@ -1610,10 +2156,6 @@ class YumOptionParser(OptionParser): self.base.usage() sys.exit(1) @@ -1189,7 +1561,7 @@ index 6056d38..afa7447 # Disable all gpg key checking, if requested. if opts.nogpgcheck: # Altering the normal configs. doesn't work too well, esp. with -@@ -1640,6 +2012,14 @@ class YumOptionParser(OptionParser): +@@ -1640,10 +2182,18 @@ class YumOptionParser(OptionParser): sys.exit(1) def getRoot(self,opts): @@ -1204,7 +1576,22 @@ index 6056d38..afa7447 self._checkAbsInstallRoot(opts) # If the conf file is inside the installroot - use that. # otherwise look for it in the normal root -@@ -1713,6 +2093,10 @@ class YumOptionParser(OptionParser): +- if opts.installroot: ++ if opts.installroot and opts.installroot.lstrip('/'): + if os.access(opts.installroot+'/'+opts.conffile, os.R_OK): + opts.conffile = opts.installroot+'/'+opts.conffile + elif opts.conffile == '/etc/yum/yum.conf': +@@ -1701,6 +2251,9 @@ class YumOptionParser(OptionParser): + group.add_option("--showduplicates", dest="showdupesfromrepos", + action="store_true", + help=_("show duplicates, in repos, in list/search commands")) ++ group.add_option("--show-duplicates", dest="showdupesfromrepos", ++ action="store_true", ++ help=SUPPRESS_HELP) + group.add_option("-e", "--errorlevel", dest="errorlevel", default=None, + help=_("error output level"), type='int', + metavar='[error level]') +@@ -1713,6 +2266,10 @@ class YumOptionParser(OptionParser): help=_("verbose operation")) group.add_option("-y", "--assumeyes", dest="assumeyes", action="store_true", help=_("answer yes for all questions")) @@ -1217,10 +1604,10 @@ index 6056d38..afa7447 group.add_option("--installroot", help=_("set install root"), diff --git a/completion-helper.py b/completion-helper.py new file mode 100755 -index 0000000..e4164f7 +index 0000000..05801d1 --- /dev/null +++ b/completion-helper.py -@@ -0,0 +1,83 @@ +@@ -0,0 +1,90 @@ +#!/usr/bin/python -t +# -*- coding: utf-8 -*- +# @@ -1246,6 +1633,7 @@ index 0000000..e4164f7 + +import cli +import yumcommands ++from yum.Errors import GroupsError, ConfigError + + +class GroupsCompletionCommand(yumcommands.GroupsCommand): @@ -1286,7 +1674,8 @@ index 0000000..e4164f7 + +def get_pattern(extcmds): + if len(extcmds) > 1 and extcmds[-1]: -+ return shlex.split(extcmds[-1])[0] + "*" ++ try: return shlex.split(extcmds[-1])[0] + "*" ++ except ValueError: pass + return "*" + +def main(args): @@ -1297,7 +1686,12 @@ index 0000000..e4164f7 + base.registerCommand(RepoListCompletionCommand()) + base.getOptionsConfig(args) + base.parseCommands() -+ base.doCommands() ++ try: ++ for repo in base.repos.listEnabled(): ++ repo.skip_if_unavailable = True ++ base.doCommands() ++ except (GroupsError, ConfigError), e: ++ base.logger.error(e) + +if __name__ == "__main__": + try: @@ -1903,7 +2297,7 @@ index 0000000..d2a0ed1 +if __name__ == "__main__": + generateAll(os.getcwd(), os.getcwd()) diff --git a/docs/yum.8 b/docs/yum.8 -index 1a8202a..604377b 100644 +index 1a8202a..dab01f6 100644 --- a/docs/yum.8 +++ b/docs/yum.8 @@ -52,6 +52,7 @@ gnome\-packagekit application\&. @@ -1914,8 +2308,12 @@ index 1a8202a..604377b 100644 .br .I \fR * localinstall rpmfile1 [rpmfile2] [\&.\&.\&.] (maintained for legacy reasons only - use install) -@@ -69,7 +70,9 @@ gnome\-packagekit application\&. +@@ -67,9 +68,13 @@ gnome\-packagekit application\&. .br + .I \fR * repolist [all|enabled|disabled] + .br ++.I \fR * repoinfo [all|enabled|disabled] ++.br .I \fR * version [ all | installed | available | group-* | nogroups* | grouplist | groupinfo ] .br -.I \fR * history [info|list|packages-list|summary|addon-info|redo|undo|rollback|new] @@ -1925,8 +2323,17 @@ index 1a8202a..604377b 100644 .br .I \fR * check .br -@@ -91,9 +94,14 @@ glob and any matches are then installed\&. If the name starts with an - command\&. If the name starts with a - character, then a search is done within +@@ -86,14 +91,20 @@ Is used to install the latest version of a package or + group of packages while ensuring that all dependencies are + satisfied\&. (See \fBSpecifying package names\fP for more information) + If no package matches the given package name(s), they are assumed to be a shell +-glob and any matches are then installed\&. If the name starts with an +-@ character the rest of the name is used as though passed to the groupinstall +-command\&. If the name starts with a - character, then a search is done within ++glob and any matches are then installed\&. If the name starts with @^ then it ++is treated as an environment group (group install @^foo), an @ character and ++it's treated as a group (plain group install)\&. If the name starts with ++a - character, then a search is done within the transaction and any matches are removed. If the name is a file, then install works like localinstall\&. If the name doesn't match a package, then package -"provides" are searched (Eg. "_sqlitecache.so()(64bit)") as are @@ -1941,7 +2348,7 @@ index 1a8202a..604377b 100644 .IP .IP "\fBupdate\fP" If run without any packages, update will update every currently -@@ -111,7 +119,7 @@ changes, for example: upgrading from somelinux 8.0 to somelinux 9. +@@ -111,7 +122,7 @@ changes, for example: upgrading from somelinux 8.0 to somelinux 9. Note that "\fBupdate\fP" works on installed packages first, and only if there are no matches does it look for available packages. The difference is most @@ -1950,7 +2357,36 @@ index 1a8202a..604377b 100644 "\fBupdate\fP foo" if foo-1-2 is installed. You can use the "\fBupdate-to\fP" if you'd prefer that nothing happen in the above case. .IP -@@ -224,10 +232,37 @@ to only remove packages which aren't required by something else. +@@ -158,6 +169,11 @@ the "install" command\&.(See \fBSpecifying package names\fP for more information + + Note that "yum" is included in the protected_packages configuration, by default. + So you can't accidentally remove yum itself. ++ ++Because remove does a lot of work to make it as easy as possible to use, there ++are also a few specific remove commands "\fBremove-n\fP", "\fBremove-na\fP" ++and "\fBremove-nevra\fP". These only work on package names, and do not process ++wildcards etc. + .IP + .IP "\fBlist\fP" + Is used to list various information about available +@@ -209,10 +225,12 @@ installed. + "\fBgroup list\fP" is used to list the available groups from all \fByum\fP repos. Groups are marked + as "installed" if all mandatory packages are installed, or if a group doesn't + have any mandatory packages then it is installed if any of the optional or +-default package are installed. +-The optional "hidden" argument will also list groups marked as not being +-"user visible". If you pass the \-v option, to enable verbose mode, then the +-groupids are displayed. ++default package are installed (when not in group_command=objects mode). ++You can pass optional arguments to the list/summary commands: installed, ++available, environment, language, packages, hidden and ids (or any of those ++prefixed by "no" to turn them off again). ++If you pass the \-v option, to enable verbose mode, then the groupids are ++displayed by default (but "yum group list ids" is often easier to read). + + "\fBgroup remove\fP" is used to remove all of the packages in a group, unlike "groupinstall" this + will remove everything regardless of group_package_types. It is worth pointing +@@ -224,10 +242,37 @@ to only remove packages which aren't required by something else. "\fBgroup info\fP" is used to give the description and package list of a group (and which type those packages are marked as). Note that you can use the yum-filter-data and @@ -1989,7 +2425,7 @@ index 1a8202a..604377b 100644 .IP .IP "\fBshell\fP" Is used to enter the 'yum shell', when a filename is specified the contents of -@@ -235,12 +270,13 @@ that file is executed in yum shell mode. See \fIyum-shell(8)\fP for more info +@@ -235,12 +280,13 @@ that file is executed in yum shell mode. See \fIyum-shell(8)\fP for more info .IP .IP "\fBresolvedep\fP" Is used to list packages providing the specified dependencies, at most one @@ -2005,7 +2441,7 @@ index 1a8202a..604377b 100644 reasons only. .IP .IP "\fBlocalupdate\fP" -@@ -248,7 +284,7 @@ Is used to update the system by specifying local rpm files. Only the specified +@@ -248,7 +294,7 @@ Is used to update the system by specifying local rpm files. Only the specified rpm files of which an older version is already installed will be installed, the remaining specified packages will be ignored. If required the enabled repositories will be used to resolve dependencies. Note @@ -2014,7 +2450,7 @@ index 1a8202a..604377b 100644 legacy reasons only. .IP .IP "\fBreinstall\fP" -@@ -260,7 +296,7 @@ on groups, files, provides and filelists just like the "install" command\&. +@@ -260,7 +306,7 @@ on groups, files, provides and filelists just like the "install" command\&. Will try and downgrade a package from the version currently installed to the previously highest version (or the specified version). The depsolver will not necessarily work, but if you specify all the packages it @@ -2023,7 +2459,14 @@ index 1a8202a..604377b 100644 work for "installonly" packages, like Kernels. downgrade operates on groups, files, provides, filelists and rpm files just like the "install" command\&. .IP -@@ -294,8 +330,8 @@ package counts/etc. will be zeroed out). +@@ -291,11 +337,15 @@ then yum will ignore any repo errors and output the information it can get + (Eg. "yum clean all; yum -C repolist" will output something, although the + package counts/etc. will be zeroed out). + .IP ++.IP "\fBrepoinfo\fP" ++.IP ++This ocmmand works exactly like repolist -v. ++.IP .IP "\fBversion\fP" Produces a "version" of the rpmdb, and of the enabled repositories if "all" is given as the first argument. You can also specify version groups in the @@ -2034,7 +2477,7 @@ index 1a8202a..604377b 100644 packages (in sorted order), and the checksum_type/checksum_data entries from the yumdb. Note that this rpmdb version is now also used significantly within yum (esp. in yum history). -@@ -321,26 +357,33 @@ and so takes sub-commands: +@@ -321,26 +371,33 @@ and so takes sub-commands: .IP "\fBhistory\fP" The history command allows the user to view what has happened in past transactions (assuming the history_record config. option is set). You can use @@ -2074,7 +2517,7 @@ index 1a8202a..604377b 100644 A (if it is not still installed), and "rollback 1" will try to remove packages B and C. Note that after a "rollback 1" you will have a fourth transaction, although the ending rpmdb version (see: yum version) should be the same in -@@ -349,6 +392,12 @@ transactions 1 and 4. +@@ -349,6 +406,12 @@ transactions 1 and 4. The addon-info command takes a transaction ID, and the packages-list command takes a package (with wildcards). @@ -2087,7 +2530,7 @@ index 1a8202a..604377b 100644 In "history list" you can change the behaviour of the 2nd column via. the configuration option history_list_view. -@@ -371,6 +420,15 @@ end of the package column in the packages-list command). +@@ -371,6 +434,15 @@ end of the package column in the packages-list command). .I \fBs\fR - The transaction completed fine, but --skip-broken was enabled and had to skip some packages. .br @@ -2103,7 +2546,7 @@ index 1a8202a..604377b 100644 .IP .IP "\fBcheck\fP" Checks the local rpmdb and produces information on any problems it finds. You -@@ -401,6 +459,11 @@ Assume yes; assume that the answer to any question which would be asked +@@ -401,6 +473,11 @@ Assume yes; assume that the answer to any question which would be asked is yes\&. .br Configuration Option: \fBassumeyes\fP @@ -2115,7 +2558,7 @@ index 1a8202a..604377b 100644 .IP "\fB\-c, \-\-config=[config file]\fP" Specifies the config file location - can take HTTP and FTP URLs and local file paths\&. -@@ -420,7 +483,7 @@ Sets the error level to [number] Practical range 0 \- 10. 0 means print only cri +@@ -420,7 +497,7 @@ Sets the error level to [number] Practical range 0 \- 10. 0 means print only cri .br Configuration Option: \fBerrorlevel\fP .IP "\fB\-\-rpmverbosity=[name]\fP" @@ -2124,8 +2567,18 @@ index 1a8202a..604377b 100644 options are: 'critical', 'emergency', 'error', 'warn' and 'debug'. .br Configuration Option: \fBrpmverbosity\fP +@@ -506,7 +583,8 @@ option will corrupt your cache (and you can use $releasever in your cachedir + configuration to stop this). + .PP + .IP "\fB\-t, \-\-tolerant\fP" +-This option currently does nothing. ++This option makes yum go slower, checking for things that shouldn't be possible ++making it more tolerant of external errors. + .br + .IP "\fB\-\-setopt=option=value\fP" + Set any config option in yum config or repo files. For options in the global diff --git a/docs/yum.conf.5 b/docs/yum.conf.5 -index 515aa73..df172fa 100644 +index 515aa73..029fa75 100644 --- a/docs/yum.conf.5 +++ b/docs/yum.conf.5 @@ -114,15 +114,27 @@ are causing problems from the transaction. @@ -2199,7 +2652,7 @@ index 515aa73..df172fa 100644 \fBinstallroot \fR Specifies an alternative installroot, relative to which all packages will be installed. -@@ -300,6 +337,14 @@ with the \fBthrottle\fR option (above). If \fBthrottle\fR is a percentage and +@@ -300,6 +337,21 @@ with the \fBthrottle\fR option (above). If \fBthrottle\fR is a percentage and ignored. Default is `0' (no bandwidth throttling). .IP @@ -2210,11 +2663,18 @@ index 515aa73..df172fa 100644 + +`6' or `IPv6': resolve to IPv6 addresses only. + ++.IP ++\fBmax_connections \fR ++ ++The maximum number of simultaneous connections. This overrides the urlgrabber ++default of 5 connections. Note that there are also implicit per-mirror limits ++and the downloader honors these too. ++ +.IP \fBsslcacert \fR Path to the directory containing the databases of the certificate authorities yum should use to verify SSL certificates. Defaults to none - uses system -@@ -331,6 +376,15 @@ Path to the SSL client key yum should use to connect to repos/remote sites +@@ -331,6 +383,15 @@ Path to the SSL client key yum should use to connect to repos/remote sites Defaults to none. .IP @@ -2230,10 +2690,32 @@ index 515aa73..df172fa 100644 \fBhistory_record \fR Boolean - should yum record history entries for transactions. This takes some disk space, and some extra time in the transactions. But it allows how to know a -@@ -595,6 +649,23 @@ package's dependencies. If any of them are no longer required by any other - package then also mark them to be removed. - Boolean (1, 0, True, False, yes,no) Defaults to False +@@ -480,6 +541,19 @@ not listed above is the other metadata, which contains the changelog information + which is used by yum-changelog. This is what "yum makecache" uses. + .IP ++\fBmddownloadpolicy \fR ++You can select which kinds of repodata you would prefer yum to download: ++ ++`sqlite' - Download the .sqlite files, if available. This is currently slightly ++faster, once they are downloaded. However these files tend to be bigger, and ++thus. take longer to download. ++ ++`sqlite' - Download the .XML files, which yum will do anyway as a fallback on ++the other options. These files tend to be smaller, but they require ++parsing/converting locally after download and some aditional checks are ++performed on them each time they are used. ++ ++.IP + \fBmultilib_policy \fR + Can be set to 'all' or 'best'. All means install all possible arches for any package you + want to install. Therefore yum install foo will install foo.i386 and foo.x86_64 on x86_64, +@@ -593,8 +667,53 @@ See color_list_installed_older for possible values. + When removing packages (by removal, update or obsoletion) go through each + package's dependencies. If any of them are no longer required by any other + package then also mark them to be removed. ++Boolean (1, 0, True, False, yes, no) Defaults to False ++ +.IP +\fBupgrade_requirements_on_install \fR +When installing/reinstalling/upgrading packages go through each package's @@ -2245,16 +2727,44 @@ index 515aa73..df172fa 100644 +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 -+ + 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 True. ++ ++\fBexit_on_lock\fR ++Should the yum client exit immediately when something else has the lock. ++Boolean (1, 0, True, False, yes, no) Defaults to False ++ ++.IP ++\fBloadts_ignoremissing\fR ++Should the load-ts command ignore packages that are missing. This includes ++packages in the TS to be removed, which aren't installed, and packages in the ++TS to be added, which aren't available. ++If this is set to true, and an rpm is missing then loadts_ignorenewrpm is ++automatically set to true. ++Boolean (1, 0, True, False, yes, no) Defaults to False ++ ++.IP ++\fBloadts_ignorerpm\fR ++Should the load-ts command ignore the rpmdb version (yum version nogroups) or ++abort if there is a mismatch between the TS file and the current machine. ++If this is set to true, then loadts_ignorenewrpm is automatically set to true. ++Boolean (1, 0, True, False, yes, no) Defaults to False ++ ++.IP ++\fBloadts_ignorenewrpm\fR ++Should the load-ts command ignore the future rpmdb version or ++abort if there is a mismatch between the TS file and what will happen on the ++current machine. ++Note that if loadts_ignorerpm is True, this option does nothing. ++Boolean (1, 0, True, False, yes, no) Defaults to False .SH "[repository] OPTIONS" -@@ -755,6 +826,11 @@ repository. +@@ -755,6 +874,11 @@ repository. Overrides the \fBbandwidth\fR option from the [main] section for this repository. @@ -2266,7 +2776,7 @@ index 515aa73..df172fa 100644 .IP \fBsslcacert \fR -@@ -776,6 +852,10 @@ repository. +@@ -776,6 +900,10 @@ repository. Overrides the \fBsslclientkey\fR option from the [main] section for this repository. @@ -2277,6 +2787,18 @@ index 515aa73..df172fa 100644 .IP \fBmetadata_expire \fR +@@ -824,7 +952,11 @@ as greater/less than any other. defaults to 1000 + If set to True yum will continue running if this repository cannot be + contacted for any reason. This should be set carefully as all repos are consulted + for any given command. Defaults to False. ++ + .IP ++\fBasync \fR ++If set to True Yum will download packages and metadata from this repo in ++parallel, if possible. Defaults to True. + + .SH "URL INCLUDE SYNTAX" + .LP diff --git a/etc/0yum.cron b/etc/0yum.cron deleted file mode 100755 index 0cfaa4b..0000000 @@ -2631,7 +3153,7 @@ index c60fa08..0000000 -ts run -exit diff --git a/etc/yum.bash b/etc/yum.bash -index f1e06e8..5945c6f 100644 +index f1e06e8..b21c594 100644 --- a/etc/yum.bash +++ b/etc/yum.bash @@ -1,53 +1,17 @@ @@ -2641,7 +3163,8 @@ index f1e06e8..5945c6f 100644 -# 1 = argument to "yum list" (all, available, updates etc) -# 2 = current word to be completed -_yum_list() --{ ++_yum_helper() + { - # Fail fast for things that look like paths. - [[ $2 == */* || $2 == [.~]* ]] && return - @@ -2664,8 +3187,7 @@ index f1e06e8..5945c6f 100644 -# 1 = argument to "yum repolist" (enabled, disabled etc) -# 2 = current word to be completed -_yum_repolist() -+_yum_helper() - { +-{ - # TODO: add -d 0 when http://yum.baseurl.org/ticket/29 is fixed - # (for now --noplugins is used to get rid of "Loaded plugins: ...") - # Drop first ("repo id repo name") and last ("repolist: ...") rows - @@ -2690,8 +3212,8 @@ index f1e06e8..5945c6f 100644 - COMPREPLY=( $( compgen -W "$( ${yum:-yum} -C grouplist $1 "$2*" \ - 2>/dev/null | sed -ne 's/^[[:space:]]\{1,\}\(.\{1,\}\)/\1/p' )" \ - -- "$2" ) ) -+ # Fail fast for things that look like paths. -+ [[ $2 == */* || $2 == [.~]* ]] && return ++ # Fail fast for things that look like paths or options. ++ [[ $2 == */* || $2 == [.~-]* ]] && return + _yum_helper list "$@" } @@ -2759,8 +3281,8 @@ index f1e06e8..5945c6f 100644 - groupinfo groupinstall grouplist groupremove help history info install - list makecache provides reinstall remove repolist resolvedep search - shell update upgrade version ) -+ groups help history info install list makecache provides reinstall -+ remove repolist search shell update upgrade version ) ++ groups help history info install list load-transaction makecache provides ++ reinstall remove repolist search shell update upgrade version ) local i c cmd subcmd for (( i=1; i < ${#words[@]}-1; i++ )) ; do @@ -2769,7 +3291,7 @@ index f1e06e8..5945c6f 100644 for c in ${cmds[@]} check-rpmdb distribution-synchronization erase \ - groupupdate grouperase localinstall localupdate whatprovides ; do + group groupinfo groupinstall grouplist groupremove groupupdate \ -+ grouperase install-na localinstall localupdate whatprovides ; do ++ grouperase install-na load-ts localinstall localupdate whatprovides ; do [[ ${words[i]} == $c ]] && cmd=$c && break done done @@ -2889,7 +3411,7 @@ index f1e06e8..5945c6f 100644 ;; esac return 0 -@@ -288,13 +285,20 @@ _yum() +@@ -288,42 +285,56 @@ _yum() ;; install) @@ -2912,7 +3434,16 @@ index f1e06e8..5945c6f 100644 COMPREPLY=( $( compgen -W 'all available updates installed extras obsoletes recent' -- "$cur" ) ) return 0 -@@ -306,24 +310,26 @@ _yum() + ;; + ++ load-transaction|load-ts) ++ COMPREPLY=( $( compgen -f -o plusdirs -X '!*.yumtx' -- "$cur" ) ) ++ return 0 ++ ;; ++ + localinstall|localupdate) + _yum_binrpmfiles "$cur" + return 0 ;; repolist) @@ -2944,7 +3475,7 @@ index f1e06e8..5945c6f 100644 COMPREPLY=( $( compgen -W 'all installed available nogroups grouplist groupinfo' -- "$cur" ) ) return 0 -@@ -337,7 +343,11 @@ _yum() +@@ -337,7 +348,11 @@ _yum() $split && return 0 @@ -2958,7 +3489,7 @@ index f1e06e8..5945c6f 100644 complete -F _yum -o filenames yum yummain.py diff --git a/output.py b/output.py -index b6aa277..b3f2c75 100755 +index b6aa277..05b255e 100755 --- a/output.py +++ b/output.py @@ -1,6 +1,6 @@ @@ -2969,6 +3500,15 @@ index b6aa277..b3f2c75 100755 # 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 +@@ -29,7 +29,7 @@ import re # For YumTerm + + from weakref import proxy as weakref + +-from urlgrabber.progress import TextMeter ++from urlgrabber.progress import TextMeter, TextMultiFileMeter + import urlgrabber.progress + from urlgrabber.grabber import URLGrabError + from yum.misc import prco_tuple_to_string @@ -47,6 +47,21 @@ import yum.history from yum.i18n import utf8_width, utf8_width_fill, utf8_text_fill @@ -2991,7 +3531,7 @@ index b6aa277..b3f2c75 100755 def _term_width(): """ Simple terminal width, limit to 20 chars. and make 0 == 80. """ if not hasattr(urlgrabber.progress, 'terminal_width_cached'): -@@ -60,17 +75,21 @@ def _term_width(): +@@ -60,17 +75,26 @@ def _term_width(): class YumTextMeter(TextMeter): @@ -3012,13 +3552,18 @@ index b6aa277..b3f2c75 100755 checkSignals() TextMeter.update(self, amount_read, now) ++class YumTextMultiFileMeter(TextMultiFileMeter): ++ def update_meter(self, meter, now): ++ checkSignals() ++ TextMultiFileMeter.update_meter(self, meter, now) ++ class YumTerm: - """some terminal "UI" helpers based on curses""" + """A class to provide some terminal "UI" helpers based on curses.""" # From initial search for "terminfo and python" got: # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/475116 -@@ -145,6 +164,17 @@ class YumTerm: +@@ -145,6 +169,17 @@ class YumTerm: self.BG_COLOR = self.__ansi_forced_BG_COLOR def reinit(self, term_stream=None, color='auto'): @@ -3036,7 +3581,7 @@ index b6aa277..b3f2c75 100755 self.__enabled = True if not hasattr(urlgrabber.progress, 'terminal_width_cached'): self.columns = 80 -@@ -255,6 +285,37 @@ class YumTerm: +@@ -255,6 +290,37 @@ class YumTerm: return re.sub(r'\$<\d+>[/*]?', '', cap) def sub(self, haystack, beg, end, needles, escape=None, ignore_case=False): @@ -3074,7 +3619,7 @@ index b6aa277..b3f2c75 100755 if not self.__enabled: return haystack -@@ -269,27 +330,106 @@ class YumTerm: +@@ -269,27 +335,106 @@ class YumTerm: haystack = re.sub(pat, render, haystack) return haystack def sub_norm(self, haystack, beg, needles, **kwds): @@ -3185,7 +3730,7 @@ index b6aa277..b3f2c75 100755 def __init__(self): self.logger = logging.getLogger("yum.cli") -@@ -304,6 +444,12 @@ class YumOutput: +@@ -304,6 +449,12 @@ class YumOutput: def printtime(self): @@ -3198,7 +3743,7 @@ index b6aa277..b3f2c75 100755 months = [_('Jan'), _('Feb'), _('Mar'), _('Apr'), _('May'), _('Jun'), _('Jul'), _('Aug'), _('Sep'), _('Oct'), _('Nov'), _('Dec')] now = time.localtime(time.time()) -@@ -312,14 +458,27 @@ class YumOutput: +@@ -312,14 +463,27 @@ class YumOutput: return ret def failureReport(self, errobj): @@ -3228,7 +3773,7 @@ index b6aa277..b3f2c75 100755 progressbar(current, total, name) def _highlight(self, highlight): -@@ -368,9 +527,29 @@ class YumOutput: +@@ -368,9 +532,29 @@ class YumOutput: def calcColumns(self, data, columns=None, remainder_column=0, total_width=None, indent=''): @@ -3261,7 +3806,7 @@ index b6aa277..b3f2c75 100755 if total_width is None: total_width = self.term.columns -@@ -473,10 +652,20 @@ class YumOutput: +@@ -473,10 +657,20 @@ class YumOutput: return (val, width, hibeg, hiend) def fmtColumns(self, columns, msg=u'', end=u'', text_width=utf8_width): @@ -3286,7 +3831,7 @@ index b6aa277..b3f2c75 100755 total_width = len(msg) data = [] for col_data in columns[:-1]: -@@ -513,8 +702,18 @@ class YumOutput: +@@ -513,8 +707,18 @@ class YumOutput: def simpleList(self, pkg, ui_overflow=False, indent='', highlight=False, columns=None): @@ -3307,7 +3852,7 @@ index b6aa277..b3f2c75 100755 if columns is None: columns = (-40, -22, -16) # Old default ver = pkg.printVer() -@@ -526,9 +725,19 @@ class YumOutput: +@@ -526,9 +730,19 @@ class YumOutput: def simpleEnvraList(self, pkg, ui_overflow=False, indent='', highlight=False, columns=None): @@ -3330,7 +3875,7 @@ index b6aa277..b3f2c75 100755 if columns is None: columns = (-63, -16) # Old default envra = '%s%s' % (indent, str(pkg)) -@@ -538,7 +747,13 @@ class YumOutput: +@@ -538,7 +752,13 @@ class YumOutput: print self.fmtColumns(columns, text_width=len) def fmtKeyValFill(self, key, val): @@ -3345,7 +3890,7 @@ index b6aa277..b3f2c75 100755 val = to_str(val) keylen = utf8_width(key) cols = self.term.columns -@@ -553,6 +768,15 @@ class YumOutput: +@@ -553,6 +773,15 @@ class YumOutput: return ret def fmtSection(self, name, fill='='): @@ -3361,7 +3906,7 @@ index b6aa277..b3f2c75 100755 name = to_str(name) cols = self.term.columns - 2 name_len = utf8_width(name) -@@ -577,6 +801,12 @@ class YumOutput: +@@ -577,6 +806,12 @@ class YumOutput: return to_unicode(s) def infoOutput(self, pkg, highlight=False): @@ -3374,7 +3919,16 @@ index b6aa277..b3f2c75 100755 (hibeg, hiend) = self._highlight(highlight) print _("Name : %s%s%s") % (hibeg, to_unicode(pkg.name), hiend) print _("Arch : %s") % to_unicode(pkg.arch) -@@ -617,9 +847,22 @@ class YumOutput: +@@ -585,7 +820,7 @@ class YumOutput: + print _("Version : %s") % to_unicode(pkg.version) + print _("Release : %s") % to_unicode(pkg.release) + print _("Size : %s") % self.format_number(float(pkg.size)) +- print _("Repo : %s") % to_unicode(pkg.repoid) ++ print _("Repo : %s") % to_unicode(pkg.repo.ui_id) + if pkg.repoid == 'installed' and 'from_repo' in pkg.yumdb_info: + print _("From repo : %s") % to_unicode(pkg.yumdb_info.from_repo) + if self.verbose_logger.isEnabledFor(logginglevels.DEBUG_3): +@@ -617,9 +852,22 @@ class YumOutput: print "" def updatesObsoletesList(self, uotup, changetype, columns=None): @@ -3400,7 +3954,7 @@ index b6aa277..b3f2c75 100755 (changePkg, instPkg) = uotup if columns is not None: -@@ -640,12 +883,45 @@ class YumOutput: +@@ -640,12 +888,45 @@ class YumOutput: def listPkgs(self, lst, description, outputType, highlight_na={}, columns=None, highlight_modes={}): @@ -3452,7 +4006,7 @@ index b6aa277..b3f2c75 100755 if outputType in ['list', 'info']: thingslisted = 0 if len(lst) > 0: -@@ -654,7 +930,8 @@ class YumOutput: +@@ -654,7 +935,8 @@ class YumOutput: for pkg in sorted(lst): key = (pkg.name, pkg.arch) highlight = False @@ -3462,7 +4016,7 @@ index b6aa277..b3f2c75 100755 elif key not in highlight_na: highlight = highlight_modes.get('not in', 'normal') elif pkg.verEQ(highlight_na[key]): -@@ -679,8 +956,11 @@ class YumOutput: +@@ -679,8 +961,11 @@ class YumOutput: def userconfirm(self): @@ -3475,7 +4029,7 @@ index b6aa277..b3f2c75 100755 yui = (to_unicode(_('y')), to_unicode(_('yes'))) nui = (to_unicode(_('n')), to_unicode(_('no'))) aui = (yui[0], yui[1], nui[0], nui[1]) -@@ -739,27 +1019,58 @@ class YumOutput: +@@ -739,27 +1024,58 @@ class YumOutput: return ret def _calcDataPkgColumns(self, data, pkg_names, pkg_names2pkgs, @@ -3538,7 +4092,7 @@ index b6aa277..b3f2c75 100755 continue for (apkg, ipkg) in sorted(pkg_names2pkgs[item], key=lambda x: x[1] or x[0]): -@@ -770,18 +1081,38 @@ class YumOutput: +@@ -770,18 +1086,38 @@ class YumOutput: else: highlight = False self.simpleEnvraList(ipkg or apkg, ui_overflow=True, @@ -3554,8 +4108,10 @@ index b6aa277..b3f2c75 100755 print _('\nGroup: %s') % group.ui_name verb = self.verbose_logger.isEnabledFor(logginglevels.DEBUG_3) - if verb: - print _(' Group-Id: %s') % to_unicode(group.groupid) +- if verb: +- print _(' Group-Id: %s') % to_unicode(group.groupid) ++ if True: ++ print _(' Group-Id: %s') % to_unicode(group.compsid) + + igroup_data = self._groupInstalledData(group) + igrp_only = set() @@ -3579,7 +4135,7 @@ index b6aa277..b3f2c75 100755 if group.ui_description: print _(' Description: %s') % to_unicode(group.ui_description) if group.langonly: -@@ -795,7 +1126,8 @@ class YumOutput: +@@ -795,7 +1131,8 @@ class YumOutput: if verb: data = {'envra' : {}, 'rid' : {}} for (section_name, pkg_names) in sections: @@ -3589,7 +4145,7 @@ index b6aa277..b3f2c75 100755 data = [data['envra'], data['rid']] columns = self.calcColumns(data) columns = (-columns[0], -columns[1]) -@@ -804,11 +1136,20 @@ class YumOutput: +@@ -804,11 +1141,77 @@ class YumOutput: if len(pkg_names) > 0: print section_name self._displayPkgsFromNames(pkg_names, verb, pkg_names2pkgs, @@ -3601,6 +4157,63 @@ index b6aa277..b3f2c75 100755 + self._displayPkgsFromNames(igrp_only, verb, pkg_names2pkgs, + columns=columns, + igroup_data=igroup_data) ++ ++ def displayGrpsInEnvironments(self, evgroup): ++ """Output information about the groups in a given evgroup ++ ++ :param group: an Environment object to output information about ++ """ ++ print _('\nEnvironment Group: %s') % evgroup.ui_name ++ print _(' Environment-Id: %s') % to_unicode(evgroup.compsid) ++ ++ igroup_data = self._groupInstalledEnvData(evgroup) ++ igrp_only = set() ++ for grp_name in igroup_data: ++ if igroup_data[grp_name] == 'installed': ++ igrp_only.add(grp_name) ++ igrp_only.difference_update(evgroup.allgroups) ++ all_grps = evgroup.allgroups + list(igrp_only) ++ ++ if evgroup.ui_description: ++ print _(' Description: %s') % to_unicode(evgroup.ui_description) ++ ++ 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 ++ ++ sections = ((_(' Mandatory Groups:'), evgroup.groups), ++ (_(' Optional Groups:'), evgroup.options)) ++ ++ for (section_name, grp_names) in sections: ++ if len(grp_names) > 0: ++ print section_name ++ for grp_name in sorted(grp_names): ++ pindent = _get_igrp_data(grp_name, " ") ++ if pindent is None: ++ continue ++ ++ print "%s%s" % (pindent, grp_name) ++ ++ if igrp_only: ++ print _(' Installed Groups:') ++ for grp_name in sorted(igrp_only): ++ print "%s%s", " ", grp_name def depListOutput(self, results): - """take a list of findDeps results and 'pretty print' the output""" @@ -3613,7 +4226,7 @@ index b6aa277..b3f2c75 100755 verb = self.verbose_logger.isEnabledFor(logginglevels.DEBUG_3) for pkg in sorted(results): print _("package: %s") % pkg.compactPrint() -@@ -832,7 +1173,18 @@ class YumOutput: +@@ -832,7 +1235,18 @@ class YumOutput: print " provider: %s" % po.compactPrint() def format_number(self, number, SI=0, space=' '): @@ -3633,7 +4246,7 @@ index b6aa277..b3f2c75 100755 symbols = [ ' ', # (none) 'k', # kilo 'M', # mega -@@ -870,16 +1222,31 @@ class YumOutput: +@@ -870,16 +1284,31 @@ class YumOutput: @staticmethod def format_time(seconds, use_hours=0): @@ -3671,7 +4284,7 @@ index b6aa277..b3f2c75 100755 if self.conf.showdupesfromrepos: msg = '%s : ' % po else: -@@ -923,7 +1290,15 @@ class YumOutput: +@@ -923,7 +1352,15 @@ class YumOutput: item = self._enc(item) can_overflow = False else: @@ -3688,7 +4301,7 @@ index b6aa277..b3f2c75 100755 if matchfor: item = self._sub_highlight(item, highlight, matchfor, -@@ -935,14 +1310,34 @@ class YumOutput: +@@ -935,14 +1372,34 @@ class YumOutput: print '\n\n' def matchcallback_verbose(self, po, values, matchfor=None): @@ -3724,7 +4337,7 @@ index b6aa277..b3f2c75 100755 for pkg in packages: # Just to be on the safe side, if for some reason getting # the package size fails, log the error and don't report download -@@ -971,18 +1366,18 @@ class YumOutput: +@@ -971,18 +1428,18 @@ class YumOutput: if (not error): if locsize: @@ -3751,7 +4364,7 @@ index b6aa277..b3f2c75 100755 totsize = 0 error = False for pkg in packages: -@@ -997,13 +1392,19 @@ class YumOutput: +@@ -997,13 +1454,19 @@ class YumOutput: self.logger.error(_('There was an error calculating installed size')) break if (not error): @@ -3776,7 +4389,7 @@ index b6aa277..b3f2c75 100755 self.tsInfo.makelists(True, True) pkglist_lines = [] data = {'n' : {}, 'v' : {}, 'r' : {}} -@@ -1032,8 +1433,7 @@ class YumOutput: +@@ -1032,8 +1495,7 @@ class YumOutput: for (d, v) in (("n",len(n)), ("v",len(evr)), ("r",len(repoid))): data[d].setdefault(v, 0) data[d][v] += 1 @@ -3786,7 +4399,7 @@ index b6aa277..b3f2c75 100755 return a_wid for (action, pkglist) in [(_('Installing'), self.tsInfo.installed), -@@ -1102,19 +1502,72 @@ class YumOutput: +@@ -1102,19 +1564,72 @@ class YumOutput: Transaction Summary %s """) % ('=' * self.term.columns)) @@ -3869,7 +4482,7 @@ index b6aa277..b3f2c75 100755 out = '' self.tsInfo.makelists() -@@ -1179,9 +1632,9 @@ Transaction Summary +@@ -1179,17 +1694,19 @@ Transaction Summary return out def setupProgressCallbacks(self): @@ -3882,7 +4495,33 @@ index b6aa277..b3f2c75 100755 # if we're below 2 on the debug level we don't need to be outputting # progress bars - this is hacky - I'm open to other options # One of these is a download -@@ -1216,10 +1669,12 @@ Transaction Summary + if self.conf.debuglevel < 2 or not sys.stdout.isatty(): + progressbar = None ++ multi_progressbar = None + callback = None + else: + progressbar = YumTextMeter(fo=sys.stdout) ++ multi_progressbar = YumTextMultiFileMeter(fo=sys.stdout) + callback = CacheProgressCallback() + + # setup our failure report for failover +@@ -1200,13 +1717,14 @@ Transaction Summary + interrupt_callback = self.interrupt_callback + if hasattr(self, 'prerepoconf'): + self.prerepoconf.progressbar = progressbar ++ self.prerepoconf.multi_progressbar = multi_progressbar + self.prerepoconf.callback = callback + self.prerepoconf.failure_callback = failure_callback + self.prerepoconf.interrupt_callback = interrupt_callback + else: + # Just in case some API user decides to do self.repos before + # calling us. +- self.repos.setProgressBar(progressbar) ++ self.repos.setProgressBar(progressbar, multi_progressbar) + self.repos.callback = callback + self.repos.setFailureCallback(failure_callback) + self.repos.setInterruptCallback(interrupt_callback) +@@ -1216,10 +1734,12 @@ Transaction Summary self.dsCallback = dscb def setupProgessCallbacks(self): @@ -3896,27 +4535,27 @@ index b6aa277..b3f2c75 100755 confirm_func = self._cli_confirm_gpg_key_import gpg_import_func = self.getKeyForRepo gpgca_import_func = self.getCAKeyForRepo -@@ -1233,14 +1688,12 @@ Transaction Summary +@@ -1233,14 +1753,12 @@ Transaction Summary self.repos.gpgca_import_func = gpgca_import_func def interrupt_callback(self, cbobj): - '''Handle CTRL-C's during downloads +- +- If a CTRL-C occurs a URLGrabError will be raised to push the download +- onto the next mirror. +- +- If two CTRL-C's occur in quick succession then yum will exit. + '''Handle CTRL-C's during downloads. If a CTRL-C occurs a + URLGrabError will be raised to push the download onto the next + mirror. If two CTRL-C's occur in quick succession then yum + will exit. -- If a CTRL-C occurs a URLGrabError will be raised to push the download -- onto the next mirror. -- -- If two CTRL-C's occur in quick succession then yum will exit. -- - @param cbobj: urlgrabber callback obj + :param cbobj: :class:`urlgrabber.grabber.CallbackObject` ''' delta_exit_chk = 2.0 # Delta between C-c's so we treat as exit delta_exit_str = _("two") # Human readable version of above -@@ -1269,6 +1722,14 @@ to exit. +@@ -1269,6 +1787,14 @@ to exit. def download_callback_total_cb(self, remote_pkgs, remote_size, download_start_timestamp): @@ -3931,7 +4570,7 @@ index b6aa277..b3f2c75 100755 if len(remote_pkgs) <= 1: return if not hasattr(urlgrabber.progress, 'TerminalLine'): -@@ -1434,8 +1895,17 @@ to exit. +@@ -1434,8 +1960,17 @@ to exit. return tids, printall def historyListCmd(self, extcmds): @@ -3941,16 +4580,16 @@ index b6aa277..b3f2c75 100755 + + :param extcmds: list of extra command line arguments + :return: (exit_code, [errors]) - -+ exit_code is:: + ++ exit_code is:: + + 0 = we're done, exit + 1 = we've errored, exit with error string + """ tids, printall = self._history_list_transactions(extcmds) if tids is None: return 1, ['Failed history list'] -@@ -1564,6 +2034,16 @@ to exit. +@@ -1564,6 +2099,16 @@ to exit. return old[0] def historyInfoCmd(self, extcmds): @@ -3967,7 +4606,7 @@ index b6aa277..b3f2c75 100755 def str2int(x): try: return int(x) -@@ -1656,6 +2136,9 @@ to exit. +@@ -1656,6 +2201,9 @@ to exit. def _hpkg2from_repo(self, hpkg): """ Given a pkg, find the ipkg.ui_from_repo ... if none, then get an apkg. ... and put a ? in there. """ @@ -3977,7 +4616,7 @@ index b6aa277..b3f2c75 100755 ipkgs = self.rpmdb.searchPkgTuple(hpkg.pkgtup) if not ipkgs: apkgs = self.pkgSack.searchPkgTuple(hpkg.pkgtup) -@@ -1672,13 +2155,12 @@ to exit. +@@ -1672,13 +2220,12 @@ to exit. 'o' : _('Updated'), 'n' : _('Downgraded')} _pkg_states_available = {'i' : _('Installed'), 'e' : _('Not installed'), 'o' : _('Older'), 'n' : _('Newer')} @@ -3994,7 +4633,7 @@ index b6aa277..b3f2c75 100755 prefix = " " * prefix_len if was_installed: _pkg_states = _pkg_states_installed -@@ -1702,9 +2184,11 @@ to exit. +@@ -1702,9 +2249,11 @@ to exit. else: (hibeg, hiend) = self._highlight('normal') state = utf8_width_fill(state, _pkg_states['maxlen']) @@ -4008,7 +4647,7 @@ index b6aa277..b3f2c75 100755 if type(old.tid) == type([]): print _("Transaction ID :"), "%u..%u" % (old.tid[0], old.tid[-1]) -@@ -1778,8 +2262,8 @@ to exit. +@@ -1778,8 +2327,8 @@ to exit. default_addons = set(['config-main', 'config-repos', 'saved_tx']) non_default = set(addon_info).difference(default_addons) if len(non_default) > 0: @@ -4019,7 +4658,7 @@ index b6aa277..b3f2c75 100755 if old.trans_with: # This is _possible_, but not common -@@ -1794,7 +2278,9 @@ to exit. +@@ -1794,7 +2343,9 @@ to exit. print _("Packages Skipped:") pkg_max_len = max((len(str(hpkg)) for hpkg in old.trans_skip)) for hpkg in old.trans_skip: @@ -4030,7 +4669,7 @@ index b6aa277..b3f2c75 100755 if old.rpmdb_problems: print _("Rpmdb Problems:") -@@ -1833,6 +2319,13 @@ to exit. +@@ -1833,6 +2384,13 @@ to exit. 'Updated' : _('Updated'), } def historyInfoCmdPkgsAltered(self, old, pats=[]): @@ -4044,7 +4683,7 @@ index b6aa277..b3f2c75 100755 last = None # Note that these don't use _simple_pkg() because we are showing what # happened to them in the transaction ... not the difference between the -@@ -1886,6 +2379,10 @@ to exit. +@@ -1886,6 +2444,10 @@ to exit. self._hpkg2from_repo(hpkg)) def historySummaryCmd(self, extcmds): @@ -4055,7 +4694,7 @@ index b6aa277..b3f2c75 100755 tids, printall = self._history_list_transactions(extcmds) if tids is None: return 1, ['Failed history info'] -@@ -1946,6 +2443,10 @@ to exit. +@@ -1946,6 +2508,10 @@ to exit. utf8_width_fill(uiacts, 16, 16), count) def historyAddonInfoCmd(self, extcmds): @@ -4066,7 +4705,7 @@ index b6aa277..b3f2c75 100755 tid = None if len(extcmds) > 1: tid = extcmds[1] -@@ -1983,16 +2484,19 @@ to exit. +@@ -1983,16 +2549,19 @@ to exit. for item in extcmds[2:]: if item in addon_info: @@ -4092,7 +4731,7 @@ index b6aa277..b3f2c75 100755 tids = self.history.search(extcmds) limit = None if extcmds and not tids: -@@ -2078,9 +2582,94 @@ to exit. +@@ -2078,9 +2647,94 @@ to exit. if lastdbv.end_rpmdbversion != rpmdbv: self._rpmdb_warn_checks() @@ -4188,7 +4827,7 @@ index b6aa277..b3f2c75 100755 def __init__(self, ayum=None): """requires yum-cli log and errorlog functions as arguments""" -@@ -2089,6 +2678,25 @@ class DepSolveProgressCallBack: +@@ -2089,6 +2743,25 @@ class DepSolveProgressCallBack: self.ayum = ayum def pkgAdded(self, pkgtup, mode): @@ -4214,7 +4853,7 @@ index b6aa277..b3f2c75 100755 modedict = { 'i': _('installed'), 'u': _('an update'), 'e': _('erased'), -@@ -2104,43 +2712,85 @@ class DepSolveProgressCallBack: +@@ -2104,43 +2777,85 @@ class DepSolveProgressCallBack: modeterm) def start(self): @@ -4302,7 +4941,7 @@ index b6aa277..b3f2c75 100755 needname, needflags, needversion = reqTup yb = self.ayum -@@ -2225,46 +2875,106 @@ class DepSolveProgressCallBack: +@@ -2225,46 +2940,106 @@ class DepSolveProgressCallBack: return msg def procConflict(self, name, confname): @@ -4414,7 +5053,7 @@ index b6aa277..b3f2c75 100755 def _pkgname_ui(ayum, pkgname, ts_states=None): """ Get more information on a simple pkgname, if we can. We need to search -@@ -2316,10 +3026,7 @@ def _pkgname_ui(ayum, pkgname, ts_states=None): +@@ -2316,10 +3091,7 @@ def _pkgname_ui(ayum, pkgname, ts_states=None): return pkgname class YumCliRPMCallBack(RPMBaseCallback): @@ -4426,7 +5065,7 @@ index b6aa277..b3f2c75 100755 width = property(lambda x: _term_width()) -@@ -2337,21 +3044,34 @@ class YumCliRPMCallBack(RPMBaseCallback): +@@ -2337,21 +3109,34 @@ class YumCliRPMCallBack(RPMBaseCallback): # Installing things have pkg objects passed to the events, so only need to # lookup for erased/obsoleted. def pkgname_ui(self, pkgname, ts_states=('e', 'od', 'ud', None)): @@ -4471,7 +5110,7 @@ index b6aa277..b3f2c75 100755 if type(package) not in types.StringTypes: pkgname = str(package) -@@ -2363,9 +3083,25 @@ class YumCliRPMCallBack(RPMBaseCallback): +@@ -2363,9 +3148,25 @@ class YumCliRPMCallBack(RPMBaseCallback): percent = 0 else: percent = (te_current*100L)/te_total @@ -4498,7 +5137,7 @@ index b6aa277..b3f2c75 100755 pkgname=pkgname, wid1=wid1) msg = fmt % (utf8_width_fill(process, wid1, wid1), utf8_width_fill(pkgname, wid2, wid2)) -@@ -2377,6 +3113,11 @@ class YumCliRPMCallBack(RPMBaseCallback): +@@ -2377,6 +3178,11 @@ class YumCliRPMCallBack(RPMBaseCallback): print " " def scriptout(self, package, msgs): @@ -4510,7 +5149,7 @@ index b6aa277..b3f2c75 100755 if msgs: sys.stdout.write(to_unicode(msgs)) sys.stdout.flush() -@@ -2429,8 +3170,30 @@ class YumCliRPMCallBack(RPMBaseCallback): +@@ -2429,8 +3235,30 @@ class YumCliRPMCallBack(RPMBaseCallback): wid2 = pnl return fmt, wid1, wid2 @@ -147960,20 +148599,22 @@ index 704a726..05fe6bf 100644 - - diff --git a/rpmUtils/arch.py b/rpmUtils/arch.py -index 6082005..7d67907 100644 +index 6082005..d560be0 100644 --- a/rpmUtils/arch.py +++ b/rpmUtils/arch.py -@@ -2,6 +2,9 @@ +@@ -2,6 +2,11 @@ # import os +import rpm ++import ctypes ++import struct + +_ppc64_native_is_best = False # dict mapping arch -> ( multicompat, best personality, biarch personality ) multilibArches = { "x86_64": ( "athlon", "x86_64", "athlon" ), -@@ -10,6 +13,8 @@ multilibArches = { "x86_64": ( "athlon", "x86_64", "athlon" ), +@@ -10,6 +15,8 @@ multilibArches = { "x86_64": ( "athlon", "x86_64", "athlon" ), "ppc64": ( "ppc", "ppc", "ppc64" ), "s390x": ( "s390", "s390x", "s390" ), } @@ -147982,7 +148623,15 @@ index 6082005..7d67907 100644 arches = { # ia32 -@@ -61,6 +66,13 @@ arches = { +@@ -26,6 +33,7 @@ arches = { + "ia32e": "x86_64", + + # ppc ++ "ppc64p7": "ppc64", + "ppc64pseries": "ppc64", + "ppc64iseries": "ppc64", + "ppc64": "ppc", +@@ -61,6 +69,13 @@ arches = { "armv5tejl": "armv5tel", "armv5tel": "noarch", @@ -147996,7 +148645,63 @@ index 6082005..7d67907 100644 # super-h "sh4a": "sh4", "sh4": "noarch", -@@ -231,6 +243,13 @@ def getCanonX86Arch(arch): +@@ -70,6 +85,13 @@ arches = { + "ia64": "noarch", + } + ++# Will contain information parsed from /proc/self/auxv via _parse_auxv(). ++# Should move into rpm really. ++_aux_vector = { ++ "platform": "", ++ "hwcap": 0, ++ } ++ + def legitMultiArchesInSameLib(arch=None): + # this is completely crackrock - if anyone has a better way I + # am all ears +@@ -80,7 +102,7 @@ def legitMultiArchesInSameLib(arch=None): + + results = [arch] + +- if arch == 'x86_64' or arch.startswith('sparcv9'): ++ if arch in ('x86_64', 'ppc64') or arch.startswith('sparcv9'): + for (k, v) in arches.items(): + if v == arch: + results.append(k) +@@ -210,6 +232,32 @@ def _try_read_cpuinfo(): + except: + return [] + ++def _parse_auxv(): ++ """ Read /proc/self/auxv and parse it into global dict for easier access ++ later on, very similar to what rpm does. """ ++ # In case we can't open and read /proc/self/auxv, just return ++ try: ++ data = open("/proc/self/auxv", "rb").read() ++ except: ++ return ++ ++ # Define values from /usr/include/elf.h ++ AT_PLATFORM = 15 ++ AT_HWCAP = 16 ++ fmtlen = struct.calcsize("LL") ++ offset = 0 ++ platform = ctypes.c_char_p() ++ ++ # Parse the data and fill in _aux_vector dict ++ while offset <= len(data) - fmtlen: ++ at_type, at_val = struct.unpack_from("LL", data, offset) ++ if at_type == AT_PLATFORM: ++ platform.value = at_val ++ _aux_vector["platform"] = platform.value ++ if at_type == AT_HWCAP: ++ _aux_vector["hwcap"] = at_val ++ offset = offset + fmtlen ++ + def getCanonX86Arch(arch): + # + if arch == "i586": +@@ -231,6 +279,13 @@ def getCanonX86Arch(arch): return arch @@ -148010,7 +148715,30 @@ index 6082005..7d67907 100644 def getCanonPPCArch(arch): # FIXME: should I do better handling for mac, etc? if arch != "ppc64": -@@ -308,6 +327,8 @@ def getCanonArch(skipRpmPlatform = 0): +@@ -241,6 +296,17 @@ def getCanonPPCArch(arch): + if line.find("machine") != -1: + machine = line.split(':')[1] + break ++ ++ platform = _aux_vector["platform"] ++ if machine is None and not platform: ++ return arch ++ ++ try: ++ if platform.startswith("power") and int(platform[5:]) >= 7: ++ return "ppc64p7" ++ except: ++ pass ++ + if machine is None: + return arch + +@@ -305,9 +371,13 @@ def getCanonArch(skipRpmPlatform = 0): + + arch = os.uname()[4] + ++ _parse_auxv() ++ if (len(arch) == 4 and arch[0] == "i" and arch[2:4] == "86"): return getCanonX86Arch(arch) @@ -148019,7 +148747,7 @@ index 6082005..7d67907 100644 if arch.startswith("ppc"): return getCanonPPCArch(arch) if arch.startswith("sparc"): -@@ -339,7 +360,7 @@ def getBestArch(myarch=None): +@@ -339,7 +409,7 @@ def getBestArch(myarch=None): if arch.startswith("sparc64"): arch = multilibArches[arch][1] @@ -148028,7 +148756,7 @@ index 6082005..7d67907 100644 arch = 'ppc' return arch -@@ -357,8 +378,12 @@ def getBaseArch(myarch=None): +@@ -357,8 +427,12 @@ def getBaseArch(myarch=None): if myarch.startswith("sparc64"): return "sparc" @@ -148309,8 +149037,84 @@ index 7af3f16..ea47b03 100644 def testUpdate_so_req_diff_arch(self): rpo1 = FakePackage('foozoomer') +diff --git a/test/revdepupdatetests.py b/test/revdepupdatetests.py +new file mode 100644 +index 0000000..a98341d +--- /dev/null ++++ b/test/revdepupdatetests.py +@@ -0,0 +1,70 @@ ++from testbase import * ++ ++class RevdepUpdateTests(OperationsTests): ++ ++ @staticmethod ++ def buildPkgs(pkgs, *args): ++ """ This test checks that remove_old_deps handles reverse ++ dependencies properly during yum update. Specifically, ++ remove_old_deps should remove precisely the packages which are not ++ required by any package currently installed or pending ++ installation. Two cases: ++ ++ For packages A, B, we write A -> B if B requires A. Packages ++ with a dep prefix are dep-installed. ++ ++ 1) Installed: dep1 -> dep2 -> pkg1 <- dep3 and dep3 -> dep2 ++ Update: pkg1, which requires dep2 but no longer requires dep3. ++ Correct outcome: dep1, dep2, dep3, pkg1, since dep2 still ++ needs dep3. ++ ++ 2) Installed: dep1 -> dep2 -> pkg1 <- dep3 and dep3 -> dep2 ++ Update: pkg1, which now requires only dep1 ++ Correct outcome: dep1, pkg1, since dep2 and dep3 are ++ no longer needed. ++ ++ """ ++ ++ ++ pkgs.installed_1 = FakePackage('dep1', '1', '0', '0', 'noarch') ++ pkgs.installed_1.yumdb_info.reason = 'dep' ++ ++ pkgs.installed_2 = FakePackage('dep2', '1', '0', '0', 'noarch') ++ pkgs.installed_2.yumdb_info.reason = 'dep' ++ ++ pkgs.installed_3 = FakePackage('pkg1', '1', '0', '0', 'noarch') ++ pkgs.installed_3.yumdb_info.reason = 'user' ++ ++ pkgs.installed_4 = FakePackage('dep3', '1', '0', '0', 'noarch') ++ pkgs.installed_4.yumdb_info.reason = 'dep' ++ ++ pkgs.installed_1.addRequiringPkg(pkgs.installed_2) ++ pkgs.installed_2.addRequiringPkg(pkgs.installed_3) ++ pkgs.installed_4.addRequiringPkg(pkgs.installed_2) ++ ++ pkgs.installed_2.addRequiresPkg(pkgs.installed_1) ++ pkgs.installed_2.addRequiresPkg(pkgs.installed_4) ++ pkgs.installed_3.addRequiresPkg(pkgs.installed_4) ++ pkgs.installed_3.addRequiresPkg(pkgs.installed_2) ++ ++ pkgs.update_2 = FakePackage('dep2', '2', '0', '0', 'noarch') ++ pkgs.update_2.addRequires('dep1', 'EQ', ('0', '1', '0')) ++ ++ pkgs.update_3 = FakePackage('pkg1', '2', '0', '0', 'noarch') ++ pkgs.update_3.addRequires('dep2', 'EQ', ('0', '1', '0')) ++ ++ pkgs.update_4 = FakePackage('pkg1', '2', '0', '0', 'noarch') ++ pkgs.update_4.addRequires('dep1', 'EQ', ('0', '1', '0')) ++ ++ def testUpdate(self): ++ p = self.pkgs ++ res, msg = self.runOperation(['update'], [p.installed_1, p.installed_2, p.installed_3, p.installed_4], [p.update_3]) ++ self.assert_(res=='ok', msg) ++ self.assertResult( (p.installed_1, p.installed_2, p.update_3, p.installed_4) ) ++ ++ def testUpdate2(self): ++ p = self.pkgs ++ res, msg = self.runOperation(['update'], [p.installed_1, p.installed_2, p.installed_3, p.installed_4], [p.update_4]) ++ self.assert_(res=='ok', msg) ++ self.assertResult( (p.installed_1, p.update_4) ) ++ diff --git a/test/simpleobsoletestests.py b/test/simpleobsoletestests.py -index 97a9923..70dde98 100644 +index 97a9923..55e6f14 100644 --- a/test/simpleobsoletestests.py +++ b/test/simpleobsoletestests.py @@ -244,6 +244,42 @@ class SimpleObsoletesTests(OperationsTests): @@ -148356,11 +149160,40 @@ index 97a9923..70dde98 100644 def _MultiObsHelper(self): ret = {'zsh' : FakePackage('zsh', '1', '1', '0', 'noarch'), 'ksh' : FakePackage('ksh', '1', '1', '0', 'noarch'), +@@ -588,6 +624,28 @@ class SimpleObsoletesTests(OperationsTests): + # self.assert_(res=='err', msg) + self.assertResult([]) + ++ def testRLOpenSSLMess1(self): ++ osl1 = FakePackage('openssl', '1.0.0', '1', arch='i386') ++ osl1.addProvides('libssl.1', 'EQ', ('0', '1', '1')) ++ osl2 = FakePackage('openssl', '1.0.1', '1', arch='i386') ++ osll2 = FakePackage('openssl-libs', '1.0.1', '1', arch='i386') ++ osll2.addProvides('libssl.2', 'EQ', ('0', '2', '1')) ++ osll2.addObsoletes('openssl', 'LT', (None, '1.0.1', None)) ++ ++ oc1 = FakePackage('openconnect', '2.0.1', '1', arch='i386') ++ oc1.addRequires('openssl', 'GE', ('0', '0.9.9', '1')) ++ oc2 = FakePackage('openconnect', '2.0.2', '1', arch='i386') ++ oc2.addRequires('openssl', 'GE', ('0', '0.9.9', '1')) ++ ++ res, msg = self.runOperation(['upgrade', 'openssl'], ++ [oc1, osl1], ++ [oc1, oc2, osl1, osl2, osll2]) ++ ++ # In theory don't need to upgrade oc1 => oc2 ++ self.assertResult((oc2, osl2, osll2)) ++ ++ ++ + class GitMetapackageObsoletesTests(OperationsTests): + + @staticmethod diff --git a/test/simpleupdatetests.py b/test/simpleupdatetests.py -index 6177fb1..2c8bcb3 100644 +index 6177fb1..9e22a6b 100644 --- a/test/simpleupdatetests.py +++ b/test/simpleupdatetests.py -@@ -990,3 +990,72 @@ class SimpleUpdateTests(OperationsTests): +@@ -990,3 +990,93 @@ class SimpleUpdateTests(OperationsTests): # Nothing to do... self.assert_(res==0, msg) @@ -148433,8 +149266,29 @@ index 6177fb1..2c8bcb3 100644 + # Ideal: + # self.assertResult((foo11, bar12, cbar11)) + self.assertResult((foo12, bar12, cbar11)) ++ ++ def testInstallOtherArch1(self): ++ # Installing something with dep. on newer version should upgrade older ++ # version with other arch. ++ # Eg. BZ 791326 ++ pax1 = FakePackage('inst', '1', '1', '0', 'x86_64') ++ pai1 = FakePackage('inst', '1', '1', '0', 'i686') ++ pax2 = FakePackage('inst', '2', '1', '0', 'x86_64') ++ pax2.addProvides('foo-x', 'EQ', ('0', '2', '1')) ++ pai2 = FakePackage('inst', '2', '1', '0', 'i686') ++ pai2.addProvides('foo-i', 'EQ', ('0', '2', '1')) ++ ++ pa2 = FakePackage('bar', '1', '1', '0', 'i386') ++ pa2.addRequires('foo-i', 'EQ', ('0', '2', '1')) ++ ++ res, msg = self.runOperation(['install', 'bar'], ++ [pax1], ++ [pax1, pai1, pax2, pai2, pa2]) ++ self.assert_(res=='ok', msg) ++ self.assertResult((pax2, pai2, pa2)) ++ diff --git a/test/testbase.py b/test/testbase.py -index d0f22be..0a931f1 100644 +index d0f22be..608da70 100644 --- a/test/testbase.py +++ b/test/testbase.py @@ -55,6 +55,9 @@ class FakeConf(object): @@ -148447,6 +149301,22 @@ index d0f22be..0a931f1 100644 class FakeSack: """ Fake PackageSack to use with FakeRepository""" +@@ -138,6 +141,7 @@ class FakePackage(packages.YumAvailablePackage): + self.yumdb_info = FakeYumDBInfo() + + self.prco['provides'].append((name, 'EQ', (epoch, version, release))) ++ self.prco['strong_requires'] = [] + + # Just a unique integer + self.id = self.__hash__() +@@ -149,6 +153,7 @@ class FakePackage(packages.YumAvailablePackage): + self.prco['provides'].append((name, flag, evr)) + def addRequires(self, name, flag=None, evr=(None, None, None)): + self.prco['requires'].append((name, flag, evr)) ++ self.prco['strong_requires'].append((name, flag, evr)) + def addRequiresPkg(self, pkg): + self.required_pkgs.append(pkg) + def addRequiringPkg(self, pkg): diff --git a/test/yum-leak-test.py b/test/yum-leak-test.py index 760b770..dd64483 100755 --- a/test/yum-leak-test.py @@ -148865,7 +149735,7 @@ index fa3e57c..ea4e683 100644 diff --git a/utils.py b/utils.py old mode 100644 new mode 100755 -index ced6ba0..08b09fb +index ced6ba0..e465cf7 --- a/utils.py +++ b/utils.py @@ -13,6 +13,9 @@ @@ -148941,10 +149811,24 @@ index ced6ba0..08b09fb if not pid: return -@@ -106,6 +136,16 @@ def get_process_info(pid): +@@ -63,12 +93,6 @@ def get_process_info(pid): + except ValueError, e: + return + +- # Maybe true if /proc isn't mounted, or not Linux ... or something. +- if (not os.path.exists("/proc/%d/status" % pid) or +- not os.path.exists("/proc/stat") or +- not os.path.exists("/proc/%d/stat" % pid)): +- return +- + ps = {} + for line in open("/proc/%d/status" % pid): + if line[-1] != '\n': +@@ -106,7 +130,21 @@ def get_process_info(pid): return ps def show_lock_owner(pid, logger): +- ps = get_process_info(pid) + """Output information about another process that is holding the + yum lock. + @@ -148955,10 +149839,15 @@ index ced6ba0..08b09fb + This is the same as the dictionary returned by + :func:`get_process_info`. + """ - ps = get_process_info(pid) ++ try: ++ ps = get_process_info(pid) ++ except IOError, e: ++ logger.critical("%s", exception2msg(e)) ++ ps = None if not ps: return None -@@ -129,28 +169,9 @@ def show_lock_owner(pid, logger): + +@@ -129,28 +167,9 @@ def show_lock_owner(pid, logger): return ps @@ -148989,7 +149878,7 @@ index ced6ba0..08b09fb def __init__(self,name,ver,usage): YumBaseCli.__init__(self) self._parser = YumOptionParser(base=self,utils=True,usage=usage) -@@ -167,11 +188,22 @@ class YumUtilBase(YumBaseCli): +@@ -167,11 +186,22 @@ class YumUtilBase(YumBaseCli): self.run_with_package_names.add("yum-utils") def exUserCancel(self): @@ -149012,7 +149901,7 @@ index ced6ba0..08b09fb if e.errno == 32: self.logger.critical(_('\n\nExiting on Broken Pipe')) else: -@@ -180,10 +212,13 @@ class YumUtilBase(YumBaseCli): +@@ -180,10 +210,13 @@ class YumUtilBase(YumBaseCli): return 1 def exPluginExit(self, e): @@ -149029,7 +149918,7 @@ index ced6ba0..08b09fb exitmsg = exception2msg(e) if exitmsg: self.logger.warn('\n\n%s', exitmsg) -@@ -191,11 +226,20 @@ class YumUtilBase(YumBaseCli): +@@ -191,11 +224,20 @@ class YumUtilBase(YumBaseCli): return 1 def exFatal(self, e): @@ -149050,7 +149939,7 @@ index ced6ba0..08b09fb try: self.closeRpmDB() self.doUnlock() -@@ -205,13 +249,27 @@ class YumUtilBase(YumBaseCli): +@@ -205,13 +247,27 @@ class YumUtilBase(YumBaseCli): def getOptionParser(self): @@ -149079,7 +149968,7 @@ index ced6ba0..08b09fb lockerr = "" while True: try: -@@ -233,6 +291,13 @@ class YumUtilBase(YumBaseCli): +@@ -233,6 +289,13 @@ class YumUtilBase(YumBaseCli): print "%s - %s (yum - %s)" % (self._utilName,self._utilVer,yum.__version__) def doUtilConfigSetup(self,args = sys.argv[1:],pluginsTypes=(plugins.TYPE_CORE,)): @@ -149093,7 +149982,7 @@ index ced6ba0..08b09fb # Parse only command line options that affect basic yum setup opts = self._parser.firstParse(args) -@@ -305,8 +370,9 @@ class YumUtilBase(YumBaseCli): +@@ -305,8 +368,9 @@ class YumUtilBase(YumBaseCli): return opts def doUtilYumSetup(self): @@ -149105,7 +149994,7 @@ index ced6ba0..08b09fb # FIXME - we need another way to do this, I think. try: self.waitForLock() -@@ -319,6 +385,11 @@ class YumUtilBase(YumBaseCli): +@@ -319,6 +383,11 @@ class YumUtilBase(YumBaseCli): sys.exit(1) def doUtilBuildTransaction(self, unfinished_transactions_check=True): @@ -149117,7 +150006,7 @@ index ced6ba0..08b09fb try: (result, resultmsgs) = self.buildTransaction(unfinished_transactions_check = unfinished_transactions_check) except plugins.PluginYumExit, e: -@@ -361,6 +432,7 @@ class YumUtilBase(YumBaseCli): +@@ -361,6 +430,7 @@ class YumUtilBase(YumBaseCli): self.verbose_logger.log(logginglevels.INFO_2, _('\nDependencies Resolved')) def doUtilTransaction(self): @@ -149127,10 +150016,10 @@ index ced6ba0..08b09fb return_code = self.doTransaction() diff --git a/yum-cron/Makefile b/yum-cron/Makefile new file mode 100644 -index 0000000..d68659a +index 0000000..34cb397 --- /dev/null +++ b/yum-cron/Makefile -@@ -0,0 +1,19 @@ +@@ -0,0 +1,21 @@ +all: + echo "Nothing to do" + @@ -149143,7 +150032,9 @@ index 0000000..d68659a + mkdir -p $(DESTDIR)/usr/sbin + mkdir -p $(DESTDIR)/etc/sysconfig + mkdir -p $(DESTDIR)/usr/share/yum-cron -+ install -D -m 755 yum-update.cron.sh $(DESTDIR)/etc/cron.daily/yum-update.cron ++# Install yum-update.cron as 0yum-update.cron so it runs before items like ++# manpage update, mlocate, and prelink ++ install -D -m 755 yum-update.cron.sh $(DESTDIR)/etc/cron.daily/0yum-update.cron + install -D -m 755 yum-cleanup.cron.sh $(DESTDIR)/etc/cron.daily/yum-cleanup.cron + install -D -m 755 yum-cron.sysvinit $(DESTDIR)/etc/rc.d/init.d/yum-cron + install -D -m 755 yum-cron.sh $(DESTDIR)/usr/sbin/yum-cron @@ -149408,7 +150299,7 @@ index 0000000..e300fa7 +exit 0 diff --git a/yum-cron/yum-cron.sysconfig b/yum-cron/yum-cron.sysconfig new file mode 100644 -index 0000000..4c8c40d +index 0000000..53477d3 --- /dev/null +++ b/yum-cron/yum-cron.sysconfig @@ -0,0 +1,92 @@ @@ -149465,7 +150356,7 @@ index 0000000..4c8c40d +#SYSTEMNAME="" + +# Scheduling Options (used by the default cron scripts, -+# /etc/cron.daily/yum-cleanup.cron and /etc/cron.daily/yum-update.cron) ++# /etc/cron.daily/yum-cleanup.cron and /etc/cron.daily/0yum-update.cron) +# +# Note that if you use a different cron configuration (for example, +# removing the default scripts and adding an entry in /etc/cron.d), @@ -150214,7 +151105,7 @@ index 1ce4720..25e3022 gobject.threads_init() dbus.glib.threads_init() diff --git a/yum.spec b/yum.spec -index abd203f..572112a 100644 +index abd203f..65c62a9 100644 --- a/yum.spec +++ b/yum.spec @@ -1,24 +1,51 @@ @@ -150451,7 +151342,7 @@ index abd203f..572112a 100644 -%{_sysconfdir}/cron.daily/0yum.cron -%config(noreplace) %{_sysconfdir}/yum/yum-daily.yum -%config(noreplace) %{_sysconfdir}/yum/yum-weekly.yum -+%config(noreplace) %{_sysconfdir}/cron.daily/yum-update.cron ++%config(noreplace) %{_sysconfdir}/cron.daily/0yum-update.cron +%config(noreplace) %{_sysconfdir}/cron.daily/yum-cleanup.cron %{_sysconfdir}/rc.d/init.d/yum-cron +%{_sbindir}/yum-cron @@ -150480,8 +151371,35 @@ index abd203f..572112a 100644 * Wed Apr 20 2011 James Antill - 3.4.1 - umask bug fix. +diff --git a/yum/Errors.py b/yum/Errors.py +index c1af4ad..e3e3956 100644 +--- a/yum/Errors.py ++++ b/yum/Errors.py +@@ -79,9 +79,20 @@ class RepoError(YumBaseError): + class DuplicateRepoError(RepoError): + pass + ++# Have our own custom .value with all the mirror errors. + class NoMoreMirrorsRepoError(RepoError): +- pass +- ++ def __init__(self, value=None, errors=None): ++ Exception.__init__(self) ++ self._value = value ++ self.errors = errors ++ ++ @property ++ def value(self): ++ ret = self._value ++ for url, msg in self.errors or []: ++ ret += '\n%s: %s' % (url, msg) ++ return ret ++ + class ConfigError(YumBaseError): + pass + diff --git a/yum/__init__.py b/yum/__init__.py -index 99039e0..3680356 100644 +index 99039e0..e43bf3c 100644 --- a/yum/__init__.py +++ b/yum/__init__.py @@ -46,8 +46,13 @@ import operator @@ -150534,7 +151452,7 @@ index 99039e0..3680356 100644 def __init__(self): self.fn = '/etc/yum/yum.conf' self.root = '/' -@@ -125,10 +133,12 @@ class _YumPreBaseConf: +@@ -125,12 +133,15 @@ class _YumPreBaseConf: class _YumPreRepoConf: @@ -150550,8 +151468,11 @@ index 99039e0..3680356 100644 + """ def __init__(self): self.progressbar = None ++ self.multi_progressbar = None self.callback = None -@@ -164,11 +174,11 @@ class _YumCostExclude: + self.failure_callback = None + self.interrupt_callback = None +@@ -164,11 +175,11 @@ class _YumCostExclude: return False class YumBase(depsolve.Depsolve): @@ -150568,7 +151489,7 @@ index 99039e0..3680356 100644 def __init__(self): depsolve.Depsolve.__init__(self) self._conf = None -@@ -177,6 +187,7 @@ class YumBase(depsolve.Depsolve): +@@ -177,6 +188,7 @@ class YumBase(depsolve.Depsolve): self._up = None self._comps = None self._history = None @@ -150576,7 +151497,7 @@ index 99039e0..3680356 100644 self._pkgSack = None self._lockfile = None self._tags = None -@@ -204,6 +215,7 @@ class YumBase(depsolve.Depsolve): +@@ -204,6 +216,7 @@ class YumBase(depsolve.Depsolve): self.run_with_package_names = set() self._cleanup = [] @@ -150584,7 +151505,7 @@ index 99039e0..3680356 100644 def __del__(self): self.close() -@@ -213,10 +225,15 @@ class YumBase(depsolve.Depsolve): +@@ -213,10 +226,15 @@ class YumBase(depsolve.Depsolve): for cb in self._cleanup: cb() def close(self): @@ -150600,7 +151521,7 @@ index 99039e0..3680356 100644 if self._repos: self._repos.close() -@@ -225,15 +242,33 @@ class YumBase(depsolve.Depsolve): +@@ -225,15 +243,33 @@ class YumBase(depsolve.Depsolve): return transactioninfo.TransactionData() def doGenericSetup(self, cache=0): @@ -150636,7 +151557,7 @@ index 99039e0..3680356 100644 warnings.warn(_('doConfigSetup() will go away in a future version of Yum.\n'), Errors.YumFutureDeprecationWarning, stacklevel=2) -@@ -297,15 +332,17 @@ class YumBase(depsolve.Depsolve): +@@ -297,15 +333,17 @@ class YumBase(depsolve.Depsolve): # Try the old default fn = '/etc/yum.conf' @@ -150657,7 +151578,20 @@ index 99039e0..3680356 100644 if debuglevel != None: startupconf.debuglevel = debuglevel -@@ -367,22 +404,36 @@ class YumBase(depsolve.Depsolve): +@@ -336,6 +374,12 @@ class YumBase(depsolve.Depsolve): + + self._conf = config.readMainConfig(startupconf) + ++ # update urlgrabber defaults ++ mc = self._conf.max_connections ++ if mc > 0: ++ default_grabber.opts.max_connections = mc ++ default_grabber.opts.timedhosts = self._conf.cachedir + '/timedhosts' ++ + # We don't want people accessing/altering preconf after it becomes + # worthless. So we delete it, and thus. it'll raise AttributeError + del self.preconf +@@ -367,22 +411,36 @@ class YumBase(depsolve.Depsolve): def doLoggingSetup(self, debuglevel, errorlevel, syslog_ident=None, syslog_facility=None, syslog_device='/dev/log'): @@ -150701,7 +151635,17 @@ index 99039e0..3680356 100644 if repo_age is None: repo_age = os.stat(repofn)[8] -@@ -429,7 +480,15 @@ class YumBase(depsolve.Depsolve): +@@ -391,8 +449,7 @@ class YumBase(depsolve.Depsolve): + try: + parser.readfp(confpp_obj) + except ParsingError, e: +- msg = str(e) +- raise Errors.ConfigError, msg ++ raise Errors.ConfigError(exception2msg(e)) + + # Check sections in the .repo file that was just slurped up + for section in parser.sections(): +@@ -429,7 +486,15 @@ class YumBase(depsolve.Depsolve): thisrepo.base_persistdir = self.conf._repos_persistdir @@ -150718,7 +151662,7 @@ index 99039e0..3680356 100644 if thisrepo.id in self.repo_setopts: for opt in self.repo_setopts[thisrepo.id].items: if not hasattr(thisrepo, opt): -@@ -440,6 +499,20 @@ class YumBase(depsolve.Depsolve): +@@ -440,6 +505,20 @@ class YumBase(depsolve.Depsolve): if validate and not validate(thisrepo): continue @@ -150739,7 +151683,7 @@ index 99039e0..3680356 100644 # Got our list of repo objects, add them to the repos # collection try: -@@ -448,8 +521,11 @@ class YumBase(depsolve.Depsolve): +@@ -448,8 +527,11 @@ class YumBase(depsolve.Depsolve): self.logger.warning(e) def getReposFromConfig(self): @@ -150753,7 +151697,16 @@ index 99039e0..3680356 100644 # Read .repo files from directories specified by the reposdir option # (typically /etc/yum/repos.d) repo_config_age = self.conf.config_file_age -@@ -472,12 +548,13 @@ class YumBase(depsolve.Depsolve): +@@ -466,18 +548,22 @@ class YumBase(depsolve.Depsolve): + + if os.path.isdir(reposdir): + for repofn in sorted(glob.glob('%s/*.repo' % reposdir)): ++ if not os.access(repofn, os.R_OK): ++ self.logger.warning(_("Skipping unreadable repository %s"), repr(repofn)) ++ continue + thisrepo_age = os.stat(repofn)[8] + if thisrepo_age < repo_config_age: + thisrepo_age = repo_config_age self.getReposFromConfigFile(repofn, repo_age=thisrepo_age) def readRepoConfig(self, parser, section): @@ -150772,7 +151725,17 @@ index 99039e0..3680356 100644 repo = yumRepo.YumRepository(section) try: repo.populate(parser, section, self.conf) -@@ -500,31 +577,31 @@ class YumBase(depsolve.Depsolve): +@@ -493,38 +579,40 @@ class YumBase(depsolve.Depsolve): + repo.name = to_unicode(repo.name) + + # Set attributes not from the config file ++ repo.old_base_cache_dir = getattr(self, '_old_cachedir', '') + repo.basecachedir = self.conf.cachedir + repo.yumvar.update(self.conf.yumvar) + repo.cfg = parser +- ++ # Enable parallel downloading ++ repo._async = repo.async return repo def disablePlugins(self): @@ -150825,7 +151788,7 @@ index 99039e0..3680356 100644 if isinstance(self.plugins, plugins.YumPlugins): raise RuntimeError(_("plugins already initialised")) -@@ -533,6 +610,8 @@ class YumBase(depsolve.Depsolve): +@@ -533,6 +621,8 @@ class YumBase(depsolve.Depsolve): def doRpmDBSetup(self): @@ -150834,7 +151797,7 @@ index 99039e0..3680356 100644 warnings.warn(_('doRpmDBSetup() will go away in a future version of Yum.\n'), Errors.YumFutureDeprecationWarning, stacklevel=2) -@@ -552,7 +631,8 @@ class YumBase(depsolve.Depsolve): +@@ -552,7 +642,8 @@ class YumBase(depsolve.Depsolve): return self._rpmdb def closeRpmDB(self): @@ -150844,7 +151807,7 @@ index 99039e0..3680356 100644 if self._rpmdb is not None: self._rpmdb.ts = None self._rpmdb.dropCachedData() -@@ -567,6 +647,12 @@ class YumBase(depsolve.Depsolve): +@@ -567,6 +658,12 @@ class YumBase(depsolve.Depsolve): self._ts = None def doRepoSetup(self, thisrepo=None): @@ -150857,7 +151820,17 @@ index 99039e0..3680356 100644 warnings.warn(_('doRepoSetup() will go away in a future version of Yum.\n'), Errors.YumFutureDeprecationWarning, stacklevel=2) -@@ -630,6 +716,14 @@ class YumBase(depsolve.Depsolve): +@@ -588,7 +685,8 @@ class YumBase(depsolve.Depsolve): + prerepoconf = self.prerepoconf + del self.prerepoconf + +- self.repos.setProgressBar(prerepoconf.progressbar) ++ self.repos.setProgressBar(prerepoconf.progressbar, ++ prerepoconf.multi_progressbar) + self.repos.callback = prerepoconf.callback + self.repos.setFailureCallback(prerepoconf.failure_callback) + self.repos.setInterruptCallback(prerepoconf.interrupt_callback) +@@ -630,6 +728,14 @@ class YumBase(depsolve.Depsolve): self._repos = RepoStorage(self) def doSackSetup(self, archlist=None, thisrepo=None): @@ -150872,7 +151845,7 @@ index 99039e0..3680356 100644 warnings.warn(_('doSackSetup() will go away in a future version of Yum.\n'), Errors.YumFutureDeprecationWarning, stacklevel=2) -@@ -711,6 +805,9 @@ class YumBase(depsolve.Depsolve): +@@ -711,6 +817,9 @@ class YumBase(depsolve.Depsolve): def doUpdateSetup(self): @@ -150882,7 +151855,7 @@ index 99039e0..3680356 100644 warnings.warn(_('doUpdateSetup() will go away in a future version of Yum.\n'), Errors.YumFutureDeprecationWarning, stacklevel=2) -@@ -765,6 +862,8 @@ class YumBase(depsolve.Depsolve): +@@ -765,6 +874,8 @@ class YumBase(depsolve.Depsolve): return self._up def doGroupSetup(self): @@ -150891,7 +151864,29 @@ index 99039e0..3680356 100644 warnings.warn(_('doGroupSetup() will go away in a future version of Yum.\n'), Errors.YumFutureDeprecationWarning, stacklevel=2) -@@ -837,7 +936,10 @@ class YumBase(depsolve.Depsolve): +@@ -820,16 +931,14 @@ class YumBase(depsolve.Depsolve): + self.verbose_logger.log(logginglevels.DEBUG_4, + _('Adding group file from repository: %s'), repo) + groupfile = repo.getGroups() +- # open it up as a file object so iterparse can cope with our compressed file +- if groupfile: +- groupfile = misc.repo_gen_decompress(groupfile, 'groups.xml', +- cached=repo.cache) +- # Do we want a RepoError here? +- ++ if not groupfile: ++ msg = _('Failed to retrieve group file for repository: %s') % repo ++ self.logger.critical(msg) ++ continue + try: + self._comps.add(groupfile) + except (Errors.GroupsError,Errors.CompsException), e: +- msg = _('Failed to add groups file for repository: %s - %s') % (repo, str(e)) ++ msg = _('Failed to add groups file for repository: %s - %s') % (repo, exception2msg(e)) + self.logger.critical(msg) + else: + repo.groups_added = True +@@ -837,7 +946,10 @@ class YumBase(depsolve.Depsolve): if self._comps.compscount == 0: raise Errors.GroupsError, _('No Groups Available in any repository') @@ -150903,7 +151898,16 @@ index 99039e0..3680356 100644 self.verbose_logger.debug('group time: %0.3f' % (time.time() - group_st)) return self._comps -@@ -881,9 +983,18 @@ class YumBase(depsolve.Depsolve): +@@ -868,7 +980,7 @@ class YumBase(depsolve.Depsolve): + # feed it into _tags.add() + self._tags.add(repo.id, tag_sqlite) + except (Errors.RepoError, Errors.PkgTagsError), e: +- msg = _('Failed to add Pkg Tags for repository: %s - %s') % (repo, str(e)) ++ msg = _('Failed to add Pkg Tags for repository: %s - %s') % (repo, exception2msg(e)) + self.logger.critical(msg) + + +@@ -881,9 +993,18 @@ class YumBase(depsolve.Depsolve): if self._history is None: pdb_path = self.conf.persistdir + "/history" self._history = yum.history.YumHistory(root=self.conf.installroot, @@ -150923,7 +151927,7 @@ index 99039e0..3680356 100644 # properties so they auto-create themselves with defaults repos = property(fget=lambda self: self._getRepos(), fset=lambda self, value: setattr(self, "_repos", value), -@@ -921,6 +1032,11 @@ class YumBase(depsolve.Depsolve): +@@ -921,6 +1042,11 @@ class YumBase(depsolve.Depsolve): fdel=lambda self: setattr(self, "_history", None), doc="Yum History Object") @@ -150935,7 +151939,7 @@ index 99039e0..3680356 100644 pkgtags = property(fget=lambda self: self._getTags(), fset=lambda self, value: setattr(self, "_tags",value), fdel=lambda self: setattr(self, "_tags", None), -@@ -928,9 +1044,10 @@ class YumBase(depsolve.Depsolve): +@@ -928,9 +1054,10 @@ class YumBase(depsolve.Depsolve): def doSackFilelistPopulate(self): @@ -150949,7 +151953,7 @@ index 99039e0..3680356 100644 necessary = False # I can't think of a nice way of doing this, we have to have the sack here -@@ -951,8 +1068,12 @@ class YumBase(depsolve.Depsolve): +@@ -951,8 +1078,12 @@ class YumBase(depsolve.Depsolve): self.repos.populateSack(mdtype='filelists') def yumUtilsMsg(self, func, prog): @@ -150964,7 +151968,7 @@ index 99039e0..3680356 100644 if self.rpmdb.contains(name="yum-utils"): return -@@ -964,8 +1085,17 @@ class YumBase(depsolve.Depsolve): +@@ -964,8 +1095,17 @@ class YumBase(depsolve.Depsolve): (hibeg, prog, hiend)) def buildTransaction(self, unfinished_transactions_check=True): @@ -150984,7 +151988,7 @@ index 99039e0..3680356 100644 if (unfinished_transactions_check and misc.find_unfinished_transactions(yumlibpath=self.conf.persistdir)): msg = _('There are unfinished transactions remaining. You might ' \ -@@ -1004,7 +1134,7 @@ class YumBase(depsolve.Depsolve): +@@ -1004,7 +1144,7 @@ class YumBase(depsolve.Depsolve): # If transaction was changed by postresolve plugins then we should run skipbroken again (rescode, restring) = self._doSkipBroken(rescode, restring, clear_skipped=False ) @@ -150993,7 +151997,47 @@ index 99039e0..3680356 100644 self.tsInfo.pkgSack.dropCachedData() # FIXME: This is horrible, see below and yummain. Maybe create a real -@@ -1242,13 +1372,15 @@ class YumBase(depsolve.Depsolve): +@@ -1044,6 +1184,39 @@ class YumBase(depsolve.Depsolve): + if first.verEQ(other): + continue + msg = _('Protected multilib versions: %s != %s') ++ if not xrestring: ++ # People are confused about protected mutilib ... so give ++ # them a nicer message. ++ bigmsg = _("""\ ++ Multilib version problems found. This often means that the root ++cause is something else and multilib version checking is just ++pointing out that there is a problem. Eg.: ++ ++ 1. You have an upgrade for %(name)s which is missing some ++ dependency that another package requires. Yum is trying to ++ solve this by installing an older version of %(name)s of the ++ different architecture. If you exclude the bad architecture ++ yum will tell you what the root cause is (which package ++ requires what). You can try redoing the upgrade with ++ --exclude %(name)s.otherarch ... this should give you an error ++ message showing the root cause of the problem. ++ ++ 2. You have multiple architectures of %(name)s installed, but ++ yum can only see an upgrade for one of those arcitectures. ++ If you don't want/need both architectures anymore then you ++ can remove the one with the missing update and everything ++ will work. ++ ++ 3. You have duplicate versions of %(name)s installed already. ++ You can use "yum check" to get yum show these errors. ++ ++...you can also use --setopt=protected_multilib=false to remove ++this checking, however this is almost never the correct thing to ++do as something else is very likely to go wrong (often causing ++much more problems). ++ ++""") % {'name' : pkgname} ++ msg = bigmsg + msg + xrestring.append(msg % (first, other)) + if xrestring: + rescode = 1 +@@ -1242,13 +1415,15 @@ class YumBase(depsolve.Depsolve): if None in pkgtup: return None return pkgtup @@ -151013,7 +152057,7 @@ index 99039e0..3680356 100644 if pkgtup is None: return self._not_found_i[pkgtup] = YumNotFoundPackage(pkgtup) -@@ -1454,8 +1586,14 @@ class YumBase(depsolve.Depsolve): +@@ -1454,8 +1629,14 @@ class YumBase(depsolve.Depsolve): return probs def runTransaction(self, cb): @@ -151029,7 +152073,7 @@ index 99039e0..3680356 100644 self.plugins.run('pretrans') # We may want to put this other places, eventually, but for now it's -@@ -1516,10 +1654,23 @@ class YumBase(depsolve.Depsolve): +@@ -1516,10 +1697,23 @@ class YumBase(depsolve.Depsolve): pass self._ts_save_file = None @@ -151053,7 +152097,7 @@ index 99039e0..3680356 100644 # make resultobject - just a plain yumgenericholder object resultobject = misc.GenericHolder() -@@ -1567,13 +1718,24 @@ class YumBase(depsolve.Depsolve): +@@ -1567,13 +1761,24 @@ class YumBase(depsolve.Depsolve): self.plugins.run('posttrans') # sync up what just happened versus what is in the rpmdb if not self.ts.isTsFlagSet(rpm.RPMTRANS_FLAG_TEST): @@ -151083,7 +152127,7 @@ index 99039e0..3680356 100644 # check to see that the rpmdb and the tsInfo roughly matches # push package object metadata outside of rpmdb into yumdb # delete old yumdb metadata entries -@@ -1584,9 +1746,16 @@ class YumBase(depsolve.Depsolve): +@@ -1584,9 +1789,16 @@ class YumBase(depsolve.Depsolve): # that there is not also an install of this pkg in the tsInfo (reinstall) # for any kind of install add from_repo to the yumdb, and the cmdline # and the install reason @@ -151100,7 +152144,7 @@ index 99039e0..3680356 100644 for txmbr in self.tsInfo: if txmbr.output_state in TS_INSTALL_STATES: if not self.rpmdb.contains(po=txmbr.po): -@@ -1596,7 +1765,9 @@ class YumBase(depsolve.Depsolve): +@@ -1596,7 +1808,9 @@ class YumBase(depsolve.Depsolve): ' but is not!' % txmbr.po)) # Note: Get Panu to do te.Failed() so we don't have to txmbr.output_state = TS_FAILED @@ -151110,7 +152154,7 @@ index 99039e0..3680356 100644 po = self.getInstalledPackageObject(txmbr.pkgtup) rpo = txmbr.po po.yumdb_info.from_repo = rpo.repoid -@@ -1630,6 +1801,10 @@ class YumBase(depsolve.Depsolve): +@@ -1630,6 +1844,10 @@ class YumBase(depsolve.Depsolve): if md: po.yumdb_info.from_repo_timestamp = str(md.timestamp) @@ -151121,7 +152165,7 @@ index 99039e0..3680356 100644 loginuid = misc.getloginuid() if txmbr.updates or txmbr.downgrades or txmbr.reinstall: if txmbr.updates: -@@ -1640,11 +1815,16 @@ class YumBase(depsolve.Depsolve): +@@ -1640,11 +1858,16 @@ class YumBase(depsolve.Depsolve): opo = po if 'installed_by' in opo.yumdb_info: po.yumdb_info.installed_by = opo.yumdb_info.installed_by @@ -151138,7 +152182,7 @@ index 99039e0..3680356 100644 # Remove old ones after installing new ones, so we can copy values. for txmbr in self.tsInfo: if txmbr.output_state in TS_INSTALL_STATES: -@@ -1662,10 +1842,13 @@ class YumBase(depsolve.Depsolve): +@@ -1662,10 +1885,13 @@ class YumBase(depsolve.Depsolve): ' but is not!' % txmbr.po)) # Note: Get Panu to do te.Failed() so we don't have to txmbr.output_state = TS_FAILED @@ -151152,7 +152196,7 @@ index 99039e0..3680356 100644 self.verbose_logger.log(logginglevels.DEBUG_2, 'What is this? %s' % txmbr.po) self.plugins.run('postverifytrans') -@@ -1680,10 +1863,11 @@ class YumBase(depsolve.Depsolve): +@@ -1680,10 +1906,11 @@ class YumBase(depsolve.Depsolve): self.verbose_logger.debug('VerifyTransaction time: %0.3f' % (time.time() - vt_st)) def costExcludePackages(self): @@ -151168,7 +152212,7 @@ index 99039e0..3680356 100644 # if all the repo.costs are equal then don't bother running things costs = {} for r in self.repos.listEnabled(): -@@ -1705,10 +1889,12 @@ class YumBase(depsolve.Depsolve): +@@ -1705,10 +1932,12 @@ class YumBase(depsolve.Depsolve): done = True def excludePackages(self, repo=None): @@ -151184,7 +152228,7 @@ index 99039e0..3680356 100644 if "all" in self.conf.disable_excludes: return -@@ -1735,9 +1921,11 @@ class YumBase(depsolve.Depsolve): +@@ -1735,9 +1964,11 @@ class YumBase(depsolve.Depsolve): self.pkgSack.addPackageExcluder(repoid, exid,'exclude.match', match) def includePackages(self, repo): @@ -151199,7 +152243,7 @@ index 99039e0..3680356 100644 includelist = repo.getIncludePkgList() if len(includelist) == 0: -@@ -1757,8 +1945,11 @@ class YumBase(depsolve.Depsolve): +@@ -1757,8 +1988,11 @@ class YumBase(depsolve.Depsolve): self.pkgSack.addPackageExcluder(repo.id, exid, 'exclude.marked') def doLock(self, lockfile = YUM_PID_FILE): @@ -151213,7 +152257,7 @@ index 99039e0..3680356 100644 if self.conf.uid != 0: # If we are a user, assume we are using the root cache ... so don't # bother locking. -@@ -1774,38 +1965,26 @@ class YumBase(depsolve.Depsolve): +@@ -1774,38 +2008,26 @@ class YumBase(depsolve.Depsolve): mypid=str(os.getpid()) while not self._lock(lockfile, mypid, 0644): @@ -151267,7 +152311,7 @@ index 99039e0..3680356 100644 # if we're not root then we don't lock - just return nicely # Note that we can get here from __del__, so if we haven't created # YumBase.conf we don't want to do so here as creating stuff inside -@@ -1830,31 +2009,69 @@ class YumBase(depsolve.Depsolve): +@@ -1830,31 +2052,69 @@ class YumBase(depsolve.Depsolve): self._unlock(lockfile) self._lockfile = None @@ -151347,7 +152391,7 @@ index 99039e0..3680356 100644 failed = False if type(fo) is types.InstanceType: -@@ -1894,9 +2111,16 @@ class YumBase(depsolve.Depsolve): +@@ -1894,9 +2154,16 @@ class YumBase(depsolve.Depsolve): def verifyChecksum(self, fo, checksumType, csum): @@ -151367,7 +152411,7 @@ index 99039e0..3680356 100644 try: filesum = misc.checksum(checksumType, fo) except Errors.MiscError, e: -@@ -1908,6 +2132,17 @@ class YumBase(depsolve.Depsolve): +@@ -1908,6 +2175,17 @@ class YumBase(depsolve.Depsolve): return 0 def downloadPkgs(self, pkglist, callback=None, callback_total=None): @@ -151385,10 +152429,28 @@ index 99039e0..3680356 100644 def mediasort(apo, bpo): # FIXME: we should probably also use the mediaid; else we # could conceivably ping-pong between different disc1's -@@ -1998,16 +2233,6 @@ class YumBase(depsolve.Depsolve): +@@ -1979,8 +2257,9 @@ class YumBase(depsolve.Depsolve): + urlgrabber.progress.text_meter_total_size(remote_size) + beg_download = time.time() + i = 0 +- local_size = 0 ++ local_size = [0] + done_repos = set() ++ async = hasattr(urlgrabber.grabber, 'parallel_wait') + for po in remote_pkgs: + # Recheck if the file is there, works around a couple of weird + # edge cases. +@@ -1992,52 +2271,47 @@ class YumBase(depsolve.Depsolve): + remote_size -= po.size + if hasattr(urlgrabber.progress, 'text_meter_total_size'): + urlgrabber.progress.text_meter_total_size(remote_size, +- local_size) ++ local_size[0]) + continue + if os.path.getsize(local) >= po.size: os.unlink(local) - checkfunc = (self.verifyPkg, (po, 1), {}) +- checkfunc = (self.verifyPkg, (po, 1), {}) - dirstat = os.statvfs(po.repo.pkgdir) - if (dirstat.f_bavail * dirstat.f_bsize) <= long(po.size): - adderror(po, _('Insufficient space in download directory %s\n' @@ -151399,19 +152461,65 @@ index 99039e0..3680356 100644 - format_number(po.size))) - continue - - try: - if i == 1 and not local_size and remote_size == po.size: - text = os.path.basename(po.relativepath) -@@ -2032,7 +2257,7 @@ class YumBase(depsolve.Depsolve): - done_repos.add(po.repoid) - - except Errors.RepoError, e: +- try: +- if i == 1 and not local_size and remote_size == po.size: +- text = os.path.basename(po.relativepath) +- else: +- text = '(%s/%s): %s' % (i, len(remote_pkgs), +- os.path.basename(po.relativepath)) +- mylocal = po.repo.getPackage(po, +- checkfunc=checkfunc, +- text=text, +- cache=po.repo.http_caching != 'none', +- ) +- local_size += po.size ++ def checkfunc(obj, po=po): ++ self.verifyPkg(obj, po, 1) ++ local_size[0] += po.size + if hasattr(urlgrabber.progress, 'text_meter_total_size'): + urlgrabber.progress.text_meter_total_size(remote_size, +- local_size) ++ local_size[0]) + if po.repoid not in done_repos: ++ done_repos.add(po.repoid) + # Check a single package per. repo. ... to give a hint to + # the user on big downloads. + result, errmsg = self.sigCheckPkg(po) + if result != 0: + self.verbose_logger.warn("%s", errmsg) +- done_repos.add(po.repoid) +- +- except Errors.RepoError, e: - adderror(po, str(e)) -+ adderror(po, exception2msg(e)) - else: - po.localpath = mylocal +- else: +- po.localpath = mylocal ++ po.localpath = obj.filename if po in errors: -@@ -2052,7 +2277,22 @@ class YumBase(depsolve.Depsolve): + del errors[po] + ++ text = os.path.basename(po.relativepath) ++ kwargs = {} ++ if async and po.repo._async: ++ kwargs['failfunc'] = lambda obj, po=po: adderror(po, exception2msg(obj.exception)) ++ kwargs['async'] = True ++ elif not (i == 1 and not local_size[0] and remote_size == po.size): ++ text = '(%s/%s): %s' % (i, len(remote_pkgs), text) ++ try: ++ po.repo.getPackage(po, ++ checkfunc=checkfunc, ++ text=text, ++ cache=po.repo.http_caching != 'none', ++ **kwargs ++ ) ++ except Errors.RepoError, e: ++ adderror(po, exception2msg(e)) ++ if async: ++ urlgrabber.grabber.parallel_wait() ++ + if hasattr(urlgrabber.progress, 'text_meter_total_size'): + urlgrabber.progress.text_meter_total_size(0) + if callback_total is not None and not errors: +@@ -2052,7 +2326,22 @@ class YumBase(depsolve.Depsolve): return errors def verifyHeader(self, fo, po, raiseError): @@ -151435,7 +152543,7 @@ index 99039e0..3680356 100644 if type(fo) is types.InstanceType: fo = fo.filename -@@ -2076,9 +2316,12 @@ class YumBase(depsolve.Depsolve): +@@ -2076,9 +2365,12 @@ class YumBase(depsolve.Depsolve): return 1 def downloadHeader(self, po): @@ -151450,7 +152558,7 @@ index 99039e0..3680356 100644 if hasattr(po, 'pkgtype') and po.pkgtype == 'local': return -@@ -2122,15 +2365,17 @@ class YumBase(depsolve.Depsolve): +@@ -2122,15 +2414,17 @@ class YumBase(depsolve.Depsolve): return def sigCheckPkg(self, po): @@ -151476,7 +152584,7 @@ index 99039e0..3680356 100644 if self._override_sigchecks: check = False hasgpgkey = 0 -@@ -2181,6 +2426,9 @@ class YumBase(depsolve.Depsolve): +@@ -2181,6 +2475,9 @@ class YumBase(depsolve.Depsolve): return result, msg def cleanUsedHeadersPackages(self): @@ -151486,7 +152594,7 @@ index 99039e0..3680356 100644 filelist = [] for txmbr in self.tsInfo: if txmbr.po.state not in TS_INSTALL_STATES: -@@ -2218,27 +2466,42 @@ class YumBase(depsolve.Depsolve): +@@ -2218,27 +2515,42 @@ class YumBase(depsolve.Depsolve): _('%s removed'), fn) def cleanHeaders(self): @@ -151531,7 +152639,7 @@ index 99039e0..3680356 100644 cachedir = self.conf.persistdir + "/rpmdb-indexes/" if not os.path.exists(cachedir): filelist = [] -@@ -2272,8 +2535,29 @@ class YumBase(depsolve.Depsolve): +@@ -2272,8 +2584,29 @@ class YumBase(depsolve.Depsolve): def doPackageLists(self, pkgnarrow='all', patterns=None, showdups=None, ignore_case=False): @@ -151563,7 +152671,7 @@ index 99039e0..3680356 100644 if showdups is None: showdups = self.conf.showdupesfromrepos ygh = misc.GenericHolder(iter=pkgnarrow) -@@ -2323,10 +2607,22 @@ class YumBase(depsolve.Depsolve): +@@ -2323,10 +2656,22 @@ class YumBase(depsolve.Depsolve): key = (pkg.name, pkg.arch) if pkg.pkgtup in dinst: reinstall_available.append(pkg) @@ -151589,7 +152697,7 @@ index 99039e0..3680356 100644 # produce the updates list of tuples elif pkgnarrow == 'updates': -@@ -2461,14 +2757,13 @@ class YumBase(depsolve.Depsolve): +@@ -2461,14 +2806,13 @@ class YumBase(depsolve.Depsolve): def findDeps(self, pkgs): @@ -151609,7 +152717,7 @@ index 99039e0..3680356 100644 results = {} for pkg in pkgs: -@@ -2495,10 +2790,22 @@ class YumBase(depsolve.Depsolve): +@@ -2495,10 +2839,22 @@ class YumBase(depsolve.Depsolve): # pre 3.2.10 API used to always showdups, so that's the default atm. def searchGenerator(self, fields, criteria, showdups=True, keys=False, searchtags=True, searchrpmdb=True): @@ -151636,7 +152744,7 @@ index 99039e0..3680356 100644 sql_fields = [] for f in fields: sql_fields.append(RPM_TO_SQLITE.get(f, f)) -@@ -2661,6 +2968,14 @@ class YumBase(depsolve.Depsolve): +@@ -2661,6 +3017,14 @@ class YumBase(depsolve.Depsolve): yield (po, vs) def searchPackageTags(self, criteria): @@ -151651,7 +152759,7 @@ index 99039e0..3680356 100644 results = {} # name = [(criteria, taglist)] for c in criteria: c = c.lower() -@@ -2677,11 +2992,16 @@ class YumBase(depsolve.Depsolve): +@@ -2677,11 +3041,16 @@ class YumBase(depsolve.Depsolve): return results def searchPackages(self, fields, criteria, callback=None): @@ -151673,7 +152781,7 @@ index 99039e0..3680356 100644 warnings.warn(_('searchPackages() will go away in a future version of Yum.\ Use searchGenerator() instead. \n'), Errors.YumFutureDeprecationWarning, stacklevel=2) -@@ -2700,6 +3020,19 @@ class YumBase(depsolve.Depsolve): +@@ -2700,13 +3069,23 @@ class YumBase(depsolve.Depsolve): def searchPackageProvides(self, args, callback=None, callback_has_matchfor=False): @@ -151693,7 +152801,15 @@ index 99039e0..3680356 100644 def _arg_data(arg): if not misc.re_glob(arg): isglob = False -@@ -2723,7 +3056,7 @@ class YumBase(depsolve.Depsolve): +- if arg[0] != '/': +- canBeFile = False +- else: +- canBeFile = True ++ canBeFile = arg.startswith('/') + else: + isglob = True + canBeFile = misc.re_filename(arg) +@@ -2723,7 +3102,7 @@ class YumBase(depsolve.Depsolve): where = self.returnPackagesByDep(arg) else: usedDepString = False @@ -151702,10 +152818,16 @@ index 99039e0..3680356 100644 self.verbose_logger.log(logginglevels.DEBUG_1, P_('Searching %d package', 'Searching %d packages', len(where)), len(where)) -@@ -2817,25 +3150,93 @@ class YumBase(depsolve.Depsolve): +@@ -2817,25 +3196,160 @@ class YumBase(depsolve.Depsolve): return matches +- def doGroupLists(self, uservisible=0, patterns=None, ignore_case=True): +- """returns two lists of groups, installed groups and available groups +- optional 'uservisible' bool to tell it whether or not to return +- only groups marked as uservisible""" +- +- + def _groupInstalledData(self, group): + """ Return a dict of + pkg_name => @@ -151740,30 +152862,81 @@ index 99039e0..3680356 100644 + + return ret + ++ def _groupInstalledEnvData(self, evgroup): ++ """ Return a dict of ++ grp_name => ++ (installed, available, ++ backlisted-installed, blacklisted-available). """ ++ ret = {} ++ if not evgroup or self.conf.group_command != 'objects': ++ return ret ++ ++ grp_names = {} ++ if evgroup.environmentid in self.igroups.groups: ++ grp_names = self.igroups.environments[evgroup.environmentid] ++ grp_names = grp_names.grp_names ++ ++ for grp_name in set(evgroup.allgroups + list(grp_names)): ++ igrp = self.igroups.groups.get(grp_name) ++ if grp_name not in grp_names and not igrp: ++ ret[grp_name] = 'available' ++ continue ++ ++ if not igrp: ++ ret[grp_name] = 'blacklisted-available' ++ continue ++ ++ if igrp.environment == evgroup.environmentid: ++ ret[grp_name] = 'installed' ++ break ++ else: ++ ret[grp_name] = 'blacklisted-installed' ++ ++ return ret ++ + def _groupReturnGroups(self, patterns=None, ignore_case=True): + igrps = None ++ ievgrps = None + if patterns is None: + grps = self.comps.groups + if self.conf.group_command == 'objects': + igrps = self.igroups.groups.values() -+ return igrps, grps ++ evgrps = self.comps.environments ++ if False and self.conf.group_command == 'objects': ++ # FIXME: Environment groups. ++ ievgrps = self.igroups.environments.values() ++ return igrps, grps, ievgrps, evgrps ++ ++ gpats = [] ++ epats = [] ++ for pat in patterns: ++ if pat.startswith('@^'): ++ epats.append(pat[2:]) ++ elif pat.startswith('@'): ++ gpats.append(pat[1:]) ++ else: ++ epats.append(pat) ++ gpats.append(pat) ++ ++ epats = ",".join(epats) ++ gpats = ",".join(gpats) + -+ pats = ",".join(patterns) + cs = not ignore_case -+ grps = self.comps.return_groups(pats, case_sensitive=cs) ++ grps = self.comps.return_groups(gpats, 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 ++ gpats = gpats + "," + ",".join([grp.groupid for grp in grps]) ++ igrps = self.igroups.return_groups(gpats, case_sensitive=cs) + - def doGroupLists(self, uservisible=0, patterns=None, ignore_case=True): -- """returns two lists of groups, installed groups and available groups -- optional 'uservisible' bool to tell it whether or not to return -- only groups marked as uservisible""" -- -- ++ evgrps = self.comps.return_environments(epats, case_sensitive=cs) ++ if self.conf.group_command == 'objects': ++ epats = epats+ "," + ",".join([grp.environmentid for grp in evgrps]) ++ ievgrps = self.igroups.return_environments(epats, case_sensitive=cs) ++ return igrps, grps, ievgrps, evgrps ++ ++ def doGroupLists(self, uservisible=0, patterns=None, ignore_case=True, ++ return_evgrps=False): + """Return two lists of groups: installed groups and available + groups. + @@ -151774,9 +152947,13 @@ index 99039e0..3680356 100644 + lists. If not given, all groups will be included + :param ignore_case: whether to ignore case when determining + whether group names match the strings in *patterns* ++ :param return_evgrps: whether to return environment groups as well as ++ package groups + """ installed = [] available = [] ++ einstalled = [] ++ eavailable = [] if self.comps.compscount == 0: raise Errors.GroupsError, _('No group data available for configured repositories') @@ -151786,13 +152963,20 @@ index 99039e0..3680356 100644 - else: - grps = self.comps.return_groups(",".join(patterns), - case_sensitive=not ignore_case) -+ igrps, grps = self._groupReturnGroups(patterns, ignore_case) ++ igrps, grps, ievgrps, evgrps = self._groupReturnGroups(patterns, ++ ignore_case) + + if igrps is not None: + digrps = {} + for igrp in igrps: + digrps[igrp.gid] = igrp + igrps = digrps ++ ++ if ievgrps is not None: ++ digrps = {} ++ for ievgrp in ievgrps: ++ digrps[ievgrp.evgid] = ievgrp ++ ievgrps = digrps + for grp in grps: - if grp.installed: @@ -151807,23 +152991,53 @@ index 99039e0..3680356 100644 if uservisible: if grp.user_visible: installed.append(grp) -@@ -2848,12 +3249,29 @@ class YumBase(depsolve.Depsolve): +@@ -2847,34 +3361,98 @@ class YumBase(depsolve.Depsolve): + available.append(grp) else: available.append(grp) ++ ++ for evgrp in evgrps: ++ if ievgrps is None: ++ evgrp_installed = evgrp.installed ++ else: ++ evgrp_installed = evgrp.environmentid in ievgrps ++ if evgrp_installed: ++ del ievgrps[evgrp.environmentid] ++ ++ if evgrp_installed: ++ einstalled.append(evgrp) ++ else: ++ eavailable.append(evgrp) + if igrps is None: -+ return sorted(installed), sorted(available) ++ igrps = {} ++ if ievgrps is None: ++ ievgrps = {} + + 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.groupid = igrp.gid + grp.installed = True + grp.name = grp.groupid + for pkg_name in igrp.pkg_names: + grp.mandatory_packages[pkg_name] = 1 + installed.append(grp) + ++ for ievgrp in ievgrps.values(): ++ # These are installed evgroups that aren't in comps anymore. so we ++ # create fake comps evgroups for them. ++ evgrp = comps.Environment() ++ grp.environmentid = ievgrp.evgid ++ evgrp.installed = True ++ evgrp.name = evgrp.environmentid ++ evgrp._groups = list(ievgrp.groups) ++ einstalled.append(evgrp) ++ ++ if return_evgrps: ++ return (sorted(installed), sorted(available), ++ sorted(einstalled), sorted(eavailable)) return sorted(installed), sorted(available) - @@ -151838,26 +153052,41 @@ index 99039e0..3680356 100644 + transaction set by this function + """ txmbrs_used = [] - - thesegroups = self.comps.return_groups(grpid) -@@ -2861,20 +3279,28 @@ class YumBase(depsolve.Depsolve): +- +- thesegroups = self.comps.return_groups(grpid) ++ ++ if self.conf.group_command == 'objects': ++ thesegroups = self.igroups.return_groups(grpid) ++ else: ++ thesegroups = self.comps.return_groups(grpid) + if not thesegroups: 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: +- pkgs = thisgroup.packages +- for pkg in thisgroup.packages: ++ ++ if self.conf.group_command == 'objects': ++ pkgs = thisgroup.pkg_names ++ gid = thisgroup.gid ++ else: ++ pkgs = thisgroup.packages ++ gid = thisgroup.groupid ++ ++ for pkg in pkgs: + 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) +- txmbr.groups.append(thisgroup.groupid) ++ txmbr.groups.append(gid) + if igroup_data: -+ self.igroups.del_group(thisgroup.groupid) ++ self.igroups.del_group(gid) return txmbrs_used @@ -151871,7 +153100,7 @@ index 99039e0..3680356 100644 thesegroups = self.comps.return_groups(grpid) if not thesegroups: raise Errors.GroupsError, _("No Group named %s exists") % to_unicode(grpid) -@@ -2898,13 +3324,18 @@ class YumBase(depsolve.Depsolve): +@@ -2898,13 +3476,58 @@ class YumBase(depsolve.Depsolve): self.tsInfo.remove(txmbr.po.pkgtup) @@ -151882,8 +153111,48 @@ index 99039e0..3680356 100644 - Optionally take: - group_package_types=List - overrides self.conf.group_package_types - enable_group_conditionals=Bool - overrides self.conf.enable_group_conditionals ++ def environmentRemove(self, evgrpid): ++ """Mark all the packages in the given group to be removed. ++ ++ :param evgrpid: the name of the environment containing the groups to ++ mark for removal ++ :return: a list of transaction members added to the ++ transaction set by this function ++ """ ++ txmbrs_used = [] ++ ++ if self.conf.group_command == 'objects': ++ thesegroups = self.igroups.return_environments(evgrpid) ++ else: ++ thesegroups = self.comps.return_environments(evgrpid) ++ if not thesegroups: ++ raise Errors.GroupsError, _("No Environment named %s exists") % to_unicode(evgrpid) ++ ++ for thisgroup in thesegroups: ++ igroup_data = self._groupInstalledEnvData(thisgroup) ++ ++ if self.conf.group_command == 'objects': ++ grps = thisgroup.grp_names ++ evgid = thisgroup.evgid ++ else: ++ grps = thisgroup.allgroups ++ evgid = thisgroup.environmentid ++ ++ for grp in grps: ++ if grp in igroup_data and igroup_data[grp] != 'installed': ++ continue ++ ++ txmbrs = self.groupRemove(grp) ++ txmbrs_used.extend(txmbrs) ++ for txmbr in txmbrs: ++ txmbr.environments.append(evgid) ++ if igroup_data: ++ self.igroups.del_environment(evgid) ++ ++ return txmbrs_used ++ + def selectGroup(self, grpid, group_package_types=[], -+ enable_group_conditionals=None, upgrade=False): ++ enable_group_conditionals=None, upgrade=False, ievgrp=None): + """Mark all the packages in the given group to be installed. + + :param grpid: the name of the group containing the packages to @@ -151897,7 +153166,17 @@ index 99039e0..3680356 100644 """ if not self.comps.has_group(grpid): -@@ -2934,12 +3365,47 @@ class YumBase(depsolve.Depsolve): +@@ -2920,6 +3543,9 @@ class YumBase(depsolve.Depsolve): + if group_package_types: + package_types = group_package_types + ++ if self.conf.group_command == 'compat': ++ upgrade = False ++ + for thisgroup in thesegroups: + if thisgroup.selected: + continue +@@ -2934,12 +3560,49 @@ class YumBase(depsolve.Depsolve): if 'optional' in package_types: pkgs.extend(thisgroup.optional_packages) @@ -151907,7 +153186,8 @@ index 99039e0..3680356 100644 + if thisgroup.groupid in self.igroups.groups: + igrp = self.igroups.groups[thisgroup.groupid] + else: -+ self.igroups.add_group(thisgroup.groupid,thisgroup.packages) ++ self.igroups.add_group(thisgroup.groupid, ++ thisgroup.packages, ievgrp) + pkgs.extend(list(igroup_data.keys())) + old_txmbrs = len(txmbrs_used) @@ -151935,7 +153215,8 @@ index 99039e0..3680356 100644 + if (upgrade and + (self.conf.group_command == 'simple' or + (igroup_data and igroup_data[pkg] == 'installed'))): -+ txmbrs = self.update(name = pkg) ++ txmbrs = self.update(name = pkg, ++ pkg_warning_level='debug2') + elif igroup_data and igroup_data[pkg] == 'installed': + pass # Don't upgrade on install. + else: @@ -151946,15 +153227,28 @@ index 99039e0..3680356 100644 except Errors.InstallError, e: self.verbose_logger.debug(_('No package named %s available to be installed'), pkg) -@@ -2953,6 +3419,7 @@ class YumBase(depsolve.Depsolve): +@@ -2953,7 +3616,9 @@ class YumBase(depsolve.Depsolve): group_conditionals = enable_group_conditionals count_cond_test = 0 +- if group_conditionals: + # FIXME: What do we do about group conditionals when group==objects - if group_conditionals: ++ # or group upgrade for group_command=simple? ++ if not upgrade and group_conditionals: for condreq, cond in thisgroup.conditional_packages.iteritems(): if self.isPackageInstalled(cond): -@@ -2997,10 +3464,14 @@ class YumBase(depsolve.Depsolve): + try: +@@ -2990,17 +3655,22 @@ class YumBase(depsolve.Depsolve): + if cond not in self.tsInfo.conditionals: + self.tsInfo.conditionals[cond] = [] + self.tsInfo.conditionals[cond].extend(pkgs) +- if len(txmbrs_used) == old_txmbrs: +- self.logger.critical(_('Warning: Group %s does not have any packages.'), thisgroup.groupid) ++ ++ if not upgrade and len(txmbrs_used) == old_txmbrs: ++ self.logger.critical(_('Warning: Group %s does not have any packages to install.'), thisgroup.groupid) + if count_cond_test: + self.logger.critical(_('Group %s does have %u conditional packages, which may get installed.'), count_cond_test) return txmbrs_used def deselectGroup(self, grpid, force=False): @@ -151973,9 +153267,100 @@ index 99039e0..3680356 100644 if not self.comps.has_group(grpid): raise Errors.GroupsError, _("No Group named %s exists") % to_unicode(grpid) -@@ -3035,12 +3506,21 @@ class YumBase(depsolve.Depsolve): +@@ -3008,7 +3678,8 @@ class YumBase(depsolve.Depsolve): + thesegroups = self.comps.return_groups(grpid) + if not thesegroups: + raise Errors.GroupsError, _("No Group named %s exists") % to_unicode(grpid) +- ++ ++ # FIXME: Do something with groups as objects, and env. groups. + for thisgroup in thesegroups: + thisgroup.selected = False + +@@ -3034,13 +3705,102 @@ class YumBase(depsolve.Depsolve): + for pkg in self.tsInfo.conditionals.get(txmbr.name, []): self.tsInfo.remove(pkg.pkgtup) ++ def selectEnvironment(self, evgrpid, group_package_types=[], ++ enable_group_conditionals=None, upgrade=False): ++ """Mark all the groups in the given environment group to be installed. ++ ++ :param evgrpid: the name of the env. group containing the groups to ++ mark for installation ++ :param group_package_types: a list of the types of groups to ++ work with. This overrides self.conf.group_package_types ++ :param enable_group_conditionals: overrides ++ self.conf.enable_group_conditionals ++ :return: a list of transaction members added to the ++ transaction set by this function ++ """ ++ evgrps = self.comps.return_environments(evgrpid) ++ if not evgrps: ++ raise Errors.GroupsError, _("No Environment named %s exists") % to_unicode(evgrpid) ++ ++ ret = [] ++ for evgrp in evgrps: ++ ++ ievgrp = None ++ if self.conf.group_command == 'compat': ++ grps = ",".join(sorted(evgrp.groups)) ++ elif self.conf.group_command == 'simple': ++ if not upgrade: ++ grps = ",".join(sorted(evgrp.groups)) ++ else: # Only upgrade the installed groups... ++ grps = [] ++ for grpid in evgrp.groups: ++ grp = self.comps.return_group(grpid) ++ if grp is None: ++ continue ++ if not grp.installed: ++ continue ++ grps.append(grpid) ++ grps = ",".join(sorted(grps)) ++ elif self.conf.group_command == 'objects': ++ igroup_data = self._groupInstalledEnvData(evgrp) ++ ++ grps = [] ++ for grpid in evgrp.groups: ++ if (grpid not in igroup_data or ++ igroup_data[grpid].startswith('blacklisted')): ++ msg = _('Skipping group %s from environment %s'), ++ self.verbose_logger.log(logginglevels.DEBUG_2, ++ msg, grpid, evgrp.environmentid) ++ continue ++ grps.append(grpid) ++ if evgrp.environmentid in self.igroups.environments: ++ ievgrp = self.igroups.environments[evgrp.environmentid] ++ else: ++ self.igroups.add_environment(evgrp.environmentid, ++ evgrp.allgroups) ++ grps = ",".join(sorted(grps)) ++ ++ txs = self.selectGroup(grps, ++ group_package_types, ++ enable_group_conditionals, upgrade, ++ ievgrp=ievgrp) ++ ret.extend(txs) ++ return ret ++ ++ def deselectEnvironment(self, evgrpid, force=False): ++ """Unmark the groups in the given environment group from being ++ installed. ++ ++ :param evgrpid: the name of the environment group containing the ++ groups to unmark from installation ++ :param force: if True, force remove all the packages in the ++ given groups from the transaction ++ """ ++ evgrps = self.comps.return_environments(evgrpid) ++ if not thesegroups: ++ raise Errors.GroupsError, _("No Environment named %s exists") % to_unicode(evgrpid) ++ ++ for evgrp in evgrps: ++ grps = ",".join(sorted(evgrp.groups)) ++ self.deselectGroup(grps, force) ++ # FIXME: env. needs to be marked not-to-be-installed, etc. ++ def getPackageObject(self, pkgtup, allow_missing=False): - """retrieves a packageObject from a pkgtuple - if we need - to pick and choose which one is best we better call out @@ -152001,7 +153386,7 @@ index 99039e0..3680356 100644 # look it up in the self.localPackages first: for po in self.localPackages: if po.pkgtup == pkgtup: -@@ -3049,7 +3529,7 @@ class YumBase(depsolve.Depsolve): +@@ -3049,7 +3809,7 @@ class YumBase(depsolve.Depsolve): pkgs = self.pkgSack.searchPkgTuple(pkgtup) if len(pkgs) == 0: @@ -152010,7 +153395,7 @@ index 99039e0..3680356 100644 if allow_missing: # This can happen due to excludes after .up has return None # happened. raise Errors.DepError, _('Package tuple %s could not be found in packagesack') % str(pkgtup) -@@ -3065,13 +3545,21 @@ class YumBase(depsolve.Depsolve): +@@ -3065,13 +3825,21 @@ class YumBase(depsolve.Depsolve): return result def getInstalledPackageObject(self, pkgtup): @@ -152037,7 +153422,7 @@ index 99039e0..3680356 100644 raise Errors.RpmDBError, _('Package tuple %s could not be found in rpmdb') % str(pkgtup) # Dito. FIXME from getPackageObject() for len() > 1 ... :) -@@ -3079,9 +3567,11 @@ class YumBase(depsolve.Depsolve): +@@ -3079,9 +3847,11 @@ class YumBase(depsolve.Depsolve): return po def gpgKeyCheck(self): @@ -152051,7 +153436,7 @@ index 99039e0..3680356 100644 gpgkeyschecked = self.conf.cachedir + '/.gpgkeyschecked.yum' if os.path.exists(gpgkeyschecked): return 1 -@@ -3106,9 +3596,13 @@ class YumBase(depsolve.Depsolve): +@@ -3106,9 +3876,13 @@ class YumBase(depsolve.Depsolve): return 1 def returnPackagesByDep(self, depstring): @@ -152067,7 +153452,7 @@ index 99039e0..3680356 100644 if not depstring: return [] -@@ -3135,9 +3629,16 @@ class YumBase(depsolve.Depsolve): +@@ -3135,9 +3909,16 @@ class YumBase(depsolve.Depsolve): return self.pkgSack.getProvides(depname, depflags, depver).keys() def returnPackageByDep(self, depstring): @@ -152087,7 +153472,7 @@ index 99039e0..3680356 100644 # we get all sorts of randomness here errstring = depstring if type(depstring) not in types.StringTypes: -@@ -3149,16 +3650,22 @@ class YumBase(depsolve.Depsolve): +@@ -3149,16 +3930,22 @@ class YumBase(depsolve.Depsolve): raise Errors.YumBaseError, _('No Package found for %s') % errstring ps = ListPackageSack(pkglist) @@ -152114,7 +153499,7 @@ index 99039e0..3680356 100644 if not depstring: return [] -@@ -3184,12 +3691,47 @@ class YumBase(depsolve.Depsolve): +@@ -3184,12 +3971,47 @@ class YumBase(depsolve.Depsolve): return self.rpmdb.getProvides(depname, depflags, depver).keys() @@ -152164,7 +153549,7 @@ index 99039e0..3680356 100644 if len(pkglist) == 0: -@@ -3198,14 +3740,23 @@ class YumBase(depsolve.Depsolve): +@@ -3198,14 +4020,23 @@ class YumBase(depsolve.Depsolve): if len(pkglist) == 1: return pkglist[0] @@ -152194,7 +153579,7 @@ index 99039e0..3680356 100644 returnlist = [] compatArchList = self.arch.get_arch_list(arch) multiLib = [] -@@ -3222,9 +3773,9 @@ class YumBase(depsolve.Depsolve): +@@ -3222,9 +4053,9 @@ class YumBase(depsolve.Depsolve): singleLib.append(po) # we now have three lists. find the best package(s) of each @@ -152207,7 +153592,7 @@ index 99039e0..3680356 100644 if single_name and multi and single and multi.name != single.name: # Sinlge _must_ match multi, if we want a single package name -@@ -3238,7 +3789,7 @@ class YumBase(depsolve.Depsolve): +@@ -3238,7 +4069,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: @@ -152216,7 +153601,7 @@ index 99039e0..3680356 100644 if best.arch == "noarch": returnlist.append(no) else: -@@ -3246,7 +3797,7 @@ class YumBase(depsolve.Depsolve): +@@ -3246,7 +4077,7 @@ class YumBase(depsolve.Depsolve): if single: returnlist.append(single) # similar for the non-multilib case elif single: @@ -152225,7 +153610,7 @@ index 99039e0..3680356 100644 if best.arch == "noarch": returnlist.append(no) else: -@@ -3350,23 +3901,27 @@ class YumBase(depsolve.Depsolve): +@@ -3350,28 +4181,58 @@ class YumBase(depsolve.Depsolve): done = True slow = next_func(slow) @@ -152240,6 +153625,20 @@ index 99039e0..3680356 100644 assert pattern[0] == '@' group_string = pattern[1:] tx_return = [] ++ ++ if group_string and group_string[0] == '^': ++ group_string = group_string[1:] ++ # Actually dealing with "environment groups". ++ for env_grp in self.comps.return_environments(group_string): ++ try: ++ txmbrs = self.selectEnvironment(env_grp.environmentid, ++ upgrade=upgrade) ++ tx_return.extend(txmbrs) ++ except yum.Errors.GroupsError: ++ self.logger.critical(_('Warning: Environment Group %s does not exist.'), group_string) ++ continue ++ return tx_return ++ for group in self.comps.return_groups(group_string): try: - txmbrs = self.selectGroup(group.groupid) @@ -152258,16 +153657,46 @@ index 99039e0..3680356 100644 def _at_groupremove(self, pattern): " Do groupremove via. leading @ on the cmd line, for remove." assert pattern[0] == '@' -@@ -3398,7 +3953,7 @@ class YumBase(depsolve.Depsolve): + group_string = pattern[1:] + tx_return = [] ++ ++ if group_string and group_string[0] == '^': ++ group_string = group_string[1:] ++ # Actually dealing with "environment groups". ++ try: ++ txmbrs = self.environmentRemove(group_string) ++ except yum.Errors.GroupsError: ++ self.logger.critical(_('Warning: Environment Group %s does not exist.'), group_string) ++ else: ++ tx_return.extend(txmbrs) ++ return tx_return ++ + try: + txmbrs = self.groupRemove(group_string) + except yum.Errors.GroupsError: +@@ -3387,6 +4248,8 @@ class YumBase(depsolve.Depsolve): + assert pattern[0] == '@' + grpid = pattern[1:] + ++ # FIXME: **** environment groups and groups as objects... **** ++ + thesegroups = self.comps.return_groups(grpid) + if not thesegroups: + raise Errors.GroupsError, _("No Group named %s exists") % to_unicode(grpid) +@@ -3398,7 +4261,11 @@ class YumBase(depsolve.Depsolve): def _minus_deselect(self, pattern): """ Remove things from the transaction, like kickstart. """ assert pattern[0] == '-' - pat = pattern[1:] + pat = pattern[1:].strip() ++ ++ if pat and pat.startswith('@^'): ++ pat = pat[2:] ++ return self.deselectEnvironment(pat) if pat and pat[0] == '@': pat = pat[1:] -@@ -3437,14 +3992,61 @@ class YumBase(depsolve.Depsolve): +@@ -3437,14 +4304,83 @@ class YumBase(depsolve.Depsolve): if flag not in self.tsInfo.probFilterFlags: self.tsInfo.probFilterFlags.append(flag) @@ -152295,6 +153724,28 @@ index 99039e0..3680356 100644 + return True + + return False ++ ++ def _valid_install_arch(self, po, ipkgs=None): ++ ''' See if we can install this arch of package, mainly for ++ i386 vs. i586 or ppc64 vs. ppc64 etc. ''' ++ if not ipkgs: ++ ipkgs = self.rpmdb.searchNames([po.name]) ++ for txmbr in self.tsInfo.matchNaevr(po.name): ++ if txmbr.output_state not in TS_INSTALL_STATES: ++ continue ++ ipkgs.append(txmbr.po) ++ ++ for ipkg in ipkgs: ++ if po.arch == ipkg.arch: ++ continue ++ if not po.verEQ(ipkg): ++ continue ++ if canCoinstall(po.arch, ipkg.arch): ++ continue ++ self.verbose_logger.log(logginglevels.INFO_2, ++ _("Package: %s - can't co-install with %s"), po, ipkg) ++ return False ++ return True + def install(self, po=None, **kwargs): - """try to mark for install the item specified. Uses provided package @@ -152335,7 +153786,7 @@ index 99039e0..3680356 100644 pkgs = [] was_pattern = False if po: -@@ -3477,20 +4079,12 @@ class YumBase(depsolve.Depsolve): +@@ -3477,20 +4413,12 @@ class YumBase(depsolve.Depsolve): self.verbose_logger.debug(_('Checking for virtual provide or file-provide for %s'), arg) @@ -152362,7 +153813,7 @@ index 99039e0..3680356 100644 else: nevra_dict = self._nevra_kwarg_parse(kwargs) -@@ -3577,8 +4171,8 @@ class YumBase(depsolve.Depsolve): +@@ -3577,17 +4505,20 @@ class YumBase(depsolve.Depsolve): continue # make sure this shouldn't be passed to update: @@ -152373,7 +153824,11 @@ index 99039e0..3680356 100644 txmbrs = self.update(po=po) tx_return.extend(txmbrs) continue -@@ -3587,7 +4181,7 @@ class YumBase(depsolve.Depsolve): ++ ++ if not self._valid_install_arch(po, ipkgs): ++ continue + + # Make sure we're not installing a package which is obsoleted by # something else in the repo. Unless there is a obsoletion loop, # at which point ignore everything. obsoleting_pkg = None @@ -152382,7 +153837,7 @@ index 99039e0..3680356 100644 obsoleting_pkg = self._test_loop(po, self._pkg2obspkg) if obsoleting_pkg is not None: # this is not a definitive check but it'll make sure we don't -@@ -3600,23 +4194,23 @@ class YumBase(depsolve.Depsolve): +@@ -3600,23 +4531,23 @@ class YumBase(depsolve.Depsolve): already_obs = pkgs[0] if already_obs: @@ -152413,7 +153868,7 @@ index 99039e0..3680356 100644 continue # make sure we don't have a name.arch of this already installed -@@ -3630,7 +4224,7 @@ class YumBase(depsolve.Depsolve): +@@ -3630,7 +4561,7 @@ class YumBase(depsolve.Depsolve): found = True break if not found: @@ -152422,7 +153877,7 @@ index 99039e0..3680356 100644 txmbrs = self.update(po=po) tx_return.extend(txmbrs) continue -@@ -3719,14 +4313,33 @@ class YumBase(depsolve.Depsolve): +@@ -3719,19 +4650,47 @@ class YumBase(depsolve.Depsolve): return txmbr def update(self, po=None, requiringPo=None, update_to=False, **kwargs): @@ -152463,7 +153918,21 @@ index 99039e0..3680356 100644 # check for args - if no po nor kwargs, do them all # if po, do it, ignore all else # if no po do kwargs -@@ -3765,7 +4378,12 @@ class YumBase(depsolve.Depsolve): + # uninstalled pkgs called for update get returned with errors in a list, maybe? + ++ pkg_warn = kwargs.get('pkg_warning_level', 'flibble') ++ def _dbg2(*args, **kwargs): ++ self.verbose_logger.log(logginglevels.DEBUG_2, *args, **kwargs) ++ level2func = {'debug2' : _dbg2, ++ 'warning' : self.verbose_logger.warning} ++ if pkg_warn not in level2func: ++ pkg_warn = 'warning' ++ pkg_warn = level2func[pkg_warn] ++ + tx_return = [] + if not po and not kwargs: # update everything (the easy case) + self.verbose_logger.log(logginglevels.DEBUG_2, _('Updating Everything')) +@@ -3765,7 +4724,14 @@ class YumBase(depsolve.Depsolve): if new is None: continue tx_return.extend(self.update(po=new)) @@ -152471,13 +153940,15 @@ index 99039e0..3680356 100644 + + # Upgrade the installed groups, as part of generic "yum upgrade" + if self.conf.group_command == 'objects': ++ for ievgrp in self.igroups.environments: ++ tx_return.extend(self._at_groupupgrade('@^' + ievgrp)) + for igrp in self.igroups.groups: -+ tx_return.extend(self._at_groupupgrade(igrp)) ++ tx_return.extend(self._at_groupupgrade('@' + igrp)) + return tx_return # complications -@@ -3787,7 +4405,7 @@ class YumBase(depsolve.Depsolve): +@@ -3787,7 +4753,7 @@ class YumBase(depsolve.Depsolve): return self._minus_deselect(kwargs['pattern']) if kwargs['pattern'] and kwargs['pattern'][0] == '@': @@ -152486,10 +153957,22 @@ index 99039e0..3680356 100644 arg = kwargs['pattern'] if not update_to: -@@ -3920,6 +4538,18 @@ class YumBase(depsolve.Depsolve): +@@ -3843,7 +4809,7 @@ class YumBase(depsolve.Depsolve): + availpkgs = self._compare_providers(availpkgs, requiringPo) + availpkgs = map(lambda x: x[0], availpkgs) + elif not availpkgs: +- self.logger.warning(_("No package matched to upgrade: %s"), self._ui_nevra_dict(nevra_dict)) ++ pkg_warn(_("No package matched to upgrade: %s"), self._ui_nevra_dict(nevra_dict)) + + # for any thing specified + # get the list of available pkgs matching it (or take the po) +@@ -3920,6 +4886,21 @@ class YumBase(depsolve.Depsolve): tx_return.append(txmbr) for available_pkg in availpkgs: ++ if not self._valid_install_arch(available_pkg): ++ continue ++ + # "Just do it" if it's a local pkg. + if isinstance(available_pkg, YumLocalPackage): + n = available_pkg.name @@ -152505,7 +153988,7 @@ index 99039e0..3680356 100644 # Make sure we're not installing a package which is obsoleted by # something else in the repo. Unless there is a obsoletion loop, # at which point ignore everything. -@@ -3985,11 +4615,18 @@ class YumBase(depsolve.Depsolve): +@@ -3985,11 +4966,18 @@ class YumBase(depsolve.Depsolve): return tx_return def remove(self, po=None, **kwargs): @@ -152529,7 +154012,7 @@ index 99039e0..3680356 100644 if not po and not kwargs: raise Errors.RemoveError, 'Nothing specified to remove' -@@ -4055,17 +4692,19 @@ class YumBase(depsolve.Depsolve): +@@ -4055,17 +5043,19 @@ class YumBase(depsolve.Depsolve): return tx_return def installLocal(self, pkg, po=None, updateonly=False): @@ -152559,7 +154042,7 @@ index 99039e0..3680356 100644 # read in the package into a YumLocalPackage Object # append it to self.localPackages # check if it can be installed or updated based on nevra versus rpmdb -@@ -4183,16 +4822,15 @@ class YumBase(depsolve.Depsolve): +@@ -4183,16 +5173,15 @@ class YumBase(depsolve.Depsolve): return tx_return def reinstallLocal(self, pkg, po=None): @@ -152584,7 +154067,7 @@ index 99039e0..3680356 100644 if not po: try: po = YumUrlPackage(self, ts=self.rpmdb.readOnlyTS(), url=pkg, -@@ -4215,9 +4853,19 @@ class YumBase(depsolve.Depsolve): +@@ -4215,9 +5204,19 @@ class YumBase(depsolve.Depsolve): return self.reinstall(po=po) def reinstall(self, po=None, **kwargs): @@ -152607,7 +154090,7 @@ index 99039e0..3680356 100644 self._add_prob_flags(rpm.RPMPROB_FILTER_REPLACEPKG, rpm.RPMPROB_FILTER_REPLACENEWFILES, rpm.RPMPROB_FILTER_REPLACEOLDFILES) -@@ -4259,16 +4907,15 @@ class YumBase(depsolve.Depsolve): +@@ -4259,16 +5258,15 @@ class YumBase(depsolve.Depsolve): return tx_mbrs def downgradeLocal(self, pkg, po=None): @@ -152632,7 +154115,7 @@ index 99039e0..3680356 100644 if not po: try: po = YumUrlPackage(self, ts=self.rpmdb.readOnlyTS(), url=pkg, -@@ -4309,13 +4956,19 @@ class YumBase(depsolve.Depsolve): +@@ -4309,13 +5307,19 @@ class YumBase(depsolve.Depsolve): return False def downgrade(self, po=None, **kwargs): @@ -152659,7 +154142,28 @@ index 99039e0..3680356 100644 if not po and not kwargs: raise Errors.DowngradeError, 'Nothing specified to downgrade' -@@ -4457,7 +5110,7 @@ class YumBase(depsolve.Depsolve): +@@ -4397,6 +5401,10 @@ class YumBase(depsolve.Depsolve): + # installed version. Indexed fromn the latest installed pkgtup. + downgrade_apkgs = {} + for pkg in sorted(apkgs): ++ # We are cleverer here, I think... ++ # if not self._valid_install_arch(pkg, ipkgs): ++ # continue ++ + na = (pkg.name, pkg.arch) + + # Here we allow downgrades from .i386 => .noarch, or .i586 => .i386 +@@ -4421,6 +5429,9 @@ class YumBase(depsolve.Depsolve): + warned_nas.add(na) + continue + ++ if pkg.verEQ(lipkg): ++ continue ++ + if pkg.verGE(lipkg): + if na not in warned_nas: + msg = _('Only Upgrade available on package: %s') % pkg +@@ -4457,7 +5468,7 @@ class YumBase(depsolve.Depsolve): if e and v and r: evr = '%s:%s-%s' % (e, v, r) elif v and r: @@ -152668,7 +154172,7 @@ index 99039e0..3680356 100644 elif e and v: evr = '%s:%s' % (e, v) elif v: # e and r etc. is just too weird to print -@@ -4500,12 +5153,24 @@ class YumBase(depsolve.Depsolve): +@@ -4500,12 +5511,24 @@ class YumBase(depsolve.Depsolve): return returndict @@ -152696,7 +154200,7 @@ index 99039e0..3680356 100644 old_conf_obs = self.conf.obsoletes self.conf.obsoletes = False done = False -@@ -4515,19 +5180,46 @@ class YumBase(depsolve.Depsolve): +@@ -4515,19 +5538,46 @@ class YumBase(depsolve.Depsolve): done = True for pkg in transaction.trans_data: if pkg.state == 'Downgrade': @@ -152743,7 +154247,7 @@ index 99039e0..3680356 100644 if self.install(pkgtup=pkg.pkgtup): done = True for pkg in transaction.trans_data: -@@ -4538,8 +5230,14 @@ class YumBase(depsolve.Depsolve): +@@ -4538,8 +5588,14 @@ class YumBase(depsolve.Depsolve): return done def history_undo(self, transaction): @@ -152760,7 +154264,25 @@ index 99039e0..3680356 100644 # NOTE: This is somewhat basic atm. ... for instance we don't check # that we are going from the old new version. However it's still # better than the RHN rollback code, and people pay for that :). -@@ -4674,39 +5372,49 @@ class YumBase(depsolve.Depsolve): +@@ -4616,7 +5672,7 @@ class YumBase(depsolve.Depsolve): + + except urlgrabber.grabber.URLGrabError, e: + raise Errors.YumBaseError(_('GPG key retrieval failed: ') + +- to_unicode(str(e))) ++ exception2msg(e)) + + # check for a .asc file accompanying it - that's our gpg sig on the key + # suck it down and do the check +@@ -4649,7 +5705,7 @@ class YumBase(depsolve.Depsolve): + keys_info = misc.getgpgkeyinfo(rawkey, multiple=True) + except ValueError, e: + raise Errors.YumBaseError(_('Invalid GPG Key from %s: %s') % +- (url, to_unicode(str(e)))) ++ (url, exception2msg(e))) + keys = [] + for keyinfo in keys_info: + thiskey = {} +@@ -4674,39 +5730,49 @@ class YumBase(depsolve.Depsolve): if pkgs: pkgs = sorted(pkgs)[-1] msg = (_('Importing %s key 0x%s:\n' @@ -152828,7 +154350,7 @@ index 99039e0..3680356 100644 user_cb_fail = False for keyurl in keyurls: keys = self._retrievePublicKey(keyurl, repo) -@@ -4725,7 +5433,9 @@ class YumBase(depsolve.Depsolve): +@@ -4725,7 +5791,9 @@ class YumBase(depsolve.Depsolve): # Try installing/updating GPG key self._getKeyImportMessage(info, keyurl) rc = False @@ -152839,7 +154361,7 @@ index 99039e0..3680356 100644 rc = True # grab the .sig/.asc for the keyurl, if it exists -@@ -4751,8 +5461,8 @@ class YumBase(depsolve.Depsolve): +@@ -4751,8 +5819,8 @@ class YumBase(depsolve.Depsolve): ts = self.rpmdb.readOnlyTS() result = ts.pgpImportPubkey(misc.procgpgkey(info['raw_key'])) if result != 0: @@ -152850,7 +154372,7 @@ index 99039e0..3680356 100644 self.logger.info(_('Key imported successfully')) key_installed = True -@@ -4760,18 +5470,20 @@ class YumBase(depsolve.Depsolve): +@@ -4760,18 +5828,20 @@ class YumBase(depsolve.Depsolve): raise Errors.YumBaseError, _("Didn't install any keys") if not key_installed: @@ -152876,7 +154398,7 @@ index 99039e0..3680356 100644 def _getAnyKeyForRepo(self, repo, destdir, keyurl_list, is_cakey=False, callback=None): """ -@@ -4788,6 +5500,18 @@ class YumBase(depsolve.Depsolve): +@@ -4788,6 +5858,18 @@ class YumBase(depsolve.Depsolve): """ key_installed = False @@ -152895,7 +154417,7 @@ index 99039e0..3680356 100644 user_cb_fail = False for keyurl in keyurl_list: keys = self._retrievePublicKey(keyurl, repo, getSig=not is_cakey) -@@ -4819,8 +5543,11 @@ class YumBase(depsolve.Depsolve): +@@ -4819,8 +5901,11 @@ class YumBase(depsolve.Depsolve): if not key_installed: self._getKeyImportMessage(info, keyurl, keytype) rc = False @@ -152908,7 +154430,7 @@ index 99039e0..3680356 100644 elif callback: rc = callback({"repo": repo, "userid": info['userid'], "hexkeyid": info['hexkeyid'], "keyurl": keyurl, -@@ -4835,7 +5562,8 @@ class YumBase(depsolve.Depsolve): +@@ -4835,7 +5920,8 @@ class YumBase(depsolve.Depsolve): # Import the key result = misc.import_key_to_pubring(info['raw_key'], info['hexkeyid'], gpgdir=destdir) if not result: @@ -152918,7 +154440,7 @@ index 99039e0..3680356 100644 self.logger.info(_('Key imported successfully')) key_installed = True # write out the key id to imported_cakeys in the repos basedir -@@ -4851,36 +5579,35 @@ class YumBase(depsolve.Depsolve): +@@ -4851,36 +5937,35 @@ class YumBase(depsolve.Depsolve): pass if not key_installed and user_cb_fail: @@ -152971,7 +154493,41 @@ index 99039e0..3680356 100644 self._getAnyKeyForRepo(repo, repo.gpgcadir, repo.gpgcakey, is_cakey=True, callback=callback) def _limit_installonly_pkgs(self): -@@ -4959,19 +5686,22 @@ class YumBase(depsolve.Depsolve): +@@ -4927,6 +6012,7 @@ class YumBase(depsolve.Depsolve): + ts = self.rpmdb.readOnlyTS() + (cur_kernel_v, cur_kernel_r) = misc.get_running_kernel_version_release(ts) + install_only_names = set(self.conf.installonlypkgs) ++ found = {} + for m in self.tsInfo.getMembers(): + if m.ts_state not in ('i', 'u'): + continue +@@ -4937,12 +6023,21 @@ class YumBase(depsolve.Depsolve): + if not po_names.intersection(install_only_names): + continue + +- installed = self.rpmdb.searchNevra(name=m.name) ++ if m.name not in found: ++ found[m.name] = 1 ++ else: ++ found[m.name] += 1 ++ ++ for name in found: ++ installed = self.rpmdb.searchNevra(name=name) + installed = _sort_and_filter_installonly(installed) +- if len(installed) < self.conf.installonly_limit - 1: +- continue # we're adding one + +- numleft = len(installed) - self.conf.installonly_limit + 1 ++ total = len(installed) + found[name] ++ if total <= self.conf.installonly_limit: ++ continue # Not adding enough to trigger. ++ ++ # Number left to try and remove... ++ numleft = total - self.conf.installonly_limit + for po in installed: + if (po.version, po.release) == (cur_kernel_v, cur_kernel_r): + # don't remove running +@@ -4959,19 +6054,22 @@ class YumBase(depsolve.Depsolve): txmbr.depends_on.append(rel) def processTransaction(self, callback=None,rpmTestDisplay=None, rpmDisplay=None): @@ -153007,7 +154563,7 @@ index 99039e0..3680356 100644 if not callback: callback = callbacks.ProcessTransNoOutputCallback() -@@ -5114,13 +5844,19 @@ class YumBase(depsolve.Depsolve): +@@ -5114,13 +6212,19 @@ class YumBase(depsolve.Depsolve): return results def add_enable_repo(self, repoid, baseurls=[], mirrorlist=None, **kwargs): @@ -153034,7 +154590,7 @@ index 99039e0..3680356 100644 # out of place fixme - maybe we should make this the default repo addition # routine and use it from getReposFromConfigFile(), etc. newrepo = yumRepo.YumRepository(repoid) -@@ -5167,9 +5903,15 @@ class YumBase(depsolve.Depsolve): +@@ -5167,9 +6271,15 @@ class YumBase(depsolve.Depsolve): def setCacheDir(self, force=False, tmpdir=None, reuse=True, suffix='/$basearch/$releasever'): @@ -153053,7 +154609,24 @@ index 99039e0..3680356 100644 if not force and os.geteuid() == 0: return True # We are root, not forced, so happy with the global dir. if tmpdir is None: -@@ -5220,13 +5962,24 @@ class YumBase(depsolve.Depsolve): +@@ -5179,7 +6289,7 @@ class YumBase(depsolve.Depsolve): + try: + cachedir = misc.getCacheDir(tmpdir, reuse) + except (IOError, OSError), e: +- self.logger.critical(_('Could not set cachedir: %s') % str(e)) ++ self.logger.critical(_('Could not set cachedir: %s') % exception2msg(e)) + cachedir = None + + if cachedir is None: +@@ -5190,6 +6300,7 @@ class YumBase(depsolve.Depsolve): + self.prerepoconf.cachedir = cachedir + else: + self.repos.setCacheDir(cachedir) ++ self._old_cachedir = self.conf.cachedir + self.conf.cachedir = cachedir + return True # We got a new cache dir + +@@ -5220,13 +6331,24 @@ class YumBase(depsolve.Depsolve): self.history.write_addon_data('config-repos', myrepos) def verify_plugins_cb(self, verify_package): @@ -153081,7 +154654,7 @@ index 99039e0..3680356 100644 if self.tsInfo._unresolvedMembers: if auto: self.logger.critical(_("Dependencies not solved. Will not save unresolved transaction.")) -@@ -5234,7 +5987,7 @@ class YumBase(depsolve.Depsolve): +@@ -5234,7 +6356,7 @@ class YumBase(depsolve.Depsolve): raise Errors.YumBaseError(_("Dependencies not solved. Will not save unresolved transaction.")) if not filename: @@ -153090,27 +154663,52 @@ index 99039e0..3680356 100644 fd,filename = tempfile.mkstemp(suffix='.yumtx', prefix=prefix) f = os.fdopen(fd, 'w') else: -@@ -5244,7 +5997,7 @@ class YumBase(depsolve.Depsolve): +@@ -5244,13 +6366,17 @@ class YumBase(depsolve.Depsolve): msg = "%s\n" % self.rpmdb.simpleVersion(main_only=True)[0] msg += "%s\n" % self.ts.getTsFlags() - if self.tsInfo.pkgSack is None: # rm Transactions don't have pkgSack +- msg += "0\n" ++ + if self.tsInfo._pkgSack is None: # Transactions have pkgSack? - msg += "0\n" ++ msg += "1\n" else: - msg += "%s\n" % len(self.repos.listEnabled()) -@@ -5266,7 +6019,17 @@ class YumBase(depsolve.Depsolve): +- msg += "%s\n" % len(self.repos.listEnabled()) ++ msg += "%s\n" % (len(self.repos.listEnabled()) + 1) + for r in self.repos.listEnabled(): + msg += "%s:%s:%s\n" % (r.id, len(r.sack), r.repoXML.revision) - - def load_ts(self, filename, ignorerpm=None, ignoremissing=None): ++ # Save what we think the future rpmdbv will be. ++ msg += "%s:%s\n" % ('installed', self.tsInfo.futureRpmDBVersion()) ++ + msg += "%s\n" % len(self.tsInfo.getMembers()) + for txmbr in self.tsInfo.getMembers(): + msg += txmbr._dump() +@@ -5260,13 +6386,25 @@ class YumBase(depsolve.Depsolve): + except (IOError, OSError), e: + self._ts_save_file = None + if auto: +- self.logger.critical(_("Could not save transaction file %s: %s") % (filename, str(e))) ++ self.logger.critical(_("Could not save transaction file %s: %s") % (filename, exception2msg(e))) + else: +- raise Errors.YumBaseError(_("Could not save transaction file %s: %s") % (filename, str(e))) +- +- +- def load_ts(self, filename, ignorerpm=None, ignoremissing=None): - """loads a transaction from a .yumtx file""" ++ raise Errors.YumBaseError(_("Could not save transaction file %s: %s") % (filename, exception2msg(e))) ++ ++ ++ def load_ts(self, filename, ignorerpm=None, ignoremissing=None, ++ ignorenewrpm=None): + """Load a transaction from a .yumtx file. + + :param filename: the name of the file to load the transaction + from -+ :param ignorerpm: whether to ignore messages from rpm ++ :param ignorerpm: whether to ignore starting rpmdb version mismatch. + :param ignoremissing: whether to ignore that there may be + transaction members missing ++ :param ignorenewrpm: whether to ignore ending rpmdb version mismatch. + :return: the members of the loaded transaction + :raises: :class:`yum.Errors.YumBaseError` if there are problems + loading the transaction @@ -153118,7 +154716,33 @@ index 99039e0..3680356 100644 # check rpmversion - if not match throw a fit # check repoversions (and repos)- if not match throw a fit # load each txmbr - if pkgs being updated don't exist, bail w/error -@@ -5292,6 +6055,16 @@ class YumBase(depsolve.Depsolve): +@@ -5276,26 +6414,45 @@ class YumBase(depsolve.Depsolve): + try: + data = open(filename, 'r').readlines() + except (IOError, OSError), e: +- raise Errors.YumBaseError(_("Could not access/read saved transaction %s : %s") % (filename, str(e))) ++ raise Errors.YumBaseError(_("Could not access/read saved transaction %s : %s") % (filename, exception2msg(e))) + + + if ignorerpm is None: + ignorerpm = self.conf.loadts_ignorerpm ++ if ignorenewrpm is None: ++ ignorenewrpm = self.conf.loadts_ignorenewrpm + if ignoremissing is None: + ignoremissing = self.conf.loadts_ignoremissing ++ ++ # Inherit this, because for the ending version to match the starting ++ # version must match. ++ if ignorerpm: ++ ignorenewrpm = True + + # data format + # 0 == rpmdb version + # 1 == tsflags + # 2 == numrepos + # 3:numrepos = repos ++ # -- post 3.2.29 update: 'installed' repo. added with the values as the ++ # new rpmdb version. # 3+numrepos = num pkgs # 3+numrepos+1 -> EOF= txmembers @@ -153135,7 +154759,31 @@ index 99039e0..3680356 100644 # rpm db ver rpmv = data[0].strip() if rpmv != str(self.rpmdb.simpleVersion(main_only=True)[0]): -@@ -5329,6 +6102,7 @@ class YumBase(depsolve.Depsolve): +- msg = _("rpmdb ver mismatched saved transaction version, ") ++ msg = _("rpmdb ver mismatched saved transaction version,") + if ignorerpm: + msg += _(" ignoring, as requested.") + self.logger.critical(_(msg)) +@@ -5318,8 +6475,17 @@ class YumBase(depsolve.Depsolve): + numrepos = int(data[2].strip()) + repos = [] + rindex=3+numrepos ++ future_rpmdbv = None + for r in data[3:rindex]: +- repos.append(r.strip().split(':')) ++ repo = r.strip().split(':') ++ ++ if repo and repo[0] == 'installed': ++ # This is an update hack to list the _future_ rpmdb version. ++ # Doing it this way allows older yum's to load newer ts files. ++ future_rpmdbv = "%s:%s" % (repo[1], repo[2]) ++ continue ++ ++ repos.append(repo) + + # pkgs/txmbrs + numpkgs = int(data[rindex].strip()) +@@ -5329,6 +6495,7 @@ class YumBase(depsolve.Depsolve): pkgcount = 0 pkgprob = False curpkg = None @@ -153143,6 +154791,82 @@ index 99039e0..3680356 100644 for l in data[pkgstart:]: l = l.rstrip() # our main txmbrs +@@ -5356,6 +6523,7 @@ class YumBase(depsolve.Depsolve): + if not ignoremissing: + raise Errors.YumBaseError(msg) + else: ++ ignorenewrpm = True + self.logger.critical(msg) + else: + pkgcount += 1 +@@ -5432,12 +6600,18 @@ class YumBase(depsolve.Depsolve): + if pkgprob: + msg = _("Transaction members, relations are missing or ts has been modified,") + if ignoremissing: ++ ignorenewrpm = True + msg += _(" ignoring, as requested. You must redepsolve!") + self.logger.critical(msg) + else: + msg += _(" aborting.") + raise Errors.YumBaseError(msg) + ++ if len(self.tsInfo) != pkgcount: ++ future_rpmdbv = None ++ if future_rpmdbv is not None: ++ self.tsInfo._check_future_rpmdbv = (pkgcount, future_rpmdbv, ++ ignorenewrpm) + return self.tsInfo.getMembers() + + def _remove_old_deps(self): +@@ -5470,18 +6644,6 @@ class YumBase(depsolve.Depsolve): + if requiring == required: # if they are self-requiring skip them + continue + +- # go through the stuff in the ts to be installed - make sure none of that needs the required pkg, either. +- for (provn,provf,provevr) in required.provides: +- if self.tsInfo.getNewRequires(provn, provf, provevr).keys(): +- still_needed = True +- okay_to_remove[required] = False +- break +- for fn in required.filelist + required.dirlist: +- if self.tsInfo.getNewRequires(fn, None,(None,None,None)).keys(): +- okay_to_remove[required] = False +- still_needed = True +- break +- + #for tbi_pkg in self.tsInfo.getMembersWithState(output_states=TS_INSTALL_STATES): + # for reqtuple in tbi_pkg.po.requires: + # if required.provides_for(reqtuple): +@@ -5533,7 +6695,24 @@ class YumBase(depsolve.Depsolve): + # Debugging output + self.verbose_logger.log(logginglevels.DEBUG_2, _("%s has revdep %s which was user-installed."), pkg, curpkg) + ok_to_remove[pkg] = False ++ ok_to_remove[curpkg] = False + return True ++ ++ # Go through the stuff in the ts to be installed - make sure ++ # none of that needs the required pkg, either. ++ for (provn,provf,provevr) in curpkg.provides: ++ if self.tsInfo.getNewRequires(provn, provf, provevr).keys(): ++ ok_to_remove[pkg] = False ++ ok_to_remove[curpkg] = False ++ self.verbose_logger.log(logginglevels.DEBUG_2, _("%s is needed by a package to be installed."), curpkg) ++ return True ++ for fn in curpkg.filelist + curpkg.dirlist: ++ if self.tsInfo.getNewRequires(fn, None,(None,None,None)).keys(): ++ ok_to_remove[pkg] = False ++ ok_to_remove[curpkg] = False ++ self.verbose_logger.log(logginglevels.DEBUG_2, _("%s is needed by a package to be installed."), curpkg) ++ return True ++ + visited[curpkg] = True + all_leaves_visited = True + leaves = curpkg.requiring_packages() +@@ -5547,4 +6726,3 @@ class YumBase(depsolve.Depsolve): + # Debugging output + self.verbose_logger.log(logginglevels.DEBUG_2, _("%s has no user-installed revdeps."), pkg) + return False +- diff --git a/yum/callbacks.py b/yum/callbacks.py index 7ad25ce..a9a8e53 100644 --- a/yum/callbacks.py @@ -153290,8 +155014,342 @@ index 7ad25ce..a9a8e53 100644 + """ pass +diff --git a/yum/comps.py b/yum/comps.py +index 65f6d5e..4e765ef 100755 +--- a/yum/comps.py ++++ b/yum/comps.py +@@ -43,6 +43,16 @@ class CompsObj(object): + return self.name + + @property ++ def compsid(self): ++ """ Return the "id": categoryid, groupid, environmentid. """ ++ ++ for idT in ('categoryid', 'groupid', 'environmentid'): ++ if hasattr(self, idT): ++ return getattr(self, idT) ++ ++ return None ++ ++ @property + def ui_name(self): + """ Return the "name" of the object for the current locale. """ + return self.nameByLang(get_my_lang_code()) +@@ -108,7 +118,7 @@ class Group(CompsObj): + self.optional_packages = {} + self.default_packages = {} + self.conditional_packages = {} +- self.langonly = None ## what the hell is this? ++ self.langonly = None + self.groupid = None + self.display_order = 1024 + self.installed = False +@@ -272,6 +282,137 @@ class Group(CompsObj): + return msg + + ++class Environment(CompsObj): ++ """ Environment object parsed from group data in each repo, and merged """ ++ ++ def __init__(self, elem=None): ++ self.name = "" ++ self.environmentid = None ++ self.description = "" ++ self.translated_name = {} ++ self.translated_description = {} ++ self.display_order = 1024 ++ self.langonly = None ++ self.installed = False ++ self._groups = {} ++ self._options = {} ++ ++ if elem: ++ self.parse(elem) ++ ++ def _allgroupiter(self): ++ lst = self._groups.keys() + \ ++ self._options.keys() ++ return lst ++ ++ allgroups = property(_allgroupiter) ++ ++ def _groupiter(self): ++ return self._groups.keys() ++ ++ groups = property(_groupiter) ++ ++ def _optioniter(self): ++ return self._options.keys() ++ ++ options = property(_optioniter) ++ ++ def parse(self, elem): ++ for child in elem: ++ if child.tag == 'id': ++ myid = child.text ++ if self.environmentid is not None: ++ raise CompsException ++ self.environmentid = myid ++ ++ elif child.tag == 'name': ++ text = child.text ++ if text: ++ text = text.encode('utf8') ++ ++ lang = child.attrib.get(lang_attr) ++ if lang: ++ self.translated_name[lang] = text ++ else: ++ self.name = text ++ ++ elif child.tag == 'description': ++ text = child.text ++ if text: ++ text = text.encode('utf8') ++ ++ lang = child.attrib.get(lang_attr) ++ if lang: ++ self.translated_description[lang] = text ++ else: ++ self.description = text ++ ++ elif child.tag == 'grouplist': ++ self.parse_group_list(child) ++ ++ elif child.tag == 'optionlist': ++ self.parse_option_list(child) ++ ++ elif child.tag == 'display_order': ++ self.display_order = parse_number(child.text) ++ ++ def parse_group_list(self, grouplist_elem): ++ for child in grouplist_elem: ++ if child.tag == 'groupid': ++ groupid = child.text ++ self._groups[groupid] = 1 ++ ++ def parse_option_list(self, optionlist_elem): ++ for child in optionlist_elem: ++ if child.tag == 'groupid': ++ optionid = child.text ++ self._options[optionid] = 1 ++ ++ def add(self, obj): ++ """Add another category object to this object""" ++ ++ for grp in obj.groups: ++ self._groups[grp] = 1 ++ ++ for grp in obj.options: ++ self._options[grp] = 1 ++ ++ # name and description translations ++ for lang in obj.translated_name: ++ if lang not in self.translated_name: ++ self.translated_name[lang] = obj.translated_name[lang] ++ ++ for lang in obj.translated_description: ++ if lang not in self.translated_description: ++ self.translated_description[lang] = obj.translated_description[lang] ++ ++ def xml(self): ++ """write out an xml stanza for the environment object""" ++ msg =""" ++ ++ %s ++ %s\n""" % (self.environmentid, self.display_order) ++ ++ msg +=""" %s\n""" % self.name ++ for (lang, val) in self.translated_name.items(): ++ msg += """ %s\n""" % (lang, val) ++ ++ msg += """ %s\n""" % self.description ++ for (lang, val) in self.translated_description.items(): ++ msg += """ %s\n""" % (lang, val) ++ ++ msg += """ \n""" ++ for grp in self.groups: ++ msg += """ %s\n""" % grp ++ msg += """ \n""" ++ msg += """ \n""" ++ for grp in self.options: ++ msg += """ %s\n""" % grp ++ msg += """ \n""" ++ msg += """ \n""" ++ ++ return msg ++ + class Category(CompsObj): + """ Category object parsed from group data in each repo. and merged. """ + +@@ -376,6 +517,7 @@ class Category(CompsObj): + class Comps(object): + def __init__(self, overwrite_groups=False): + self._groups = {} ++ self._environments = {} + self._categories = {} + self.compscount = 0 + self.overwrite_groups = overwrite_groups +@@ -388,12 +530,18 @@ class Comps(object): + grps.sort(key=lambda x: (x.display_order, x.name)) + return grps + ++ def get_environments(self): ++ environments = self._environments.values() ++ environments.sort(key=lambda x: (x.display_order, x.name)) ++ return environments ++ + def get_categories(self): + cats = self._categories.values() + cats.sort(key=lambda x: (x.display_order, x.name)) + return cats + + groups = property(get_groups) ++ environments = property(get_environments) + categories = property(get_categories) + + def has_group(self, grpid): +@@ -447,6 +595,57 @@ class Comps(object): + + return returns.values() + ++ def has_environment(self, environmentid): ++ exists = self.return_environments(environmentid) ++ ++ if exists: ++ return True ++ ++ return False ++ ++ def return_environment(self, environmentid): ++ """Return the first group which matches""" ++ environments = self.return_environments(environmentid) ++ if environments: ++ return environments[0] ++ ++ return None ++ ++ def return_environments(self, env_pattern, case_sensitive=False): ++ """return all environments which match either by glob or exact match""" ++ returns = {} ++ ++ for item in env_pattern.split(','): ++ item = item.strip() ++ if item in self._environments: ++ env = self._environments[item] ++ returns[env.environmentid] = env ++ 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 env in self.environments: ++ for name in env.name, env.environmentid, env.ui_name: ++ if match(name): ++ done = True ++ returns[env.environmentid] = env ++ break ++ if done: ++ continue ++ ++ # If we didn't match to anything in the current locale, try others ++ for env in self.environments: ++ for name in env.translated_name.values(): ++ if match(name): ++ returns[env.environmentid] = env ++ break ++ ++ return returns.values() ++ + # This is close to returnPackages() etc. API ... need to std. these names + # the above return_groups uses different, but equal, API. + def return_categories(self, pattern, ignore_case=True): +@@ -490,6 +689,13 @@ class Comps(object): + else: + self._groups[group.groupid] = group + ++ def add_environment(self, environment): ++ if environment.environmentid in self._environments: ++ env = self._environments[environment.environmentid] ++ env.add(environment) ++ else: ++ self._environments[environment.environmentid] = environment ++ + def add_category(self, category): + if category.categoryid in self._categories: + thatcat = self._categories[category.categoryid] +@@ -520,6 +726,9 @@ class Comps(object): + if elem.tag == "group": + group = Group(elem) + self.add_group(group) ++ if elem.tag == "environment": ++ environment = Environment(elem) ++ self.add_environment(environment) + if elem.tag == "category": + category = Category(elem) + self.add_category(category) +@@ -557,13 +766,32 @@ class Comps(object): + if pkgname in inst_pkg_names: + group.installed = True + break ++ ++ # Now do basically the same thing for evgroups. ++ inst_grp_names = {} ++ for group in self.groups: ++ inst_grp_names[group.groupid] = group.installed ++ for evgroup in self.environments: ++ if evgroup.groups: ++ evgroup.installed = True ++ for grpname in evgroup.groups: ++ if not inst_grp_names.get(grpname): ++ evgroup.installed = False ++ break ++ else: ++ evgroup.installed = False ++ for grpname in evgroup.optional: ++ if grpname in inst_grp_names: ++ evgroup.installed = True ++ break + + self.compiled = True + + def xml(self): + """returns the xml of the comps files in this class, merged""" + +- if not self._groups and not self._categories: ++ if not self._groups and not self._categories and \ ++ not self._environments: + return "" + + msg = """ +@@ -575,6 +803,8 @@ class Comps(object): + msg += g.xml() + for c in self.get_categories(): + msg += c.xml() ++ for e in self.get_environments(): ++ msg += e.xml() + + msg += """\n\n""" + +@@ -595,6 +825,13 @@ def main(): + for pkg in group.packages: + print ' ' + pkg + ++ for environment in p.environments: ++ print environment.name ++ for group in environment.groups: ++ print ' ' + group ++ for group in environment.options: ++ print ' *' + group ++ + for category in p.categories: + print category.name + for group in category.groups: diff --git a/yum/config.py b/yum/config.py -index d09511f..fe7e1cb 100644 +index d09511f..74be397 100644 --- a/yum/config.py +++ b/yum/config.py @@ -45,15 +45,18 @@ from misc import get_uuid, read_in_items_from_dot_dir @@ -153562,12 +155620,12 @@ index d09511f..fe7e1cb 100644 class PositiveIntOption(IntOption): - +- """ +- An option representing a positive integer value, where 0 can have a special +- represention. + """An option representing a positive integer value, where 0 can + have a special representation. """ -- An option representing a positive integer value, where 0 can have a special -- represention. -- """ - def __init__(self, default=None, range_min=0, range_max=None, names_of_0=None): @@ -153758,11 +155816,11 @@ index d09511f..fe7e1cb 100644 - Invalid inputs: -10, -0.1, 45.6L, 123Mb - - Return value will always be an integer +- +- 1k = 1024 bytes. + Valid inputs: 100, 123M, 45.6k, 12.4G, 100K, 786.3, 0. + Invalid inputs: -10, -0.1, 45.6L, 123Mb. -- 1k = 1024 bytes. -- - ValueError will be raised if the option couldn't be parsed. + :param s: the string to parse + :return: the number of bytes represented by *s* @@ -153991,8 +156049,9 @@ index d09511f..fe7e1cb 100644 commands = ListOption() exclude = ListOption() - failovermethod = Option('roundrobin') +- proxy = UrlOption(schemes=('http', 'ftp', 'https'), allow_none=True) + failovermethod = Option(__main_failovermethod_default__) - proxy = UrlOption(schemes=('http', 'ftp', 'https'), allow_none=True) ++ proxy = UrlOption(default=False, schemes=('http', 'ftp', 'https'), allow_none=True) proxy_username = Option() proxy_password = Option() username = Option() @@ -154020,7 +156079,7 @@ index d09511f..fe7e1cb 100644 alwaysprompt = BoolOption(True) exactarch = BoolOption(True) tolerant = BoolOption(True) -@@ -681,11 +778,16 @@ class YumConf(StartupConf): +@@ -681,11 +778,17 @@ class YumConf(StartupConf): enable_group_conditionals = BoolOption(True) groupremove_leaf_only = BoolOption(False) group_package_types = ListOption(['mandatory', 'default']) @@ -154034,10 +156093,11 @@ index d09511f..fe7e1cb 100644 + ip_resolve = CaselessSelectionOption( + allowed = ('ipv4', 'ipv6', 'whatever'), + mapper = {'4': 'ipv4', '6': 'ipv6'}) ++ max_connections = IntOption(0) http_caching = SelectionOption('all', ('none', 'packages', 'all')) metadata_expire = SecondsOption(60 * 60 * 6) # Time in seconds (6h). -@@ -698,12 +800,14 @@ class YumConf(StartupConf): +@@ -698,12 +801,14 @@ class YumConf(StartupConf): # Note that "instant" is the old behaviour, but group:primary is very # similar but better :). mdpolicy = ListOption(['group:primary']) @@ -154054,7 +156114,7 @@ index d09511f..fe7e1cb 100644 color = SelectionOption('auto', ('auto', 'never', 'always'), mapper={'on' : 'always', 'yes' : 'always', -@@ -715,11 +819,13 @@ class YumConf(StartupConf): +@@ -715,11 +820,13 @@ class YumConf(StartupConf): color_list_installed_newer = Option('bold,yellow') color_list_installed_reinstall = Option('normal') color_list_installed_extra = Option('bold,red') @@ -154068,7 +156128,7 @@ index d09511f..fe7e1cb 100644 color_update_installed = Option('normal') color_update_local = Option('bold') -@@ -731,6 +837,7 @@ class YumConf(StartupConf): +@@ -731,6 +838,7 @@ class YumConf(StartupConf): sslverify = BoolOption(True) sslclientcert = Option() sslclientkey = Option() @@ -154076,7 +156136,11 @@ index d09511f..fe7e1cb 100644 history_record = BoolOption(True) history_record_packages = ListOption(['yum', 'rpm']) -@@ -747,15 +854,25 @@ class YumConf(StartupConf): +@@ -744,18 +852,29 @@ class YumConf(StartupConf): + + loadts_ignoremissing = BoolOption(False) + loadts_ignorerpm = BoolOption(False) ++ loadts_ignorenewrpm = BoolOption(False) clean_requirements_on_remove = BoolOption(False) @@ -154102,7 +156166,7 @@ index d09511f..fe7e1cb 100644 output = '[main]\n' # we exclude all vars which start with _ or are in this list: excluded_vars = ('cfg', 'uid', 'yumvar', 'progress_obj', 'failure_obj', -@@ -778,14 +895,12 @@ class YumConf(StartupConf): +@@ -778,14 +897,12 @@ class YumConf(StartupConf): return output class RepoConf(BaseConfig): @@ -154120,7 +156184,7 @@ index d09511f..fe7e1cb 100644 ck = self.__cached_keys if not isinstance(self, RepoConf): ck = set() -@@ -823,39 +938,43 @@ class RepoConf(BaseConfig): +@@ -823,39 +940,44 @@ class RepoConf(BaseConfig): bandwidth = Inherit(YumConf.bandwidth) throttle = Inherit(YumConf.throttle) timeout = Inherit(YumConf.timeout) @@ -154142,6 +156206,7 @@ index d09511f..fe7e1cb 100644 + ssl_check_cert_permissions = Inherit(YumConf.ssl_check_cert_permissions) skip_if_unavailable = BoolOption(False) ++ async = BoolOption(True) class VersionGroupConf(BaseConfig): + """Option definitions for version groups.""" @@ -154175,7 +156240,7 @@ index d09511f..fe7e1cb 100644 # ' xemacs syntax hack -@@ -876,20 +995,24 @@ def readStartupConfig(configfile, root): +@@ -876,20 +998,24 @@ def readStartupConfig(configfile, root): raise Errors.ConfigError("All plugin search paths must be absolute") # Stuff this here to avoid later re-parsing startupconf._parser = parser @@ -154206,7 +156271,7 @@ index d09511f..fe7e1cb 100644 # ' xemacs syntax hack -@@ -956,6 +1079,12 @@ def readMainConfig(startupconf): +@@ -956,6 +1082,12 @@ def readMainConfig(startupconf): return yumconf def readVersionGroupsConfig(configfile="/etc/yum/version-groups.conf"): @@ -154219,7 +156284,7 @@ index d09511f..fe7e1cb 100644 parser = ConfigParser() confpp_obj = ConfigPreProcessor(configfile) try: -@@ -970,17 +1099,16 @@ def readVersionGroupsConfig(configfile="/etc/yum/version-groups.conf"): +@@ -970,17 +1102,16 @@ def readVersionGroupsConfig(configfile="/etc/yum/version-groups.conf"): def getOption(conf, section, name, option): @@ -154247,7 +156312,7 @@ index d09511f..fe7e1cb 100644 try: val = conf.get(section, name) except (NoSectionError, NoOptionError): -@@ -1028,7 +1156,10 @@ def _getsysver(installroot, distroverpkg): +@@ -1028,25 +1159,28 @@ def _getsysver(installroot, distroverpkg): if idx.count() == 0: releasever = '$releasever' else: @@ -154259,26 +156324,55 @@ index d09511f..fe7e1cb 100644 releasever = hdr['version'] del hdr del idx -@@ -1036,13 +1167,12 @@ def _getsysver(installroot, distroverpkg): + del ts return releasever - def writeRawRepoFile(repo,only=None): +-def writeRawRepoFile(repo,only=None): - """ - Writes changes in a repo object back to a .repo file. - @param repo: Repo Object - @param only: List of attributes to work on (None = All) - It work by reading the repo file, changes the values there shall be changed and write it back to disk. - """ -+ """Write changes in a repo object back to a .repo file. +- ++def _readRawRepoFile(repo): + if not _use_iniparse: +- return ++ return None ++ ++ if not hasattr(repo, 'repofile') or not repo.repofile: ++ return None ++ ++ try: ++ ini = INIConfig(open(repo.repofile)) ++ except: ++ return None +- ini = INIConfig(open(repo.repofile)) + # b/c repoids can have $values in them we need to map both ways to figure + # out which one is which + section_id = repo.id +@@ -1054,6 +1188,19 @@ def writeRawRepoFile(repo,only=None): + for sect in ini._sections.keys(): + if varReplace(sect, repo.yumvar) == repo.id: + section_id = sect ++ return ini, section_id ++ ++def writeRawRepoFile(repo,only=None): ++ """Write changes in a repo object back to a .repo file. ++ + :param repo: the Repo Object to write back out + :param only: list of attributes to work on. If *only* is None, all + options will be written out + """ - if not _use_iniparse: - return - -@@ -1069,7 +1199,7 @@ def writeRawRepoFile(repo,only=None): ++ if not _use_iniparse: ++ return ++ ++ ini, section_id = _readRawRepoFile(repo) + + # Updated the ConfigParser with the changed values + cfgOptions = repo.cfg.options(repo.id) +@@ -1069,7 +1216,7 @@ def writeRawRepoFile(repo,only=None): # If the value is the same, but just interpreted ... when we don't want # to keep the interpreted values. if (name in ini[section_id] and @@ -154288,7 +156382,7 @@ index d09511f..fe7e1cb 100644 if name not in cfgOptions and option.default == value: diff --git a/yum/depsolve.py b/yum/depsolve.py -index 6d744c0..449cf48 100644 +index 6d744c0..5e95662 100644 --- a/yum/depsolve.py +++ b/yum/depsolve.py @@ -31,13 +31,15 @@ from transactioninfo import TransactionMember @@ -154497,7 +156591,40 @@ index 6d744c0..449cf48 100644 msg = self._err_missing_requires(requiringPo, requirement) self.verbose_logger.log(logginglevels.DEBUG_2, _('No update paths found for %s. Failure!'), requiringPo) return self._requiringFromTransaction(requiringPo, requirement, errorlist) -@@ -696,6 +776,13 @@ class Depsolve(object): +@@ -405,12 +485,30 @@ class Depsolve(object): + # try to update the requiring package in hopes that all this problem goes away :( + self.verbose_logger.log(logginglevels.DEBUG_2, _('Trying to update %s to resolve dep'), requiringPo) + txmbrs = self.update(po=requiringPo, requiringPo=requiringPo) ++ fixed = False + if not txmbrs: + msg = self._err_missing_requires(requiringPo, requirement) +- self.verbose_logger.log(logginglevels.DEBUG_2, _('No update paths found for %s. Failure!'), requiringPo) ++ self.verbose_logger.log(logginglevels.DEBUG_2, _('No update paths found for %s. Failure due to requirement: %s!'), requiringPo, msg) ++ else: ++ req_lookup = (needname, needflags, ++ rpmUtils.miscutils.stringToVersion(needversion)) ++ ++ for txmbr in txmbrs: ++ # This works for upgrades, and for obsoletes, on one side. ++ # Also catches upgrades that don't work on the other. ++ if txmbr.output_state in TS_INSTALL_STATES: ++ if txmbr.po.checkPrco('requires', req_lookup): ++ fixed = False ++ break ++ else: ++ fixed = True ++ if txmbrs and not fixed: ++ msg = self._err_missing_requires(requiringPo, requirement) ++ self.verbose_logger.log(logginglevels.DEBUG_2, _("Update for %s. Doesn't fix requirement: %s!"), requiringPo, msg) ++ ++ if not fixed: + return self._requiringFromTransaction(requiringPo, requirement, errorlist) + checkdeps = 1 +- + + if needmode in ['e']: + self.verbose_logger.log(logginglevels.DEBUG_2, _('TSINFO: %s package requiring %s marked as erase'), +@@ -696,6 +794,13 @@ class Depsolve(object): self.tsInfo.remove(txmbr.pkgtup) def prof_resolveDeps(self): @@ -154511,7 +156638,7 @@ index 6d744c0..449cf48 100644 fn = "anaconda.prof.0" import hotshot, hotshot.stats prof = hotshot.Profile(fn) -@@ -709,6 +796,13 @@ class Depsolve(object): +@@ -709,6 +814,13 @@ class Depsolve(object): return rc def cprof_resolveDeps(self): @@ -154525,7 +156652,7 @@ index 6d744c0..449cf48 100644 import cProfile, pstats prof = cProfile.Profile() rc = prof.runcall(self.resolveDeps) -@@ -722,7 +816,17 @@ class Depsolve(object): +@@ -722,7 +834,17 @@ class Depsolve(object): return rc def resolveDeps(self, full_check=True, skipping_broken=False): @@ -154544,7 +156671,7 @@ index 6d744c0..449cf48 100644 if not len(self.tsInfo): return (0, [_('Success - empty transaction')]) -@@ -778,6 +882,25 @@ class Depsolve(object): +@@ -778,6 +900,25 @@ class Depsolve(object): if checkdep: break # The next conflict might be the same pkg @@ -154570,7 +156697,7 @@ index 6d744c0..449cf48 100644 if CheckDeps: if self.dsCallback: self.dsCallback.restartLoop() self.verbose_logger.log(logginglevels.DEBUG_1, _('Restarting Loop')) -@@ -920,9 +1043,12 @@ class Depsolve(object): +@@ -920,9 +1061,12 @@ class Depsolve(object): # if this is an update, we should check what the old # requires were to make things faster @@ -154585,7 +156712,7 @@ index 6d744c0..449cf48 100644 oldreqs = set(oldreqs) ret = [] -@@ -1150,6 +1276,11 @@ class Depsolve(object): +@@ -1150,6 +1294,11 @@ class Depsolve(object): return ret def isPackageInstalled(self, pkgname): @@ -154597,7 +156724,7 @@ index 6d744c0..449cf48 100644 lst = self.tsInfo.matchNaevr(name = pkgname) for txmbr in lst: if txmbr.output_state in TS_INSTALL_STATES: -@@ -1166,7 +1297,7 @@ class Depsolve(object): +@@ -1166,7 +1315,7 @@ class Depsolve(object): return True _isPackageInstalled = isPackageInstalled @@ -154606,7 +156733,7 @@ index 6d744c0..449cf48 100644 """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, -@@ -1210,6 +1341,24 @@ class Depsolve(object): +@@ -1210,6 +1359,24 @@ class Depsolve(object): return None return x @@ -154631,7 +156758,7 @@ index 6d744c0..449cf48 100644 # Actual start of _compare_providers(). # Do a NameArch filtering, based on repo. __cmp__ -@@ -1332,6 +1481,26 @@ class Depsolve(object): +@@ -1332,6 +1499,26 @@ class Depsolve(object): _('common prefix of %s between %s and %s' % (cpl, po, reqpo))) pkgresults[po] += cpl*2 @@ -154658,7 +156785,7 @@ index 6d744c0..449cf48 100644 # 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 -@@ -1393,42 +1562,52 @@ class Depsolve(object): +@@ -1393,42 +1580,52 @@ class Depsolve(object): class DepCheck(object): @@ -154701,12 +156828,12 @@ index 6d744c0..449cf48 100644 class Requires(object): - -- """ -- A pure data class for holding a package and the list of things it -- requires. + """A pure data class for holding a package and the list of things + it requires. """ +- A pure data class for holding a package and the list of things it +- requires. +- """ - def __init__(self, pkg,requires): self.pkg = pkg # po of requiring pkg @@ -155380,10 +157507,10 @@ index 9889bf6..76a258d 100755 Setup the yum translation domain and make _() and P_() translation wrappers diff --git a/yum/igroups.py b/yum/igroups.py new file mode 100644 -index 0000000..625ee66 +index 0000000..6a04f3a --- /dev/null +++ b/yum/igroups.py -@@ -0,0 +1,141 @@ +@@ -0,0 +1,267 @@ +#! /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 @@ -155399,7 +157526,7 @@ index 0000000..625ee66 +# 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 ++# Copyright 2010, 2012 Red Hat +# +# James Antill + @@ -155411,6 +157538,7 @@ index 0000000..625ee66 + def __init__(self, gid): + self.gid = gid + self.pkg_names = set() ++ self.environment = None + + def __cmp__(self, other): + if other is None: @@ -155426,12 +157554,36 @@ index 0000000..625ee66 + return sorted(pkg_names.difference(self.pkg_names)) + + ++class InstalledEnvironment(object): ++ def __init__(self, evgid): ++ self.evgid = evgid ++ self.grp_names = set() ++ ++ def __cmp__(self, other): ++ if other is None: ++ return 1 ++ return cmp(self.evgid, other.evgid) ++ ++ def _additions(self, grp_names): ++ grp_names = set(grp_names) ++ return sorted(grp_names.difference(self.grp_names)) ++ ++ def _removals(self, grp_names): ++ grp_names = set(grp_names) ++ return sorted(grp_names.difference(self.grp_names)) ++ ++ +class InstalledGroups(object): + def __init__(self, db_path): -+ self.filename = db_path + "/installed" + self.groups = {} + self.changed = False ++ self.environments = {} + ++ self._read_pkg_grps(db_path) ++ self._read_grp_grps(db_path) ++ ++ def _read_pkg_grps(self, db_path): ++ self.filename = db_path + "/installed" + if not os.access(self.filename, os.R_OK): + return + @@ -155455,6 +157607,38 @@ index 0000000..625ee66 + num -= 1 + grp.pkg_names.add(_read_str(fo)) + ++ def _read_grp_grps(self, db_path): ++ self.grp_filename = db_path + "/environment" ++ if not os.access(self.grp_filename, os.R_OK): ++ return ++ ++ def _read_str(fo): ++ return fo.readline()[:-1] ++ ++ fo = open(self.grp_filename) ++ ver = int(_read_str(fo)) ++ if ver != 1: ++ return ++ ++ groups_num = int(_read_str(fo)) ++ while groups_num > 0: ++ groups_num -= 1 ++ ++ evgrp = InstalledEnvironment(_read_str(fo)) ++ self.environments[evgrp.evgid] = evgrp ++ ++ num = int(_read_str(fo)) ++ while num > 0: ++ num -= 1 ++ grpname = _read_str(fo) ++ memb = _read_str(fo) ++ evgrp.grp_names.add(grpname) ++ assert memb in ('true', 'false') ++ if memb == 'true': ++ assert grpname in self.groups ++ if grpname in self.groups: ++ self.groups[grpname].environment = evgrp.evgid ++ + def close(self): + pass + @@ -155473,6 +157657,12 @@ index 0000000..625ee66 + if not os.access(db_path, os.W_OK): + return False + ++ self._write_pkg_grps() ++ self._write_grp_grps() ++ ++ self.changed = False ++ ++ def _write_pkg_grps(self): + fo = open(self.filename + '.tmp', 'w') + + fo.write("1\n") # version @@ -155484,9 +157674,26 @@ index 0000000..625ee66 + fo.write("%s\n" % pkgname) + fo.close() + os.rename(self.filename + '.tmp', self.filename) -+ self.changed = False + -+ def add_group(self, groupid, pkg_names): ++ def _write_grp_grps(self): ++ fo = open(self.grp_filename + '.tmp', 'w') ++ ++ fo.write("1\n") # version ++ fo.write("%u\n" % len(self.environments)) ++ for evgrp in sorted(self.environments.values()): ++ fo.write("%s\n" % evgrp.evgid) ++ fo.write("%u\n" % len(evgrp.grp_names)) ++ for grpname in sorted(evgrp.grp_names): ++ fo.write("%s\n" % grpname) ++ if self.groups[grpname].environment == evgrp.evgid: ++ fo.write("%s\n" % "true") ++ else: ++ fo.write("%s\n" % "false") ++ ++ fo.close() ++ os.rename(self.grp_filename + '.tmp', self.grp_filename) ++ ++ def add_group(self, groupid, pkg_names, ievgrp=None): + self.changed = True + + if groupid not in self.groups: @@ -155496,6 +157703,11 @@ index 0000000..625ee66 + for pkg_name in pkg_names: + grp.pkg_names.add(pkg_name) + ++ if ievgrp is not None: ++ grp.environment = ievgrp.evgid ++ ievgrp.grp_names.add(groupid) ++ return grp ++ + def del_group(self, groupid): + self.changed = True + @@ -155525,8 +157737,82 @@ index 0000000..625ee66 + break + + return returns.values() ++ ++ def add_environment(self, evgroupid, grp_names): ++ self.changed = True ++ ++ if evgroupid not in self.environments: ++ self.environments[evgroupid] = InstalledEnvironment(evgroupid) ++ grp = self.environments[evgroupid] ++ ++ for grp_name in grp_names: ++ grp.grp_names.add(grp_name) ++ return grp ++ ++ def del_environment(self, evgroupid): ++ self.changed = True ++ ++ if evgroupid in self.environments: ++ del self.environments[evgroupid] ++ ++ def return_environments(self, evgroup_pattern, case_sensitive=False): ++ returns = {} ++ ++ for item in evgroup_pattern.split(','): ++ item = item.strip() ++ if item in self.environments: ++ thisgroup = self.environments[item] ++ returns[thisgroup.evgid] = 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.environments.values(): ++ if match(group.evgid): ++ done = True ++ returns[group.evgid] = group ++ break ++ ++ return returns.values() +diff --git a/yum/metalink.py b/yum/metalink.py +index aaa4f25..61aa586 100755 +--- a/yum/metalink.py ++++ b/yum/metalink.py +@@ -180,6 +180,7 @@ class MetaLinkRepoMD: + self.repomd = None + self.old_repomds = [] + self.mirrors = [] ++ self._host2mc = {} + if not os.path.exists(filename): + raise MetaLinkRepoErrorParseFail, "File %s does not exist" %filename + try: +@@ -225,8 +226,6 @@ class MetaLinkRepoMD: + # Get the hostname from a url, stripping away any usernames/passwords + # Borrowd from fastestmirror + url2host = lambda url: url.split('/')[2].split('@')[-1] +- hosts = set() # Don't want multiple urls for one host in plain mode +- # The list of URLs is sorted, so http is before ftp + + for mirror in self.mirrors: + url = mirror.url +@@ -237,9 +236,9 @@ class MetaLinkRepoMD: + elif (url.startswith("http:") or url.startswith("ftp:") or + url.startswith("https:")): + host = url2host(url) +- if host in hosts: ++ if host in self._host2mc: + continue +- hosts.add(host) ++ self._host2mc[host] = mirror + else: + continue + diff --git a/yum/misc.py b/yum/misc.py -index 2f6ddfe..5321003 100644 +index 2f6ddfe..a0bac7b 100644 --- a/yum/misc.py +++ b/yum/misc.py @@ -8,6 +8,7 @@ import os @@ -155537,7 +157823,35 @@ index 2f6ddfe..5321003 100644 import struct import re import errno -@@ -410,6 +411,17 @@ def procgpgkey(rawkey): +@@ -21,6 +22,7 @@ import fnmatch + import bz2 + import gzip + import shutil ++import urllib + _available_compression = ['gz', 'bz2'] + try: + import lzma +@@ -95,6 +97,19 @@ def re_glob(s): + _re_compiled_glob_match = re.compile('[*?]|\[.+\]').search + return _re_compiled_glob_match(s) + ++def compile_pattern(pat, ignore_case=False): ++ """ Compile shell wildcards, return a 'match' function. """ ++ if re_glob(pat): ++ try: ++ flags = ignore_case and re.I or 0 ++ return re.compile(fnmatch.translate(pat), flags).match ++ except re.error: ++ pass # fall back to exact match ++ if ignore_case: ++ pat = pat.lower() ++ return lambda s: s.lower() == pat ++ return lambda s: s == pat ++ + _re_compiled_filename_match = None + def re_filename(s): + """ Tests if a string could be a filename. We still get negated character +@@ -410,6 +425,17 @@ def procgpgkey(rawkey): # Decode and return return base64.decodestring(block.getvalue()) @@ -155555,7 +157869,28 @@ index 2f6ddfe..5321003 100644 def getgpgkeyinfo(rawkey, multiple=False): '''Return a dict of info for the given ASCII armoured key text -@@ -940,14 +952,16 @@ def unlink_f(filename): +@@ -611,6 +637,8 @@ def getCacheDir(tmpdir='/var/tmp', reuse=True, prefix='yum-'): + try: + usertup = pwd.getpwuid(uid) + username = usertup[0] ++ # we prefer ascii-only paths ++ username = urllib.quote(username) + except KeyError: + return None # if it returns None then, well, it's bollocksed + +@@ -744,8 +772,9 @@ def _decompress_chunked(source, dest, ztype): + while True: + try: + data = s_fn.read(1024000) +- except IOError: +- break ++ except (OSError, IOError, EOFError), e: ++ msg = "Error reading from file %s: %s" % (source, str(e)) ++ raise Errors.MiscError, msg + + if not data: break + +@@ -940,14 +969,16 @@ def unlink_f(filename): if e.errno != errno.ENOENT: raise @@ -155576,25 +157911,113 @@ index 2f6ddfe..5321003 100644 def _getloginuid(): """ Get the audit-uid/login-uid, if available. None is returned if there -@@ -1112,10 +1126,12 @@ def decompress(filename, dest=None, fn_only=False, check_timestamps=False): - if check_timestamps: - fi = stat_f(filename) - fo = stat_f(out) -- if fi and fo and fo.st_mtime > fi.st_mtime: -+ if fi and fo and fo.st_mtime == fi.st_mtime: - return out +@@ -992,9 +1023,17 @@ def setup_locale(override_codecs=True, override_time=False): + locale.setlocale(locale.LC_ALL, 'C') + + if override_codecs: +- import codecs +- sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout) +- sys.stdout.errors = 'replace' ++ class UnicodeStream: ++ def __init__(self, stream, encoding): ++ self.stream = stream ++ self.encoding = encoding ++ def write(self, s): ++ if isinstance(s, unicode): ++ s = s.encode(self.encoding, 'replace') ++ self.stream.write(s) ++ def __getattr__(self, name): ++ return getattr(self.stream, name) ++ sys.stdout = UnicodeStream(sys.stdout, locale.getpreferredencoding()) - _decompress_chunked(filename, out, ztype) -+ if check_timestamps and fi: -+ os.utime(out, (fi.st_mtime, fi.st_mtime)) + + def get_my_lang_code(): +@@ -1105,17 +1144,28 @@ def decompress(filename, dest=None, fn_only=False, check_timestamps=False): + out = filename.replace('.xz', '') + + else: +- out = filename # returning the same file since it is not compressed +- ztype = None ++ return filename # returning the same file since it is not compressed + +- if ztype and not fn_only: +- if check_timestamps: +- fi = stat_f(filename) +- fo = stat_f(out) +- if fi and fo and fo.st_mtime > fi.st_mtime: ++ if check_timestamps: ++ fi = stat_f(filename) ++ fo = stat_f(out) ++ if fi and fo: ++ # Eliminate sub second precision in mtime before comparision, ++ # see http://bugs.python.org/issue14127 ++ if int(fo.st_mtime) == int(fi.st_mtime): + return out ++ if fn_only: ++ # out exists but not valid ++ return None + +- _decompress_chunked(filename, out, ztype) ++ if not fn_only: ++ try: ++ _decompress_chunked(filename, out, ztype) ++ if check_timestamps and fi: ++ os.utime(out, (fi.st_mtime, fi.st_mtime)) ++ except: ++ unlink_f(out) ++ raise return out +@@ -1123,11 +1173,7 @@ def repo_gen_decompress(filename, generated_name, cached=False): + """ This is a wrapper around decompress, where we work out a cached + generated name, and use check_timestamps. filename _must_ be from + a repo. and generated_name is the type of the file. """ +- dest = os.path.dirname(filename) +- dest += '/gen' +- if not os.path.exists(dest): +- os.makedirs(dest, mode=0755) +- dest += '/' + generated_name ++ dest = os.path.dirname(filename) + '/gen/' + generated_name + return decompress(filename, dest=dest, check_timestamps=True,fn_only=cached) + + def read_in_items_from_dot_dir(thisglob, line_as_list=True): diff --git a/yum/packageSack.py b/yum/packageSack.py -index 4af563a..47832fc 100644 +index 4af563a..a702ac1 100644 --- a/yum/packageSack.py +++ b/yum/packageSack.py -@@ -445,6 +445,7 @@ class MetaSack(PackageSackBase): +@@ -282,11 +282,7 @@ class PackageSackBase(object): + + specs = {} + for p in pkgspecs: +- if misc.re_glob(p): +- restring = fnmatch.translate(p) +- specs[p] = re.compile(restring) +- else: +- specs[p] = p ++ specs[p] = misc.compile_pattern(p) + + # We don't use simplePkgList() here because that loads all of the + # rpmdb, if we are Eg. doing a "remove PackageKit". +@@ -304,13 +300,12 @@ class PackageSackBase(object): + )) + + for (term,query) in specs.items(): +- if term == query: +- if query in names: +- exactmatch.append(self.searchPkgTuple(pkgtup)[0]) +- unmatched.discard(term) ++ if term in names: ++ exactmatch.append(self.searchPkgTuple(pkgtup)[0]) ++ unmatched.discard(term) + else: + for n in names: +- if query.match(n): ++ if query(n): + matched.append(self.searchPkgTuple(pkgtup)[0]) + unmatched.discard(term) + return misc.unique(exactmatch), misc.unique(matched), list(unmatched) +@@ -445,6 +440,7 @@ class MetaSack(PackageSackBase): nobsdict = {} last_name = '' last_pkg = None @@ -155602,7 +158025,7 @@ index 4af563a..47832fc 100644 # It takes about 0.2 of a second to convert these into packages, just # so we can sort them, which is ~40% of this functions time. So we sort # the pkgtups "by hand". -@@ -624,6 +625,8 @@ class MetaSack(PackageSackBase): +@@ -624,6 +620,8 @@ class MetaSack(PackageSackBase): class PackageSack(PackageSackBase): """represents sets (sacks) of Package Objects""" def __init__(self): @@ -155612,10 +158035,54 @@ index 4af563a..47832fc 100644 self.obsoletes = {} #obs[obsoletename] = [pkg1, pkg2, pkg3] #the package lists are packages that obsolete the key name diff --git a/yum/packages.py b/yum/packages.py -index 5ef9951..da31971 100644 +index 5ef9951..7a2624a 100644 --- a/yum/packages.py +++ b/yum/packages.py -@@ -243,34 +243,87 @@ class PackageObject(object): +@@ -106,14 +106,16 @@ def buildPkgRefDict(pkgs, casematch=True): + return pkgdict + + def parsePackages(pkgs, usercommands, casematch=0, +- unique='repo-epoch-name-version-release-arch'): ++ unique='repo-epoch-name-version-release-arch', ++ pkgdict=None): + """matches up the user request versus a pkg list: + for installs/updates available pkgs should be the 'others list' + for removes it should be the installed list of pkgs + takes an optional casematch option to determine if case should be matched + exactly. Defaults to not matching.""" + +- pkgdict = buildPkgRefDict(pkgs, bool(casematch)) ++ if pkgdict is None: ++ pkgdict = buildPkgRefDict(pkgs, bool(casematch)) + exactmatch = [] + matched = [] + unmatched = [] +@@ -130,11 +132,10 @@ def parsePackages(pkgs, usercommands, casematch=0, + trylist = pkgdict.keys() + # command and pkgdict are already lowered if not casematch + # so case sensitive is always fine +- restring = fnmatch.translate(command) +- regex = re.compile(restring) ++ regex = misc.compile_pattern(command) + foundit = 0 + for item in trylist: +- if regex.match(item): ++ if regex(item): + matched.extend(pkgdict[item]) + del pkgdict[item] + foundit = 1 +@@ -223,6 +224,10 @@ class FakeRepository: + def __str__(self): + return self.id + ++ def _ui_id(self): ++ return self.id ++ ui_id = property(fget=lambda self: self._ui_id()) ++ + + # Goal for the below is to have a packageobject that can be used by generic + # functions independent of the type of package - ie: installed or available +@@ -243,34 +248,87 @@ class PackageObject(object): def _ui_envra(self): if self.epoch == '0': @@ -155723,7 +158190,7 @@ index 5ef9951..da31971 100644 def __str__(self): return self.ui_envra -@@ -481,15 +534,14 @@ class RpmBase(object): +@@ -481,15 +539,14 @@ class RpmBase(object): if prcotuple in self._prco_lookup[prcotype]: return 1 @@ -155747,7 +158214,7 @@ index 5ef9951..da31971 100644 return 0 -@@ -611,15 +663,18 @@ class RpmBase(object): +@@ -611,15 +668,18 @@ class RpmBase(object): dirlist = property(fget=lambda self: self.returnFileEntries(ftype='dir')) ghostlist = property(fget=lambda self: self.returnFileEntries(ftype='ghost')) requires = property(fget=lambda self: self.returnPrco('requires')) @@ -155766,7 +158233,7 @@ index 5ef9951..da31971 100644 conflicts_print = property(fget=lambda self: self.returnPrco('conflicts', True)) obsoletes_print = property(fget=lambda self: self.returnPrco('obsoletes', True)) changelog = property(fget=lambda self: self.returnChangelog()) -@@ -1083,7 +1138,7 @@ class YumAvailablePackage(PackageObject, RpmBase): +@@ -1083,7 +1143,7 @@ class YumAvailablePackage(PackageObject, RpmBase): misc.to_unicode(misc.to_xml(self.summary)), misc.to_unicode(misc.to_xml(self.description)), packager, url, self.filetime, @@ -155775,7 +158242,7 @@ index 5ef9951..da31971 100644 msg += self._return_remote_location() return msg -@@ -1133,7 +1188,7 @@ class YumAvailablePackage(PackageObject, RpmBase): +@@ -1133,7 +1193,7 @@ class YumAvailablePackage(PackageObject, RpmBase): msg = "" mylist = getattr(self, pcotype) if mylist: msg = "\n \n" % pcotype @@ -155784,7 +158251,7 @@ index 5ef9951..da31971 100644 pcostring = ''' %s\n""" % misc.to_xml(fn) return msg -@@ -1194,8 +1249,8 @@ class YumAvailablePackage(PackageObject, RpmBase): +@@ -1194,8 +1254,8 @@ class YumAvailablePackage(PackageObject, RpmBase): continue newlist.append(i) mylist = newlist @@ -155810,7 +158277,7 @@ index 5ef9951..da31971 100644 if name.startswith('rpmlib('): continue # this drops out requires that the pkg provides for itself. -@@ -1217,13 +1272,16 @@ class YumAvailablePackage(PackageObject, RpmBase): +@@ -1217,13 +1277,16 @@ class YumAvailablePackage(PackageObject, RpmBase): prcostring += ''' ver="%s"''' % misc.to_xml(v, attrib=True) if r: prcostring += ''' rel="%s"''' % misc.to_xml(r, attrib=True) @@ -155829,7 +158296,7 @@ index 5ef9951..da31971 100644 return msg def _dump_changelog(self, clog_limit): -@@ -1272,6 +1330,13 @@ class YumAvailablePackage(PackageObject, RpmBase): +@@ -1272,6 +1335,13 @@ class YumAvailablePackage(PackageObject, RpmBase): return misc.to_utf8(msg) @@ -155843,7 +158310,7 @@ index 5ef9951..da31971 100644 # This is a tweak on YumAvailablePackage() and is a base class for packages # which are actual rpms. -@@ -1299,7 +1364,8 @@ class YumHeaderPackage(YumAvailablePackage): +@@ -1299,7 +1369,8 @@ class YumHeaderPackage(YumAvailablePackage): self.pkgid = self.hdr[rpm.RPMTAG_SHA1HEADER] if not self.pkgid: self.pkgid = "%s.%s" %(self.hdr['name'], self.hdr['buildtime']) @@ -155853,7 +158320,7 @@ index 5ef9951..da31971 100644 self.__mode_cache = {} self.__prcoPopulated = False -@@ -1353,6 +1419,12 @@ class YumHeaderPackage(YumAvailablePackage): +@@ -1353,6 +1424,12 @@ class YumHeaderPackage(YumAvailablePackage): continue lst = hdr[getattr(rpm, 'RPMTAG_%sFLAGS' % tag)] @@ -155866,7 +158333,7 @@ index 5ef9951..da31971 100644 flag = map(rpmUtils.miscutils.flagToString, lst) flag = map(misc.share_data, flag) -@@ -1363,6 +1435,10 @@ class YumHeaderPackage(YumAvailablePackage): +@@ -1363,6 +1440,10 @@ class YumHeaderPackage(YumAvailablePackage): prcotype = tag2prco[tag] self.prco[prcotype] = map(misc.share_data, zip(name,flag,vers)) @@ -155877,7 +158344,7 @@ index 5ef9951..da31971 100644 def tagByName(self, tag): warnings.warn("tagByName() will go away in a furture version of Yum.\n", -@@ -1447,7 +1523,7 @@ class YumHeaderPackage(YumAvailablePackage): +@@ -1447,7 +1528,7 @@ class YumHeaderPackage(YumAvailablePackage): raise NotImplementedError() def _size(self): @@ -155886,6 +158353,23 @@ index 5ef9951..da31971 100644 def _is_pre_req(self, flag): """check the flags for a requirement, return 1 or 0 whether or not requires +diff --git a/yum/parser.py b/yum/parser.py +index fccf528..f443ce7 100644 +--- a/yum/parser.py ++++ b/yum/parser.py +@@ -73,7 +73,11 @@ class ConfigPreProcessor: + if scheme == '': + # check it to make sure it's not a relative file url + if configfile[0] != '/': +- configfile = os.getcwd() + '/' + configfile ++ try: ++ rootdir = os.getcwd() + "/" ++ except: ++ rootdir = "/" ++ configfile = rootdir + configfile + url = 'file://' + configfile + else: + url = configfile diff --git a/yum/pgpmsg.py b/yum/pgpmsg.py index ee825c6..dae60c9 100644 --- a/yum/pgpmsg.py @@ -156454,8 +158938,133 @@ index bfc49b7..9ddcae6 100644 a = parsever(a) b = parsever(b) +diff --git a/yum/repoMDObject.py b/yum/repoMDObject.py +index 31b1080..eb84123 100755 +--- a/yum/repoMDObject.py ++++ b/yum/repoMDObject.py +@@ -226,12 +226,11 @@ class RepoMD: + tag = """ %s\n""" % (to_xml(item)) + tags += tag + for (cpeid, item) in self.tags['distro']: +- itemlist = list(item) # frellingsets. + if cpeid: + tag = """ %s\n""" % ( +- to_xml(cpeid, attrib=True), to_xml(itemlist[0])) ++ to_xml(cpeid, attrib=True), to_xml(item)) + else: +- tag = """ %s\n""" % (to_xml(itemlist[0])) ++ tag = """ %s\n""" % (to_xml(item)) + tags += tag + tags += """ \n""" + msg += tags +diff --git a/yum/repos.py b/yum/repos.py +index 3793bad..6d4c20e 100644 +--- a/yum/repos.py ++++ b/yum/repos.py +@@ -22,6 +22,7 @@ import misc + + import Errors + from packageSack import MetaSack ++import urlgrabber.grabber + + from weakref import proxy as weakref + +@@ -67,6 +68,47 @@ class RepoStorage: + self._cache_enabled_repos = [] + self.quick_enable_disable = {} + ++ def retrieveAllMD(self): ++ """ Download metadata for all enabled repositories, ++ based on mdpolicy. ++ """ ++ ++ if not hasattr(urlgrabber.grabber, 'parallel_wait'): ++ return ++ ++ repos = [] ++ for repo in self.listEnabled(): ++ if repo.cache: ++ continue ++ try: ++ dl = repo._async and repo._commonLoadRepoXML(repo) ++ except Errors.RepoError, e: ++ if not repo.skip_if_unavailable: ++ raise ++ self.disableRepo(repo.id) ++ dl = False ++ if dl: ++ mdtypes = repo._mdpolicy2mdtypes() ++ downloading = repo._commonRetrieveDataMD_list(mdtypes) ++ repos.append((repo, downloading, [False])) ++ ++ # with sizes first, then without sizes.. ++ for no_size in (False, True): ++ for repo, downloading, error in repos: ++ def failfunc(obj, error=error): ++ error[0] = True ++ for (ndata, nmdtype) in downloading: ++ if (ndata.size is None) == no_size: ++ repo._retrieveMD(nmdtype, async=True, failfunc=failfunc) ++ urlgrabber.grabber.parallel_wait() ++ ++ # done or revert ++ for repo, downloading, error in repos: ++ if error[0]: # some MD failed? ++ repo._revertOldRepoXML() ++ else: ++ repo._commonRetrieveDataMD_done(downloading) ++ + def doSetup(self, thisrepo = None): + + self.ayum.plugins.run('prereposetup') +@@ -89,6 +131,7 @@ class RepoStorage: + self.disableRepo(repo.id) + + self._setup = True ++ self.retrieveAllMD() + self.ayum.plugins.run('postreposetup') + + def __str__(self): +@@ -223,15 +266,16 @@ class RepoStorage: + + self._cachedir = cachedir + for repo in self.repos.values(): +- repo.old_base_cache_dir = repo.basecachedir +- repo.basecachedir = cachedir ++ if cachedir != repo.basecachedir: ++ repo.old_base_cache_dir = repo.basecachedir ++ repo.basecachedir = cachedir + + +- def setProgressBar(self, obj): ++ def setProgressBar(self, obj, multi_obj=None): + """sets the progress bar for downloading files from repos""" + + for repo in self.repos.values(): +- repo.setCallback(obj) ++ repo.setCallback(obj, multi_obj) + + def setFailureCallback(self, obj): + """sets the failure callback for all repos""" +@@ -288,6 +332,16 @@ class RepoStorage: + else: + data = [ mdtype ] + ++ if hasattr(urlgrabber.grabber, 'parallel_wait'): ++ # download all metadata in parallel ++ for repo in myrepos: ++ if repo.cache: ++ continue ++ if repo._async: ++ sack = repo.getPackageSack() ++ sack._retrieve_async(repo, data) ++ urlgrabber.grabber.parallel_wait() ++ + for repo in myrepos: + sack = repo.getPackageSack() + try: diff --git a/yum/rpmsack.py b/yum/rpmsack.py -index e289a7a..ed8e3d1 100644 +index e289a7a..302f613 100644 --- a/yum/rpmsack.py +++ b/yum/rpmsack.py @@ -48,6 +48,17 @@ def _open_no_umask(*args): @@ -156495,7 +159104,62 @@ index e289a7a..ed8e3d1 100644 continue pkg = pkg[0] -@@ -993,7 +1005,7 @@ class RPMDBPackageSack(PackageSackBase): +@@ -534,13 +546,11 @@ class RPMDBPackageSack(PackageSackBase): + qpat = pat[0] + if qpat in ('?', '*', '['): + qpat = None +- if ignore_case: +- if qpat is not None: +- qpat = qpat.lower() +- ret.append((qpat, re.compile(fnmatch.translate(pat), re.I))) +- else: +- ret.append((qpat, re.compile(fnmatch.translate(pat)))) ++ elif ignore_case: ++ qpat = qpat.lower() ++ ret.append((qpat, misc.compile_pattern(pat, ignore_case))) + return ret ++ + @staticmethod + def _match_repattern(repatterns, hdr, ignore_case): + """ This is basically parsePackages() but for rpm hdr objects. """ +@@ -558,20 +568,20 @@ class RPMDBPackageSack(PackageSackBase): + qname = qname.lower() + if qpat is not None and qpat != qname and qpat != epoch[0]: + continue +- if repat.match(hdr['name']): ++ if repat(hdr['name']): + return True +- if repat.match("%(name)s-%(version)s-%(release)s.%(arch)s" % hdr): ++ if repat("%(name)s-%(version)s-%(release)s.%(arch)s" % hdr): + return True +- if repat.match("%(name)s.%(arch)s" % hdr): ++ if repat("%(name)s.%(arch)s" % hdr): + return True +- if repat.match("%(name)s-%(version)s" % hdr): ++ if repat("%(name)s-%(version)s" % hdr): + return True +- if repat.match("%(name)s-%(version)s-%(release)s" % hdr): ++ if repat("%(name)s-%(version)s-%(release)s" % hdr): + return True +- if repat.match(epoch + ":%(name)s-%(version)s-%(release)s.%(arch)s" ++ if repat(epoch + ":%(name)s-%(version)s-%(release)s.%(arch)s" + % hdr): + return True +- if repat.match("%(name)s-%(epoch)s:%(version)s-%(release)s.%(arch)s" ++ if repat("%(name)s-%(epoch)s:%(version)s-%(release)s.%(arch)s" + % hdr): + return True + return False +@@ -616,7 +626,7 @@ class RPMDBPackageSack(PackageSackBase): + for hdr, idx in self._get_packages(): + if self._match_repattern(rpats, hdr, ignore_case): + self._makePackageObject(hdr, idx) +- self._completely_loaded = patterns is None ++ self._completely_loaded = rpats is None + + pkgobjlist = self._idx2pkg.values() + # Remove gpg-pubkeys, as no sane callers expects/likes them... +@@ -993,7 +1003,7 @@ class RPMDBPackageSack(PackageSackBase): return if not load_packages: @@ -156504,7 +159168,7 @@ index e289a7a..ed8e3d1 100644 for pkgtup in checksum_data: if checksum_data[pkgtup] is None: -@@ -1002,7 +1014,8 @@ class RPMDBPackageSack(PackageSackBase): +@@ -1002,7 +1012,8 @@ class RPMDBPackageSack(PackageSackBase): (n, a, e, v, r) = pkgtup pkg = self.searchNevra(n, e, v, r, a) if not pkg: @@ -156514,7 +159178,7 @@ index e289a7a..ed8e3d1 100644 continue pkg = pkg[0] (T, D) = checksum_data[pkgtup] -@@ -1088,7 +1101,7 @@ class RPMDBPackageSack(PackageSackBase): +@@ -1088,7 +1099,7 @@ class RPMDBPackageSack(PackageSackBase): return try: @@ -156523,7 +159187,7 @@ index e289a7a..ed8e3d1 100644 except (IOError, OSError), e: return -@@ -1456,7 +1469,7 @@ class RPMDBPackageSack(PackageSackBase): +@@ -1456,7 +1467,7 @@ class RPMDBPackageSack(PackageSackBase): providers = set() # Speedup, as usual :) problems = [] for pkg in sorted(pkgs): # The sort here is mainly for "UI" @@ -156532,7 +159196,7 @@ index e289a7a..ed8e3d1 100644 if rreq[0].startswith('rpmlib'): continue if rreq in providers: continue -@@ -1562,11 +1575,11 @@ class RPMDBAdditionalData(object): +@@ -1562,11 +1573,11 @@ class RPMDBAdditionalData(object): self._packages = {} # pkgid = dir if not os.path.exists(self.conf.db_path): try: @@ -156547,7 +159211,7 @@ index e289a7a..ed8e3d1 100644 else: if os.access(self.conf.db_path, os.W_OK): self.conf.writable = True -@@ -1622,6 +1635,7 @@ class RPMDBAdditionalDataPackage(object): +@@ -1622,6 +1633,7 @@ class RPMDBAdditionalDataPackage(object): 'installed_by', 'changed_by', 'from_repo', 'from_repo_revision', 'from_repo_timestamp', 'releasever', @@ -156555,7 +159219,7 @@ index e289a7a..ed8e3d1 100644 'command_line']) def __init__(self, conf, pkgdir, yumdb_cache=None): -@@ -1708,7 +1722,7 @@ class RPMDBAdditionalDataPackage(object): +@@ -1708,7 +1720,7 @@ class RPMDBAdditionalDataPackage(object): def _write(self, attr, value): # check for self._conf.writable before going on? if not os.path.exists(self._mydir): @@ -156564,7 +159228,7 @@ index e289a7a..ed8e3d1 100644 attr = _sanitize(attr) if attr in self._read_cached_data: -@@ -1753,7 +1767,7 @@ class RPMDBAdditionalDataPackage(object): +@@ -1753,7 +1765,7 @@ class RPMDBAdditionalDataPackage(object): if attr.endswith('.tmp'): raise AttributeError, "%s has no attribute %s" % (self, attr) @@ -156574,7 +159238,7 @@ index e289a7a..ed8e3d1 100644 raise AttributeError, "%s has no attribute %s" % (self, attr) diff --git a/yum/rpmtrans.py b/yum/rpmtrans.py -index 9b265f9..24a1f9e 100644 +index 9b265f9..74a0f3f 100644 --- a/yum/rpmtrans.py +++ b/yum/rpmtrans.py @@ -119,7 +119,11 @@ class RPMBaseCallback: @@ -156616,10 +159280,32 @@ index 9b265f9..24a1f9e 100644 newFunc.__name__ = func.__name__ newFunc.__doc__ = func.__doc__ -@@ -621,3 +633,9 @@ class RPMTransaction: +@@ -463,6 +475,11 @@ class RPMTransaction: + # SCRIPT_ERROR is only in rpm >= 4.6.0 + elif hasattr(rpm, "RPMCALLBACK_SCRIPT_ERROR") and what == rpm.RPMCALLBACK_SCRIPT_ERROR: + self._scriptError(bytes, total, h) ++ # SCRIPT_START and SCRIPT_STOP are only in rpm >= 4.10 ++ elif hasattr(rpm, "RPMCALLBACK_SCRIPT_START") and what == rpm.RPMCALLBACK_SCRIPT_START: ++ self._scriptStart(bytes, total, h); ++ elif hasattr(rpm, "RPMCALLBACK_SCRIPT_STOP") and what == rpm.RPMCALLBACK_SCRIPT_STOP: ++ self._scriptStop(bytes, total, h); + + + def _transStart(self, bytes, total, h): +@@ -619,5 +636,19 @@ class RPMTransaction: + msg = ("Non-fatal %s scriptlet failure in rpm package %s" % + (scriptlet_name, package_name)) self.display.errorlog(msg) ++ self._scriptout(package_name) # FIXME - what else should we do here? raise a failure and abort? ++ def _scriptStart(self, bytes, total, h): ++ pass ++ ++ def _scriptStop(self, bytes, total, h): ++ name, txmbr = self._getTxmbr(h) ++ self._scriptout(txmbr or name) ++ + def verify_txmbr(self, txmbr, count): + " Callback for post transaction when we are in verifyTransaction(). " + if not hasattr(self.display, 'verify_txmbr'): @@ -156627,10 +159313,39 @@ index 9b265f9..24a1f9e 100644 + + self.display.verify_txmbr(self.base, txmbr, count) diff --git a/yum/sqlitesack.py b/yum/sqlitesack.py -index 8a6f6f3..f6df93e 100644 +index 8a6f6f3..a955895 100644 --- a/yum/sqlitesack.py +++ b/yum/sqlitesack.py -@@ -406,7 +406,7 @@ class YumAvailablePackageSqlite(YumAvailablePackage, PackageObject, RpmBase): +@@ -69,7 +69,7 @@ def _parse_pkg_n(match, regexp_match, n): + if not regexp_match: + return False + +- if (match and n and match[0] not in ('?', '*') and match[0] != n[0]): ++ if (match and n and match[0] not in ('?', '*', '[') and match[0] != n[0]): + return False + if regexp_match(n): + return True +@@ -82,7 +82,7 @@ def _parse_pkg(match, regexp_match, data, e,v,r,a): + # Worthless speed hacks? + if match == n: + return True +- if (match and n and match[0] not in ('?', '*') and ++ if (match and n and match[0] not in ('?', '*', '[') and + match[0] != n[0] and match[0] != e[0]): + return False + +@@ -382,6 +382,10 @@ class YumAvailablePackageSqlite(YumAvailablePackage, PackageObject, RpmBase): + + def returnPrco(self, prcotype, printable=False): + prcotype = _share_data(prcotype) ++ if prcotype == 'strong_requires': ++ # pkg not installed so we don't know require flags yet ++ # returning all requires should work in most cases ++ prcotype = 'requires' + if isinstance(self.prco[prcotype], tuple): + sql = "SELECT name, version, release, epoch, flags " \ + "FROM %s WHERE pkgKey = ?" % prcotype +@@ -406,7 +410,7 @@ class YumAvailablePackageSqlite(YumAvailablePackage, PackageObject, RpmBase): requires = [] for ob in cur: pre = "0" @@ -156639,7 +159354,7 @@ index 8a6f6f3..f6df93e 100644 pre = "1" prco_set = (_share_data(ob['name']), _share_data(ob['flags']), (_share_data(ob['epoch']), -@@ -917,8 +917,7 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack): +@@ -917,8 +921,7 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack): # ultra simple optimization if misc.re_primary_filename(name): @@ -156649,8 +159364,33 @@ index 8a6f6f3..f6df93e 100644 if len(self.filelistsdb) == 0: # grab repo object from primarydb and force filelists population in this sack using repo +@@ -1560,7 +1563,7 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack): + patterns = tmp + return (need_full, patterns, fields, False) + +- @catchSqliteException ++ # @catchSqliteException has no effect on generators + def _yieldSQLDataList(self, repoid, patterns, fields, ignore_case): + """Yields all the package data for the given params. Excludes are done + at this stage. """ +@@ -1593,6 +1596,7 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack): + for x in cur: + yield (repo, x) + ++ @catchSqliteException + def _buildPkgObjList(self, repoid=None, patterns=None, ignore_case=False): + """Builds a list of packages, only containing nevra information. + Excludes are done at this stage. """ +@@ -1666,6 +1670,7 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack): + + return returnList + ++ @catchSqliteException + def simplePkgList(self, patterns=None, ignore_case=False): + """Returns a list of pkg tuples (n, a, e, v, r), optionally from a + single repoid. Note that the packages are always filtered to those diff --git a/yum/transactioninfo.py b/yum/transactioninfo.py -index 4d89d83..97de8f0 100644 +index 4d89d83..e7377bf 100644 --- a/yum/transactioninfo.py +++ b/yum/transactioninfo.py @@ -87,7 +87,8 @@ class TransactionData: @@ -156663,7 +159403,26 @@ index 4d89d83..97de8f0 100644 self.pkgSackPackages = 0 self.localSack = PackageSack() self._inSack = GetProvReqOnlyPackageSack() -@@ -115,6 +116,18 @@ class TransactionData: +@@ -95,6 +96,8 @@ class TransactionData: + # lists of txmbrs in their states - just placeholders + self.instgroups = [] + self.removedgroups = [] ++ self.instenvironments = [] ++ self.removedenvironments = [] + self.removed = [] + self.installed = [] + self.updated = [] +@@ -105,6 +108,9 @@ class TransactionData: + self.reinstalled = [] + self.downgraded = [] + self.failed = [] ++ ++ self._future_rpmdbv = None ++ self._check_future_rpmdbv = None + + def __len__(self): + return len(self.pkgdict) +@@ -115,6 +121,18 @@ class TransactionData: else: return iter(self.getMembers()) @@ -156682,7 +159441,7 @@ index 4d89d83..97de8f0 100644 def debugprint(self, msg): if self.debug: print msg -@@ -208,7 +221,9 @@ class TransactionData: +@@ -208,7 +226,9 @@ class TransactionData: txmbrs = self.matchNaevr(na[0], na[1]) if not txmbrs: @@ -156693,7 +159452,46 @@ index 4d89d83..97de8f0 100644 pkgs = [] else: pkgs = self.pkgSack.returnPackages(patterns=[pattern]) -@@ -547,9 +562,10 @@ class TransactionData: +@@ -334,6 +354,8 @@ class TransactionData: + + self.instgroups = [] + self.removedgroups = [] ++ self.instenvironments = [] ++ self.removedenvironments = [] + self.removed = [] + self.installed = [] + self.updated = [] +@@ -365,6 +387,10 @@ class TransactionData: + for g in txmbr.groups: + if g not in self.instgroups: + self.instgroups.append(g) ++ if txmbr.environments: ++ for evg in txmbr.environments: ++ if evg not in self.instenvironments: ++ self.instenvironments.append(evg) + if txmbr.isDep: + self.depinstalled.append(txmbr) + else: +@@ -377,6 +403,9 @@ class TransactionData: + for g in txmbr.groups: + if g not in self.instgroups: + self.removedgroups.append(g) ++ for evg in txmbr.environments: ++ if evg not in self.instenvironments: ++ self.removedenvironments.append(evg) + if txmbr.isDep: + self.depremoved.append(txmbr) + else: +@@ -402,6 +431,8 @@ class TransactionData: + self.depremoved.sort() + self.instgroups.sort() + self.removedgroups.sort() ++ self.instenvironments.sort() ++ self.removedenvironments.sort() + self.reinstalled.sort() + self.downgraded.sort() + self.failed.sort() +@@ -547,9 +578,10 @@ class TransactionData: return txmbr @@ -156706,11 +159504,48 @@ index 4d89d83..97de8f0 100644 def getNewProvides(self, name, flag=None, version=(None, None, None)): """return dict { packages -> list of matching provides } +@@ -619,6 +651,12 @@ class TransactionData: + """ Return a simple version for the future rpmdb. Works like + rpmdb.simpleVersion(main_only=True)[0], but for the state the rpmdb + will be in after the transaction. """ ++ ++ if self._future_rpmdbv is not None: ++ sc, ret = self._future_rpmdbv ++ if sc == self.state_counter: ++ return ret ++ + pkgs = self.rpmdb.returnPackages() + _reinstalled_pkgtups = {} + for txmbr in self.getMembersWithState(None, TS_INSTALL_STATES): +@@ -658,6 +696,7 @@ class TransactionData: + + self.rpmdb.transactionCachePackageChecksums(pkg_checksum_tups) + ++ self._future_rpmdbv = (self.state_counter, main) + return main + + def findObsoletedByThisMember(self, txmbr): +@@ -752,6 +791,7 @@ class TransactionMember: + self.downgraded_by = [] + self.reinstall = False + self.groups = [] # groups it's in ++ self.environments = [] # Env. groups it's in + self._poattr = ['pkgtup', 'repoid', 'name', 'arch', 'epoch', 'version', + 'release'] + +@@ -825,5 +865,7 @@ class TransactionMember: + + if self.groups: + msg += " groups: %s\n" % ' '.join(self.groups) ++ if self.environments: ++ msg += " environments: %s\n" % ' '.join(self.environments) + + return msg diff --git a/yum/update_md.py b/yum/update_md.py -index 2cb1acb..7da6a08 100644 +index 2cb1acb..0586c1c 100644 --- a/yum/update_md.py +++ b/yum/update_md.py -@@ -79,8 +79,14 @@ class UpdateNotice(object): +@@ -79,11 +79,31 @@ class UpdateNotice(object): def __getitem__(self, item): """ Allows scriptable metadata access (ie: un['update_id']). """ @@ -156725,8 +159560,69 @@ index 2cb1acb..7da6a08 100644 def __setitem__(self, item, val): self._md[item] = val ++ def __eq__(self, other): ++ # Tests to see if it's "the same data", which means that the ++ # packages can be different (see add_notice). ++ ++ if not other or not hasattr(other, '_md'): ++ return False ++ ++ for data in ('type', 'update_id', 'status', 'rights', ++ 'issued', 'updated', 'version', 'pushcount', ++ 'from', 'title', 'summary', 'description', 'solution'): ++ if self._md[data] != other._md[data]: ++ return False ++ return True ++ + def text(self, skip_data=('files', 'summary', 'rights', 'solution')): + head = """ + =============================================================================== +@@ -423,9 +443,42 @@ class UpdateMetadata(object): + def add_notice(self, un): + """ Add an UpdateNotice object. This should be fully populated with + data, esp. update_id and pkglist/packages. """ +- if not un or not un["update_id"] or un['update_id'] in self._notices: ++ if not un or not un["update_id"]: + return + ++ # This is "special", the main thing we want to deal with here is ++ # having one errata that has multiple packages in it rpmA and rpmB, but ++ # the packages are in repos. repoA and repoB. So instead of doing a ++ # single errata pointing to both rpmA and rpmB and put the same thing ++ # in both repodata (which is legal, and works fine) people want to have ++ # just the packages from repoA in the repodata for repoA and vice versa. ++ if un['update_id'] in self._notices: ++ oun = self._notices[un['update_id']] ++ if oun != un: ++ return ++ ++ # Ok, main parts of errata are the same, so now merge references: ++ seen = set() ++ for ref in oun['references']: ++ seen.add(ref['id']) ++ for ref in un['references']: ++ if ref['id'] in seen: ++ continue ++ seen.add(ref['id']) ++ oun['references'].append(ref) ++ ++ # ...and pkglist (this assumes that a pkglist name XYZ is the same): ++ seen = set() ++ for pkg in oun['pkglist']: ++ seen.add(pkg['name']) ++ for pkg in un['pkglist']: ++ if pkg['name'] in seen: ++ continue ++ seen.add(pkg['name']) ++ oun['pkglist'].append(pkg) ++ ++ un = oun ++ + self._notices[un['update_id']] = un + for pkg in un['pkglist']: + for filedata in pkg['packages']: diff --git a/yum/yumRepo.py b/yum/yumRepo.py -index e5e9ece..62e53f8 100644 +index e5e9ece..e362c43 100644 --- a/yum/yumRepo.py +++ b/yum/yumRepo.py @@ -20,10 +20,12 @@ import time @@ -156750,8 +159646,229 @@ index e5e9ece..62e53f8 100644 from constants import * import metalink -@@ -434,22 +437,13 @@ class YumRepository(Repository, config.RepoConf): - if self.proxy not in empty: +@@ -104,7 +107,7 @@ class YumPackageSack(packageSack.PackageSack): + if repo in self.added: + if 'metadata' not in self.added[repo]: + raise Errors.RepoError, '%s md for %s imported before primary' \ +- % (datatype, repo.id) ++ % (datatype, repo.ui_id) + current = 0 + for pkgid in dataobj: + current += 1 +@@ -121,6 +124,25 @@ class YumPackageSack(packageSack.PackageSack): + # umm, wtf? + pass + ++ def _retrieve_async(self, repo, data): ++ """ Just schedule the metadata downloads """ ++ ++ for item in data: ++ if item in self.added.get(repo, []): ++ continue ++ if item == 'metadata': ++ mydbtype = 'primary_db' ++ elif item == 'filelists': ++ mydbtype = 'filelists_db' ++ elif item == 'otherdata': ++ mydbtype = 'other_db' ++ else: ++ continue ++ ++ if self._check_db_version(repo, mydbtype): ++ if not self._check_uncompressed_db(repo, mydbtype): ++ repo._retrieveMD(mydbtype, async=True, failfunc=None) ++ + def populate(self, repo, mdtype='metadata', callback=None, cacheonly=0): + if mdtype == 'all': + data = ['metadata', 'filelists', 'otherdata'] +@@ -138,8 +160,6 @@ class YumPackageSack(packageSack.PackageSack): + if item in self.added[repo]: + continue + +- db_fn = None +- + if item == 'metadata': + mydbtype = 'primary_db' + mymdtype = 'primary' +@@ -162,25 +182,37 @@ class YumPackageSack(packageSack.PackageSack): + continue + + if self._check_db_version(repo, mydbtype): +- # see if we have the uncompressed db and check it's checksum vs the openchecksum +- # if not download the compressed file +- # decompress it +- # unlink it ++ # Use gen decompression on DB files. Keeps exactly what we ++ # downloaded in the download dir. + ++ # Backwards compat. ... try the old uncompressed version first. + db_un_fn = self._check_uncompressed_db(repo, mydbtype) + if not db_un_fn: ++ db_un_fn = self._check_uncompressed_db_gen(repo, mydbtype) ++ ++ if not db_un_fn: + db_fn = repo._retrieveMD(mydbtype) + if db_fn: +- if not repo.cache: +- db_un_fn = misc.decompress(db_fn) +- misc.unlink_f(db_fn) +- db_un_fn = self._check_uncompressed_db(repo, mydbtype) ++ db_un_fn = self._check_uncompressed_db_gen(repo, ++ mydbtype) ++ if not db_un_fn: # Shouldn't happen? ++ raise Errors.RepoError, '%s: Check uncompressed DB failed' % repo + + dobj = repo.cacheHandler.open_database(db_un_fn) + + else: + repo._xml2sqlite_local = True ++ # Download... + xml = repo_get_function() ++ ++ # Use generated dir. and handle compression types metadata ++ # parser doesn't understand. ++ gen = mymdtype + '.xml' ++ ret = misc.repo_gen_decompress(xml, gen, cached=repo.cache) ++ if not ret: ++ raise Errors.RepoError, '%s: Decompress DB failed' % repo ++ xml = ret ++ # Convert XML => .sqlite + xmldata = repo.repoXML.getData(mymdtype) + (ctype, csum) = xmldata.checksum + dobj = repo_cache_function(xml, csum) +@@ -193,6 +225,25 @@ class YumPackageSack(packageSack.PackageSack): + # get rid of all this stuff we don't need now + del repo.cacheHandler + ++ def _check_uncompressed_db_gen(self, repo, mdtype, fast=True): ++ """return file name of db in gen/ dir if good, None if not""" ++ ++ mydbdata = repo.repoXML.getData(mdtype) ++ (r_base, remote) = mydbdata.location ++ fname = os.path.basename(remote) ++ compressed_fn = repo.cachedir + '/' + fname ++ db_un_fn = mdtype + '.sqlite' ++ ++ if not repo._checkMD(compressed_fn, mdtype, data=mydbdata, ++ check_can_fail=fast, fast=fast): ++ return None ++ ++ ret = misc.repo_gen_decompress(compressed_fn, db_un_fn, ++ cached=repo.cache) ++ if ret: ++ return self._check_uncompressed_db_fn(repo, mdtype, ret) ++ return None ++ + def _check_uncompressed_db(self, repo, mdtype): + """return file name of uncompressed db is good, None if not""" + mydbdata = repo.repoXML.getData(mdtype) +@@ -201,9 +252,11 @@ class YumPackageSack(packageSack.PackageSack): + compressed_fn = repo.cachedir + '/' + fname + db_un_fn = misc.decompress(compressed_fn, fn_only=True) + ++ return self._check_uncompressed_db_fn(repo, mdtype, db_un_fn) ++ ++ def _check_uncompressed_db_fn(self, repo, mdtype, db_un_fn): + result = None + +- repo._preload_md_from_system_cache(os.path.basename(db_un_fn)) + if os.path.exists(db_un_fn): + if skip_old_DBMD_check and repo._using_old_MD: + return db_un_fn +@@ -260,10 +313,11 @@ class YumRepository(Repository, config.RepoConf): + self.copy_local = 0 + # holder for stuff we've grabbed + self.retrieved = { 'primary':0, 'filelists':0, 'other':0, 'group':0, +- 'updateinfo':0, 'prestodelta' : 0} ++ 'updateinfo':0} + + # callbacks + self.callback = None # for the grabber ++ self.multi_callback = None + self.failure_obj = None + self.mirror_failure_obj = None + self.interrupt_callback = None +@@ -285,6 +339,7 @@ class YumRepository(Repository, config.RepoConf): + + self._grabfunc = None + self._grab = None ++ self._async = False + + def __cmp__(self, other): + """ Sort yum repos. by cost, and then by alphanumeric on their id. """ +@@ -311,6 +366,35 @@ class YumRepository(Repository, config.RepoConf): + return self._sack + sack = property(_getSack) + ++ def _ui_id(self): ++ """ Show self.id, but include any $releasever/$basearch/etc. data. """ ++ if hasattr(self, '__cached_ui_id'): ++ return getattr(self, '__cached_ui_id') ++ ++ val = config._readRawRepoFile(self) ++ if not val: ++ val = '' ++ else: ++ ini, section_id = val ++ ini = ini[section_id] ++ if 'metalink' in ini: ++ val = ini['metalink'] ++ elif 'mirrorlist' in ini: ++ val = ini['mirrorlist'] ++ else: ++ val = ini['baseurl'] ++ ret = self.id ++ if '$releasever' in val: ++ ret += '/' ++ ret += str(self.yumvar['releasever']) ++ if '$basearch' in val: ++ ret += '/' ++ ret += str(self.yumvar['basearch']) ++ # Could maybe list some other things here too? ++ setattr(self, '__cached_ui_id', ret) ++ return ret ++ ui_id = property(_ui_id) ++ + def close(self): + if self._sack is not None: + self.sack.close() +@@ -350,6 +434,10 @@ class YumRepository(Repository, config.RepoConf): + return thisdata.location + + def __str__(self): ++ # Note: You might expect this to be .ui_id, except people got used to ++ # the fact that str(repo) == repo.id and used the former instead of ++ # the later when they wanted just the .id. So we have to live with it ++ # and use .ui_id explicitly. + return self.id + + def _checksum(self, sumtype, file, CHUNK=2**16, checksum_can_fail=False, +@@ -378,7 +466,7 @@ class YumRepository(Repository, config.RepoConf): + 'basecachedir', 'http_headers', 'metadata_cookie', + 'metadata_cookie_fn', 'quick_enable_disable', + 'repoMDFile', 'timestamp_check', 'urls', 'mirrorurls', +- 'yumvar', 'repofile') ++ 'yumvar', 'repofile', 'multi_callback') + for attr in dir(self): + if attr.startswith('_'): + continue +@@ -422,7 +510,7 @@ class YumRepository(Repository, config.RepoConf): + on then raise a repo error""" + if len(self._urls) < 1 and not self.mediaid: + raise Errors.RepoError, \ +- 'Cannot find a valid baseurl for repo: %s' % self.id ++ 'Cannot find a valid baseurl for repo: %s' % self.ui_id + + def doProxyDict(self): + if self._proxy_dict: +@@ -431,25 +519,18 @@ class YumRepository(Repository, config.RepoConf): + self._proxy_dict = {} # zap it + proxy_string = None + empty = (None, '_none_', '') +- if self.proxy not in empty: ++ if self.proxy is None: # got 'proxy=_none_' ++ proxy_string = '' # this disables default proxies ++ elif self.proxy: proxy_string = '%s' % self.proxy if self.proxy_username not in empty: - proxy_parsed = urlparse.urlsplit(self.proxy, allow_fragments=0) @@ -156778,7 +159895,39 @@ index e5e9ece..62e53f8 100644 if proxy_string is not None: self._proxy_dict['http'] = proxy_string -@@ -499,6 +493,7 @@ class YumRepository(Repository, config.RepoConf): +@@ -483,13 +564,30 @@ class YumRepository(Repository, config.RepoConf): + + ugopts = self._default_grabopts() + self._grabfunc = URLGrabber(progress_obj=self.callback, ++ multi_progress_obj=self.multi_callback, + failure_callback=self.failure_obj, + interrupt_callback=self.interrupt_callback, + copy_local=self.copy_local, + reget='simple', + **ugopts) ++ def add_mc(url): ++ host = urlparse.urlsplit(url).netloc.split('@')[-1] ++ mc = self.metalink_data._host2mc.get(host) ++ if mc: ++ url = { ++ 'mirror': misc.to_utf8(url), ++ 'kwargs': { ++ 'max_connections': mc.max_connections, ++ 'preference': mc.preference, ++ 'private': mc.private, ++ }, ++ } ++ return url ++ urls = self.urls ++ if self.metalink: ++ urls = map(add_mc, urls) + +- self._grab = mgclass(self._grabfunc, self.urls, ++ self._grab = mgclass(self._grabfunc, urls, + failure_callback=self.mirror_failure_obj) + + def _default_grabopts(self, cache=True): +@@ -499,6 +597,7 @@ class YumRepository(Repository, config.RepoConf): 'throttle': self.throttle, 'proxies': self.proxy_dict, 'timeout': self.timeout, @@ -156786,11 +159935,82 @@ index e5e9ece..62e53f8 100644 'http_headers': tuple(self.__headersListFromDict(cache=cache)), 'ssl_verify_peer': self.sslverify, 'ssl_verify_host': self.sslverify, -@@ -796,6 +791,16 @@ class YumRepository(Repository, config.RepoConf): +@@ -561,7 +660,7 @@ class YumRepository(Repository, config.RepoConf): + cookie = self.cachedir + '/' + self.metadata_cookie_fn + self.setAttribute('_dir_setup_metadata_cookie', cookie) + +- for dir in [self.cachedir, self.pkgdir]: ++ for dir in [self.cachedir, self.cachedir + '/gen', self.pkgdir]: + self._dirSetupMkdir_p(dir) + + # persistdir is really root-only but try the make anyway and just +@@ -714,15 +813,15 @@ class YumRepository(Repository, config.RepoConf): + local = self.metalink_filename + '.tmp' + if not self._metalinkCurrent(): + url = misc.to_utf8(self.metalink) +- ugopts = self._default_grabopts() ++ ugopts = self._default_grabopts(cache=self.http_caching=='all') + try: + ug = URLGrabber(progress_obj = self.callback, **ugopts) +- result = ug.urlgrab(url, local, text=self.id + "/metalink") ++ result = ug.urlgrab(url, local, text="%s/metalink" % self.ui_id) + +- except urlgrabber.grabber.URLGrabError, e: ++ except URLGrabError, e: + if not os.path.exists(self.metalink_filename): + msg = ("Cannot retrieve metalink for repository: %s. " +- "Please verify its path and try again" % self ) ++ "Please verify its path and try again" % self.ui_id ) + raise Errors.RepoError, msg + # Now, we have an old usable metalink, so we can't move to + # a newer repomd.xml ... or checksums won't match. +@@ -749,9 +848,22 @@ class YumRepository(Repository, config.RepoConf): + value), + fdel=lambda self: setattr(self, "_metalink", None)) + ++ def _all_urls_are_files(self, url): ++ if url: ++ return url.startswith("/") or url.startswith("file:") ++ ++ if not self.urls: # WTF ... but whatever. ++ return False ++ ++ # Not an explicit url ... so make sure all mirrors/etc. are file:// ++ for url in self.urls: ++ if not self._all_urls_are_files(url): ++ return False ++ return True ++ + def _getFile(self, url=None, relative=None, local=None, start=None, end=None, + copy_local=None, checkfunc=None, text=None, reget='simple', +- cache=True, size=None): ++ cache=True, size=None, **kwargs): + """retrieve file from the mirrorgroup for the repo + relative to local, optionally get range from + start to end, also optionally retrieve from a specific baseurl""" +@@ -768,7 +880,7 @@ class YumRepository(Repository, config.RepoConf): + + if local is None or relative is None: + raise Errors.RepoError, \ +- "get request for Repo %s, gave no source or dest" % self.id ++ "get request for Repo %s, gave no source or dest" % self.ui_id + + if self.cache == 1: + if os.path.exists(local): # FIXME - we should figure out a way +@@ -778,7 +890,7 @@ class YumRepository(Repository, config.RepoConf): + raise Errors.RepoError, \ + "Caching enabled but no local cache of %s from %s" % (local, + +- self) ++ self.ui_id) + + if url: + (scheme, netloc, path, query, fragid) = urlparse.urlsplit(url) +@@ -796,6 +908,16 @@ class YumRepository(Repository, config.RepoConf): except Errors.MediaError, e: verbose_logger.log(logginglevels.DEBUG_2, "Error getting package from media; falling back to url %s" %(e,)) -+ if size: ++ if size and (copy_local or not self._all_urls_are_files(url)): + dirstat = os.statvfs(os.path.dirname(local)) + avail = dirstat.f_bavail * dirstat.f_bsize + if avail < long(size): @@ -156803,7 +160023,85 @@ index e5e9ece..62e53f8 100644 if url and scheme != "media": ugopts = self._default_grabopts(cache=cache) ug = URLGrabber(progress_obj = self.callback, -@@ -1020,7 +1025,7 @@ class YumRepository(Repository, config.RepoConf): +@@ -815,14 +937,11 @@ class YumRepository(Repository, config.RepoConf): + range=(start, end), + ) + except URLGrabError, e: +- errstr = "failed to retrieve %s from %s\nerror was %s" % (relative, self.id, e) ++ errstr = "failed to retrieve %s from %s\nerror was %s" % (relative, self, e) + if self.mirrorurls: + errstr +="\n You could try running: yum clean expire-cache" + errstr +="\n To get a new set of mirrors." +- if e.errno == 256: +- raise Errors.NoMoreMirrorsRepoError, errstr +- else: +- raise Errors.RepoError, errstr ++ raise Errors.RepoError, errstr + + + else: +@@ -835,19 +954,18 @@ class YumRepository(Repository, config.RepoConf): + reget = reget, + checkfunc=checkfunc, + http_headers=headers, +- size=size ++ size=size, ++ **kwargs + ) + except URLGrabError, e: +- errstr = "failure: %s from %s: %s" % (relative, self.id, e) +- if e.errno == 256: +- raise Errors.NoMoreMirrorsRepoError, errstr +- else: +- raise Errors.RepoError, errstr ++ errstr = "failure: %s from %s: %s" % (relative, self, e) ++ errors = getattr(e, 'errors', None) ++ raise Errors.NoMoreMirrorsRepoError(errstr, errors) + + return result + __get = _getFile + +- def getPackage(self, package, checkfunc=None, text=None, cache=True): ++ def getPackage(self, package, checkfunc=None, text=None, cache=True, **kwargs): + remote = package.relativepath + local = package.localPkg() + basepath = package.basepath +@@ -857,15 +975,24 @@ class YumRepository(Repository, config.RepoConf): + return local + misc.unlink_f(local) + +- return self._getFile(url=basepath, ++ if checkfunc is None: ++ def checkfunc(obj): ++ if not package.verifyLocalPkg(): ++ misc.unlink_f(local) ++ raise URLGrabError(-1, _('Package does not match intended download.')) ++ ++ ret = self._getFile(url=basepath, + relative=remote, + local=local, + checkfunc=checkfunc, + text=text, + cache=cache, + size=package.size, ++ **kwargs + ) + ++ return ret ++ + def getHeader(self, package, checkfunc = None, reget = 'simple', + cache = True): + +@@ -991,7 +1123,7 @@ class YumRepository(Repository, config.RepoConf): + def _cachingRepoXML(self, local): + """ Should we cache the current repomd.xml """ + if self.cache and not os.path.exists(local): +- raise Errors.RepoError, 'Cannot find repomd.xml file for %s' % self ++ raise Errors.RepoError, 'Cannot find repomd.xml file for %s' % self.ui_id + if self.cache or self.metadataCurrent(): + return True + return False +@@ -1020,7 +1152,7 @@ class YumRepository(Repository, config.RepoConf): if grab_can_fail: return None raise Errors.RepoError, 'Error downloading file %s: %s' % (local, e) @@ -156812,7 +160110,25 @@ index e5e9ece..62e53f8 100644 misc.unlink_f(tfname) if grab_can_fail: return None -@@ -1260,6 +1265,9 @@ class YumRepository(Repository, config.RepoConf): +@@ -1047,7 +1179,7 @@ class YumRepository(Repository, config.RepoConf): + parse_can_fail = 'old_repo_XML' in self._oldRepoMDData + if parse_can_fail: + return None +- raise Errors.RepoError, 'Error importing repomd.xml from %s: %s' % (self, e) ++ raise Errors.RepoError, 'Error importing repomd.xml from %s: %s' % (self.ui_id, e) + + def _saveOldRepoXML(self, local): + """ If we have an older repomd.xml file available, save it out. """ +@@ -1074,7 +1206,7 @@ class YumRepository(Repository, config.RepoConf): + # We still want the old data, so we don't download twice. So we + # pretend everything is good until the revert. + if not self.timestamp_check: +- raise Errors.RepoError, "Can't download or revert repomd.xml" ++ raise Errors.RepoError, "Can't download or revert repomd.xml for %s" % self.ui_id + + if 'old_repo_XML' not in self._oldRepoMDData: + self._oldRepoMDData = {} +@@ -1260,6 +1392,9 @@ class YumRepository(Repository, config.RepoConf): return True def _check_db_version(self, mdtype, repoXML=None): @@ -156822,7 +160138,137 @@ index e5e9ece..62e53f8 100644 if repoXML is None: repoXML = self.repoXML if mdtype in repoXML.repoData: -@@ -1451,12 +1459,7 @@ class YumRepository(Repository, config.RepoConf): +@@ -1277,11 +1412,11 @@ class YumRepository(Repository, config.RepoConf): + return None + + if not file_check: +- compressed = dbmdtype.endswith("_db") +- local = self._get_mdtype_fname(data, compressed) ++ compressed = False ++ local = self._get_mdtype_fname(data) + else: + compressed = False +- local = self._get_mdtype_fname(data, False) ++ local = self._get_mdtype_fname(data) + if not os.path.exists(local): + local = misc.decompress(local, fn_only=True) + compressed = True +@@ -1302,6 +1437,17 @@ class YumRepository(Repository, config.RepoConf): + into the delete list, this means metadata can change filename + without us leaking it. """ + ++ downloading = self._commonRetrieveDataMD_list(mdtypes) ++ for (ndata, nmdtype) in downloading: ++ if not self._retrieveMD(nmdtype, retrieve_can_fail=True): ++ self._revertOldRepoXML() ++ return False ++ self._commonRetrieveDataMD_done(downloading) ++ return True ++ ++ def _commonRetrieveDataMD_list(self, mdtypes): ++ """ Return a list of metadata to be retrieved """ ++ + def _mdtype_eq(omdtype, odata, nmdtype, ndata): + """ Check if two returns from _get_mdtype_data() are equal. """ + if ndata is None: +@@ -1321,6 +1467,14 @@ class YumRepository(Repository, config.RepoConf): + return True + + all_mdtypes = self.retrieved.keys() ++ # Add in any extra stuff we don't know about. ++ for mdtype in self.repoXML.fileTypes(): ++ if mdtype in all_mdtypes: ++ continue ++ if mdtype in ('primary_db', 'filelists_db', 'other_db', 'group_gz'): ++ continue ++ all_mdtypes.append(mdtype) ++ + if mdtypes is None: + mdtypes = all_mdtypes + +@@ -1333,8 +1487,7 @@ class YumRepository(Repository, config.RepoConf): + + # Inited twice atm. ... sue me + self._oldRepoMDData['new_MD_files'] = [] +- downloading_with_size = [] +- downloading_no_size = [] ++ downloading = [] + for mdtype in all_mdtypes: + (nmdtype, ndata) = self._get_mdtype_data(mdtype) + +@@ -1371,43 +1524,16 @@ class YumRepository(Repository, config.RepoConf): + # No old repomd data, but we might still have uncompressed MD + if self._groupCheckDataMDValid(ndata, nmdtype, mdtype): + continue ++ downloading.append((ndata, nmdtype)) ++ return downloading + +- if ndata.size is None: +- downloading_no_size.append((ndata, nmdtype)) +- else: +- downloading_with_size.append((ndata, nmdtype)) +- +- if len(downloading_with_size) == 1: +- downloading_no_size.extend(downloading_with_size) +- downloading_with_size = [] +- +- remote_size = 0 +- local_size = 0 +- for (ndata, nmdtype) in downloading_with_size: # Get total size... +- remote_size += int(ndata.size) ++ def _commonRetrieveDataMD_done(self, downloading): ++ """ Uncompress the downloaded metadata """ + +- for (ndata, nmdtype) in downloading_with_size: +- urlgrabber.progress.text_meter_total_size(remote_size, local_size) +- if not self._retrieveMD(nmdtype, retrieve_can_fail=True): +- self._revertOldRepoXML() +- return False +- local_size += int(ndata.size) +- urlgrabber.progress.text_meter_total_size(0) +- for (ndata, nmdtype) in downloading_no_size: +- if not self._retrieveMD(nmdtype, retrieve_can_fail=True): +- self._revertOldRepoXML() +- return False +- +- for (ndata, nmdtype) in downloading_with_size + downloading_no_size: ++ for (ndata, nmdtype) in downloading: + local = self._get_mdtype_fname(ndata, False) +- if nmdtype.endswith("_db"): # Uncompress any compressed files +- dl_local = local +- local = misc.decompress(dl_local) +- misc.unlink_f(dl_local) + self._oldRepoMDData['new_MD_files'].append(local) +- + self._doneOldRepoXML() +- return True + + def _groupLoadRepoXML(self, text=None, mdtypes=None): + """ Retrieve the new repomd.xml from the repository, then check it +@@ -1421,7 +1547,7 @@ class YumRepository(Repository, config.RepoConf): + self._commonRetrieveDataMD(mdtypes) + + def _mdpolicy2mdtypes(self): +- md_groups = {'instant' : [], ++ md_groups = {'instant' : ['__None__'], + 'group:primary' : ['primary'], + 'group:small' : ["primary", "updateinfo"], + 'group:main' : ["primary", "group", "filelists", +@@ -1436,6 +1562,7 @@ class YumRepository(Repository, config.RepoConf): + if not mdtypes or 'group:all' in mdtypes: + mdtypes = None + else: ++ mdtypes.discard("__None__") + mdtypes = sorted(list(mdtypes)) + return mdtypes + +@@ -1446,17 +1573,12 @@ class YumRepository(Repository, config.RepoConf): + except KeyboardInterrupt: + self._revertOldRepoXML() # Undo metadata cookie? + raise +- raise Errors.RepoError, 'Bad loadRepoXML policy: %s' % (self.mdpolicy) ++ raise Errors.RepoError, 'Bad loadRepoXML policy (for %s): %s' % (self.ui_id, self.mdpolicy) + def _getRepoXML(self): if self._repoXML: return self._repoXML @@ -156832,23 +160278,186 @@ index e5e9ece..62e53f8 100644 - msg = ("Cannot retrieve repository metadata (repomd.xml) for repository: %s. " - "Please verify its path and try again" % self ) - raise Errors.RepoError, msg -+ self._loadRepoXML(text=self) ++ self._loadRepoXML(text=self.ui_id) return self._repoXML -@@ -1614,7 +1617,7 @@ class YumRepository(Repository, config.RepoConf): +@@ -1480,7 +1602,7 @@ class YumRepository(Repository, config.RepoConf): + result = self._getFile(relative='repodata/repomd.xml.asc', + copy_local=1, + local = sigfile, +- text='%s/signature' % self.id, ++ text='%s/signature' % self.ui_id, + reget=None, + checkfunc=None, + cache=self.http_caching == 'all', +@@ -1514,7 +1636,7 @@ class YumRepository(Repository, config.RepoConf): + return self._checkMD(fn, mdtype, openchecksum) + + def _checkMD(self, fn, mdtype, openchecksum=False, +- data=None, check_can_fail=False): ++ data=None, check_can_fail=False, fast=False): + """ Internal function, use .checkMD() from outside yum. """ + + thisdata = data # So the argument name is nicer +@@ -1537,6 +1659,18 @@ class YumRepository(Repository, config.RepoConf): + if size is not None: + size = int(size) + ++ if fast: ++ fsize = misc.stat_f(file) ++ if fsize is None: # File doesn't exist... ++ return None ++ if size is None: ++ return 1 ++ if size == fsize.st_size: ++ return 1 ++ if check_can_fail: ++ return None ++ raise URLGrabError(-1, 'Metadata file does not match size') ++ + try: # get the local checksum + l_csum = self._checksum(r_ctype, file, datasize=size) + except Errors.RepoError, e: +@@ -1551,15 +1685,13 @@ class YumRepository(Repository, config.RepoConf): + return None + raise URLGrabError(-1, 'Metadata file does not match checksum') + +- +- + def retrieveMD(self, mdtype): + """base function to retrieve metadata files from the remote url + returns the path to the local metadata file of a 'mdtype' + mdtype can be 'primary', 'filelists', 'other' or 'group'.""" + return self._retrieveMD(mdtype) + +- def _retrieveMD(self, mdtype, retrieve_can_fail=False): ++ def _retrieveMD(self, mdtype, retrieve_can_fail=False, **kwargs): + """ Internal function, use .retrieveMD() from outside yum. """ + # Note that this can raise Errors.RepoMDError if mdtype doesn't exist + # for this repo. +@@ -1580,15 +1712,19 @@ class YumRepository(Repository, config.RepoConf): + try: + self.checkMD(local, mdtype) + except URLGrabError, e: ++ if retrieve_can_fail: ++ return None + raise Errors.RepoError, \ + "Caching enabled and local cache: %s does not match checksum" % local + else: + return local + + else: # ain't there - raise ++ if retrieve_can_fail: ++ return None + raise Errors.RepoError, \ + "Caching enabled but no local cache of %s from %s" % (local, +- self) ++ self.ui_id) + + if (os.path.exists(local) or + self._preload_md_from_system_cache(os.path.basename(local))): +@@ -1597,8 +1733,10 @@ class YumRepository(Repository, config.RepoConf): + return local # it's the same return the local one + + try: +- checkfunc = (self.checkMD, (mdtype,), {}) +- text = "%s/%s" % (self.id, mdtype) ++ def checkfunc(obj): ++ self.checkMD(obj, mdtype) ++ self.retrieved[mdtype] = 1 ++ text = "%s/%s" % (self, mdtype) + if thisdata.size is None: + reget = None + else: +@@ -1613,8 +1751,9 @@ class YumRepository(Repository, config.RepoConf): + checkfunc=checkfunc, text=text, cache=self.http_caching == 'all', - size=thisdata.size) +- size=thisdata.size) - except (Errors.NoMoreMirrorsRepoError, Errors.RepoError): ++ size=thisdata.size, ++ **kwargs) + except Errors.RepoError: if retrieve_can_fail: return None raise +@@ -1622,9 +1761,8 @@ class YumRepository(Repository, config.RepoConf): + if retrieve_can_fail: + return None + raise Errors.RepoError, \ +- "Could not retrieve %s matching remote checksum from %s" % (local, self) ++ "Could not retrieve %s matching remote checksum from %s" % (local, self.ui_id) + else: +- self.retrieved[mdtype] = 1 + return local + + +@@ -1646,13 +1784,21 @@ class YumRepository(Repository, config.RepoConf): + + def getGroups(self): + """gets groups and returns group file path for the repository, if there +- is none it returns None""" ++ is none or retrieve/decompress fails, it returns None""" + if 'group_gz' in self.repoXML.fileTypes(): +- return self._retrieveMD('group_gz', retrieve_can_fail=True) ++ fn = self._retrieveMD('group_gz', retrieve_can_fail=True) ++ if fn: ++ try: ++ fn = misc.repo_gen_decompress(fn, 'comps.xml', cached=self.cache) ++ except IOError, e: ++ logger.warning(e) ++ fn = None ++ return fn + return self._retrieveMD('group', retrieve_can_fail=True) + +- def setCallback(self, callback): ++ def setCallback(self, callback, multi_callback=None): + self.callback = callback ++ self.multi_callback = multi_callback + self._callbacks_changed = True + + def setFailureObj(self, failure_obj): +@@ -1681,7 +1827,7 @@ class YumRepository(Repository, config.RepoConf): + print "Could not read mirrorlist %s, error was \n%s" %(url, e) + content = [] + for line in content: +- if re.match('\s*(#|$)', line): ++ if not re.match('\w+://\S+\s*$', line): + continue + mirror = line.rstrip() # no more trailing \n's + mirror = mirror.replace('$ARCH', '$BASEARCH') +@@ -1713,7 +1859,7 @@ class YumRepository(Repository, config.RepoConf): + ugopts = self._default_grabopts() + try: + fo = urlgrabber.grabber.urlopen(url, **ugopts) +- except urlgrabber.grabber.URLGrabError, e: ++ except URLGrabError, e: + print "Could not retrieve mirrorlist %s error was\n%s: %s" % (url, e.args[0], misc.to_unicode(e.args[1])) + fo = None + +@@ -1877,7 +2023,7 @@ def getMirrorList(mirrorlist, pdict = None): + + try: + fo = urlresolver.urlopen(url, proxies=pdict) +- except urlgrabber.grabber.URLGrabError, e: ++ except URLGrabError, e: + print "Could not retrieve mirrorlist %s error was\n%s: %s" % (url, e.args[0], misc.to_unicode(e.args[1])) + fo = None + diff --git a/yumcommands.py b/yumcommands.py -index 4dcbea7..9c181ec 100644 +index 4dcbea7..a2e0b1b 100644 --- a/yumcommands.py +++ b/yumcommands.py +@@ -29,7 +29,7 @@ import operator + import locale + import fnmatch + import time +-from yum.i18n import utf8_width, utf8_width_fill, to_unicode ++from yum.i18n import utf8_width, utf8_width_fill, to_unicode, exception2msg + + import yum.config + @@ -43,16 +43,24 @@ def _err_mini_usage(base, basecmd): base.logger.critical(txt) @@ -156981,7 +160590,7 @@ index 4dcbea7..9c181ec 100644 """ if base.repos.listEnabled(): return -@@ -152,85 +199,207 @@ def checkEnabledRepo(base, possible_local_files=[]): +@@ -152,113 +199,273 @@ def checkEnabledRepo(base, possible_local_files=[]): raise cli.CliError class YumCommand: @@ -157141,9 +160750,10 @@ index 4dcbea7..9c181ec 100644 - return base.installPkgs(extcmds) + return base.installPkgs(extcmds, basecmd=basecmd) except yum.Errors.YumBaseError, e: - return 1, [str(e)] - +- return 1, [str(e)] ++ return 1, [exception2msg(e)] + + class UpdateCommand(YumCommand): + """A class containing methods needed by the cli to execute the + update command. @@ -157202,8 +160812,9 @@ index 4dcbea7..9c181ec 100644 self.doneCommand(base, _("Setting up Update Process")) try: return base.updatePkgs(extcmds, update_to=(basecmd == 'update-to')) -@@ -238,21 +407,59 @@ class UpdateCommand(YumCommand): - return 1, [str(e)] + except yum.Errors.YumBaseError, e: +- return 1, [str(e)] ++ return 1, [exception2msg(e)] class DistroSyncCommand(YumCommand): + """A class containing methods needed by the cli to execute the @@ -157262,7 +160873,14 @@ index 4dcbea7..9c181ec 100644 self.doneCommand(base, _("Setting up Distribution Synchronization Process")) try: base.conf.obsoletes = 1 -@@ -289,18 +496,53 @@ def _list_cmd_calc_columns(base, ypl): + return base.distroSyncPkgs(extcmds) + except yum.Errors.YumBaseError, e: +- return 1, [str(e)] ++ return 1, [exception2msg(e)] + + def _add_pkg_simple_list_lens(data, pkg, indent=''): + """ Get the length of each pkg's column. Add that to data. +@@ -289,21 +496,56 @@ def _list_cmd_calc_columns(base, ypl): return (-columns[0], -columns[1], -columns[2]) class InfoCommand(YumCommand): @@ -157315,7 +160933,11 @@ index 4dcbea7..9c181ec 100644 + highlight = False ypl = base.returnPkgLists(extcmds, installed_available=highlight) except yum.Errors.YumBaseError, e: - return 1, [str(e)] +- return 1, [str(e)] ++ return 1, [exception2msg(e)] + else: + update_pkgs = {} + inst_pkgs = {} @@ -341,6 +583,7 @@ class InfoCommand(YumCommand): local_pkgs[(po.name, po.arch)] = po @@ -157342,7 +160964,7 @@ index 4dcbea7..9c181ec 100644 '=' : clar, 'not in' : clai}) rep = base.listPkgs(ypl.extras, _('Extra Packages'), basecmd, columns=columns) -@@ -389,35 +635,95 @@ class InfoCommand(YumCommand): +@@ -389,45 +635,144 @@ class InfoCommand(YumCommand): return 0, [] def needTs(self, base, basecmd, extcmds): @@ -157387,12 +161009,15 @@ index 4dcbea7..9c181ec 100644 + def getNames(self): +- return ['erase', 'remove'] + """Return a list containing the names of this command. This + command can be called from the command line by using any of these names. + + :return: a list containing the names of this command + """ - return ['erase', 'remove'] ++ return ['erase', 'remove', 'autoremove', ++ 'erase-n', 'erase-na', 'erase-nevra', ++ 'remove-n', 'remove-na', 'remove-nevra'] def getUsage(self): + """Return a usage string for this command. @@ -157419,6 +161044,8 @@ index 4dcbea7..9c181ec 100644 + :param extcmds: the command line arguments passed to *basecmd* + """ checkRootUID(base) ++ if basecmd == 'autoremove': ++ return checkPackageArg(base, basecmd, extcmds) def doCommand(self, base, basecmd, extcmds): @@ -157435,11 +161062,32 @@ index 4dcbea7..9c181ec 100644 + 1 = we've errored, exit with error string + 2 = we've got work yet to do, onto the next stage + """ ++ ++ pos = False ++ if basecmd == 'autoremove': ++ # We have to alter this, as it's used in resolving stage. Which ++ # sucks. Just be careful in "yum shell". ++ base.conf.clean_requirements_on_remove = True ++ ++ if not extcmds: ++ pos = True ++ extcmds = [] ++ for pkg in sorted(base.rpmdb.returnLeafNodes()): ++ if 'reason' not in pkg.yumdb_info: ++ continue ++ if pkg.yumdb_info.reason != 'dep': ++ continue ++ extcmds.append(pkg) ++ self.doneCommand(base, _("Setting up Remove Process")) try: - return base.erasePkgs(extcmds) -@@ -425,9 +731,25 @@ class EraseCommand(YumCommand): - return 1, [str(e)] +- return base.erasePkgs(extcmds) ++ ret = base.erasePkgs(extcmds, pos=pos, basecmd=basecmd) + except yum.Errors.YumBaseError, e: +- return 1, [str(e)] ++ ret = (1, [exception2msg(e)]) ++ ++ return ret def needTs(self, base, basecmd, extcmds): + """Return whether a transaction set must be set up before this @@ -157464,7 +161112,7 @@ index 4dcbea7..9c181ec 100644 return True -@@ -442,12 +764,25 @@ class GroupsCommand(YumCommand): +@@ -442,12 +787,25 @@ class GroupsCommand(YumCommand): 'groupinfo' : 'info'} def getNames(self): @@ -157490,7 +161138,16 @@ index 4dcbea7..9c181ec 100644 return _("Display, or use, the groups information") def _grp_setup_doCommand(self, base): -@@ -470,6 +805,10 @@ class GroupsCommand(YumCommand): +@@ -459,7 +817,7 @@ class GroupsCommand(YumCommand): + except yum.Errors.GroupsError: + return 1, [_('No Groups on which to run command')] + except yum.Errors.YumBaseError, e: +- return 1, [str(e)] ++ return 1, [exception2msg(e)] + + def _grp_cmd(self, basecmd, extcmds): + if basecmd in self.direct_commands: +@@ -470,6 +828,10 @@ class GroupsCommand(YumCommand): else: cmd = 'summary' @@ -157501,7 +161158,7 @@ index 4dcbea7..9c181ec 100644 remap = {'update' : 'upgrade', 'erase' : 'remove', 'mark-erase' : 'mark-remove', -@@ -479,32 +818,70 @@ class GroupsCommand(YumCommand): +@@ -479,32 +841,76 @@ class GroupsCommand(YumCommand): return cmd, extcmds def doCheck(self, base, basecmd, extcmds): @@ -157526,12 +161183,18 @@ index 4dcbea7..9c181ec 100644 + ocmds_arg = ('mark-install', 'mark-remove', + 'mark-packages', 'mark-packages-force', + 'unmark-packages', -+ 'mark-packages-sync', 'mark-packages-sync-force') ++ 'mark-packages-sync', 'mark-packages-sync-force', ++ 'mark-groups', 'mark-groups-force', ++ 'unmark-groups', ++ 'mark-groups-sync', 'mark-groups-sync-force') + + ocmds_all = ('mark-install', 'mark-remove', 'mark-convert', + 'mark-packages', 'mark-packages-force', + 'unmark-packages', -+ 'mark-packages-sync', 'mark-packages-sync-force') ++ 'mark-packages-sync', 'mark-packages-sync-force', ++ 'mark-groups', 'mark-groups-force', ++ 'unmark-groups', ++ 'mark-groups-sync', 'mark-groups-sync-force') + + if cmd in ('install', 'remove', 'info') or cmd in ocmds_arg: checkGroupArg(base, cmd, extcmds) @@ -157582,23 +161245,28 @@ index 4dcbea7..9c181ec 100644 cmd, extcmds = self._grp_cmd(basecmd, extcmds) self._grp_setup_doCommand(base) -@@ -524,39 +901,195 @@ class GroupsCommand(YumCommand): +@@ -524,39 +930,258 @@ 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) ++ gRG = base._groupReturnGroups(extcmds,ignore_case=False) ++ igrps, grps, ievgrps, evgrps = gRG ++ for evgrp in evgrps: ++ base.igroups.add_environment(evgrp.environmentid, ++ evgrp.allgroups) ++ for grp in grps: ++ base.igroups.add_group(grp.groupid, grp.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: ++ gRG = base._groupReturnGroups([extcmds[0]], ++ ignore_case=False) ++ igrps, grps, ievgrps, evgrps = gRG ++ if igrps is None or len(igrps) != 1: + return 1, ['No group matched'] + grp = igrps[0] + force = cmd == 'mark-packages-force' @@ -157618,7 +161286,8 @@ index 4dcbea7..9c181ec 100644 + return 0, ['UnMarked packages: ' + ','.join(extcmds)] + + if cmd in ('mark-packages-sync', 'mark-packages-sync-force'): -+ igrps, grps = base._groupReturnGroups(extcmds,ignore_case=False) ++ gRG = base._groupReturnGroups(extcmds,ignore_case=False) ++ igrps, grps, ievgrps, evgrps = gRG + if not igrps: + return 1, ['No group matched'] + force = cmd == 'mark-packages-sync-force' @@ -157632,6 +161301,60 @@ index 4dcbea7..9c181ec 100644 + else: + return 0, ['Marked packages-sync: ' + ','.join(extcmds)] + ++ if cmd in ('mark-groups', 'mark-groups-force'): ++ if len(extcmds) < 2: ++ return 1, ['No environment or group given'] ++ gRG = base._groupReturnGroups([extcmds[0]], ++ ignore_case=False) ++ igrps, grps, ievgrps, evgrps = gRG ++ if ievgrps is None or len(ievgrps) != 1: ++ return 1, ['No environment matched'] ++ evgrp = ievgrps[0] ++ force = cmd == 'mark-groups-force' ++ gRG = base._groupReturnGroups(extcmds[1:], ignore_case=False) ++ for grp in gRG[1]: ++ # Packages full or empty? ++ self.igroups.add_group(grp.groupid, ++ grp.packages, ievgrp) ++ if force: ++ for grp in gRG[0]: ++ grp.environment = evgrp.evgid ++ base.igroups.changed = True ++ base.igroups.save() ++ return 0, ['Marked groups: ' + ','.join(extcmds[1:])] ++ ++ if cmd == 'unmark-groups': ++ gRG = base._groupReturnGroups([extcmds[0]], ++ ignore_case=False) ++ igrps, grps, ievgrps, evgrps = gRG ++ if igrps is None: ++ return 1, ['No groups matched'] ++ for grp in igrps: ++ grp.environment = None ++ base.igroups.changed = True ++ base.igroups.save() ++ return 0, ['UnMarked groups: ' + ','.join(extcmds)] ++ ++ if cmd in ('mark-groups-sync', 'mark-groups-sync-force'): ++ gRG = base._groupReturnGroups(extcmds,ignore_case=False) ++ igrps, grps, ievgrps, evgrps = gRG ++ if not ievgrps: ++ return 1, ['No environment matched'] ++ force = cmd == 'mark-groups-sync-force' ++ for evgrp in ievgrps: ++ grp_names = ",".join(sorted(evgrp.grp_names)) ++ for grp in base.igroups.return_groups(grp_names): ++ if not force and grp.environment is not None: ++ continue ++ grp.environment = evgrp.evgid ++ base.igroups.changed = True ++ base.igroups.save() ++ if force: ++ return 0, ['Marked groups-sync-force: '+','.join(extcmds)] ++ else: ++ return 0, ['Marked groups-sync: ' + ','.join(extcmds)] ++ ++ # FIXME: This doesn't do environment groups atm. + if cmd == 'mark-convert': + # Convert old style info. into groups as objects. + @@ -157681,15 +161404,19 @@ index 4dcbea7..9c181ec 100644 + return 0, ['Converted old style groups to objects.'] + + if cmd == 'mark-remove': -+ for strng in extcmds: -+ for group in base.comps.return_groups(strng): -+ base.igroups.del_group(group.groupid) ++ gRG = base._groupReturnGroups(extcmds,ignore_case=False) ++ igrps, grps, ievgrps, evgrps = gRG ++ for evgrp in ievgrps: ++ base.igroups.del_environment(evgrp.evgid) ++ for grp in igrps: ++ base.igroups.del_group(grp.gid) + base.igroups.save() + return 0, ['Marked remove: ' + ','.join(extcmds)] + + except yum.Errors.YumBaseError, e: - return 1, [str(e)] +- return 1, [str(e)] ++ return 1, [exception2msg(e)] def needTs(self, base, basecmd, extcmds): @@ -157779,7 +161506,14 @@ index 4dcbea7..9c181ec 100644 base.logger.debug(_("Making cache files for all metadata files.")) base.logger.debug(_("This may take a while depending on the speed of this computer")) try: -@@ -572,9 +1105,7 @@ class MakeCacheCommand(YumCommand): +@@ -565,81 +1190,216 @@ class MakeCacheCommand(YumCommand): + repo.mdpolicy = "group:all" + base.doRepoSetup(dosack=0) + base.repos.doSetup() +- for repo in base.repos.listEnabled(): +- repo.repoXML + + # These convert the downloaded data into usable data, # we can't remove them until *LoadRepo() can do: # 1. Download a .sqlite.bz2 and convert to .sqlite # 2. Download a .xml.gz and convert to .xml.gz.sqlite @@ -157788,9 +161522,22 @@ index 4dcbea7..9c181ec 100644 - base.repos.populateSack(mdtype='otherdata', cacheonly=1) + base.repos.populateSack(mdtype='all', cacheonly=1) ++ # Now decompress stuff, so that -C works, sigh. ++ fname_map = {'group_gz' : 'groups.xml', ++ 'pkgtags' : 'pkgtags.sqlite', ++ 'updateinfo' : 'updateinfo.xml', ++ } ++ for repo in base.repos.listEnabled(): ++ for MD in repo.repoXML.fileTypes(): ++ if MD not in fname_map: ++ continue ++ misc.repo_gen_decompress(repo.retrieveMD(MD), ++ fname_map[MD], ++ cached=repo.cache) except yum.Errors.YumBaseError, e: -@@ -582,44 +1113,134 @@ class MakeCacheCommand(YumCommand): +- return 1, [str(e)] ++ return 1, [exception2msg(e)] return 0, [_('Metadata Cache Created')] def needTs(self, base, basecmd, extcmds): @@ -157925,8 +161672,9 @@ index 4dcbea7..9c181ec 100644 base.logger.debug("Searching Packages: ") try: return base.provides(extcmds) -@@ -627,19 +1248,56 @@ class ProvidesCommand(YumCommand): - return 1, [str(e)] + except yum.Errors.YumBaseError, e: +- return 1, [str(e)] ++ return 1, [exception2msg(e)] class CheckUpdateCommand(YumCommand): + """A class containing methods needed by the cli to execute the @@ -157982,7 +161730,13 @@ index 4dcbea7..9c181ec 100644 obscmds = ['obsoletes'] + extcmds base.extcmds.insert(0, 'updates') result = 0 -@@ -681,19 +1339,56 @@ class CheckUpdateCommand(YumCommand): +@@ -676,161 +1436,437 @@ class CheckUpdateCommand(YumCommand): + columns=columns) + result = 100 + except yum.Errors.YumBaseError, e: +- return 1, [str(e)] ++ return 1, [exception2msg(e)] + else: return result, [] class SearchCommand(YumCommand): @@ -158039,8 +161793,9 @@ index 4dcbea7..9c181ec 100644 base.logger.debug(_("Searching Packages: ")) try: return base.search(extcmds) -@@ -701,24 +1396,70 @@ class SearchCommand(YumCommand): - return 1, [str(e)] + except yum.Errors.YumBaseError, e: +- return 1, [str(e)] ++ return 1, [exception2msg(e)] def needTs(self, base, basecmd, extcmds): + """Return whether a transaction set must be set up before this @@ -158110,8 +161865,10 @@ index 4dcbea7..9c181ec 100644 base.conf.obsoletes = 1 self.doneCommand(base, _("Setting up Upgrade Process")) try: -@@ -727,25 +1468,64 @@ class UpgradeCommand(YumCommand): - return 1, [str(e)] + return base.updatePkgs(extcmds, update_to=(basecmd == 'upgrade-to')) + except yum.Errors.YumBaseError, e: +- return 1, [str(e)] ++ return 1, [exception2msg(e)] class LocalInstallCommand(YumCommand): + """A class containing methods needed by the cli to execute the @@ -158175,8 +161932,11 @@ index 4dcbea7..9c181ec 100644 self.doneCommand(base, _("Setting up Local Package Process")) updateonly = basecmd == 'localupdate' -@@ -755,19 +1535,61 @@ class LocalInstallCommand(YumCommand): - return 1, [str(e)] + try: + return base.localInstall(filelist=extcmds, updateonly=updateonly) + except yum.Errors.YumBaseError, e: +- return 1, [str(e)] ++ return 1, [exception2msg(e)] def needTs(self, base, basecmd, extcmds): + """Return whether a transaction set must be set up before this @@ -158238,8 +161998,9 @@ index 4dcbea7..9c181ec 100644 base.logger.debug(_("Searching Packages for Dependency:")) try: return base.resolveDepCli(extcmds) -@@ -775,19 +1597,56 @@ class ResolveDepCommand(YumCommand): - return 1, [str(e)] + except yum.Errors.YumBaseError, e: +- return 1, [str(e)] ++ return 1, [exception2msg(e)] class ShellCommand(YumCommand): + """A class containing methods needed by the cli to execute the @@ -158295,8 +162056,9 @@ index 4dcbea7..9c181ec 100644 self.doneCommand(base, _('Setting up Yum Shell')) try: return base.doShell() -@@ -795,23 +1654,69 @@ class ShellCommand(YumCommand): - return 1, [str(e)] + except yum.Errors.YumBaseError, e: +- return 1, [str(e)] ++ return 1, [exception2msg(e)] def needTs(self, base, basecmd, extcmds): + """Return whether a transaction set must be set up before this @@ -158365,7 +162127,9 @@ index 4dcbea7..9c181ec 100644 self.doneCommand(base, _("Finding dependencies: ")) try: return base.deplist(extcmds) -@@ -820,17 +1725,46 @@ class DepListCommand(YumCommand): + except yum.Errors.YumBaseError, e: +- return 1, [str(e)] ++ return 1, [exception2msg(e)] class RepoListCommand(YumCommand): @@ -158374,12 +162138,13 @@ index 4dcbea7..9c181ec 100644 + """ def getNames(self): +- return ('repolist',) + """Return a list containing the names of this command. This + command can be called from the command line by using any of these names. + + :return: a list containing the names of this command + """ - return ('repolist',) ++ return ('repolist', 'repoinfo') def getUsage(self): + """Return a usage string for this command. @@ -158412,7 +162177,19 @@ index 4dcbea7..9c181ec 100644 def _repo_size(repo): ret = 0 for pkg in repo.sack.returnPackages(): -@@ -866,6 +1800,13 @@ class RepoListCommand(YumCommand): +@@ -857,7 +1893,10 @@ class RepoListCommand(YumCommand): + arg = 'enabled' + extcmds = map(lambda x: x.lower(), extcmds) + +- verbose = base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3) ++ if basecmd == 'repoinfo': ++ verbose = True ++ else: ++ verbose = base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3) + if arg != 'disabled' or extcmds: + try: + # Setup so len(repo.sack) is correct +@@ -866,6 +1905,13 @@ class RepoListCommand(YumCommand): except yum.Errors.RepoError: if verbose: raise @@ -158426,7 +162203,7 @@ index 4dcbea7..9c181ec 100644 repos = base.repos.repos.values() repos.sort() -@@ -924,111 +1865,108 @@ class RepoListCommand(YumCommand): +@@ -924,111 +1970,108 @@ class RepoListCommand(YumCommand): ui_enabled = dhibeg + _('disabled') + hiend ui_endis_wid = utf8_width(_('disabled')) @@ -158440,7 +162217,7 @@ index 4dcbea7..9c181ec 100644 - cols.append((rid, repo.name, - (ui_enabled, ui_endis_wid), ui_num)) + if not verbose: -+ rid = str(repo) ++ rid = repo.ui_id # can't use str() + if enabled and repo.metalink: + mdts = repo.metalink_data.repomd.timestamp + if mdts > repo.repoXML.timestamp: @@ -158452,7 +162229,7 @@ index 4dcbea7..9c181ec 100644 + md = repo.repoXML else: + md = None -+ out = [base.fmtKeyValFill(_("Repo-id : "), repo), ++ out = [base.fmtKeyValFill(_("Repo-id : "), repo.ui_id), + base.fmtKeyValFill(_("Repo-name : "), repo.name)] + + if force_show or extcmds: @@ -158626,12 +162403,12 @@ index 4dcbea7..9c181ec 100644 - base.verbose_logger.log(logginglevels.DEBUG_3, - "%s\n", - "\n".join(map(misc.to_unicode, out))) -+ base.verbose_logger.log(logginglevels.DEBUG_3, "%s\n", ++ base.verbose_logger.info("%s\n", + "\n".join(map(misc.to_unicode, out))) if not verbose and cols: # Work out the first (id) and last (enabled/disalbed/count), -@@ -1088,21 +2026,54 @@ class RepoListCommand(YumCommand): +@@ -1088,21 +2131,54 @@ class RepoListCommand(YumCommand): return 0, ['repolist: ' +to_unicode(locale.format("%d", tot_num, True))] def needTs(self, base, basecmd, extcmds): @@ -158686,7 +162463,7 @@ index 4dcbea7..9c181ec 100644 if len(extcmds) == 0: base.usage() raise cli.CliError -@@ -1147,28 +2118,85 @@ class HelpCommand(YumCommand): +@@ -1147,28 +2223,85 @@ class HelpCommand(YumCommand): return help_output def doCommand(self, base, basecmd, extcmds): @@ -158772,7 +162549,7 @@ index 4dcbea7..9c181ec 100644 self.doneCommand(base, _("Setting up Reinstall Process")) try: return base.reinstallPkgs(extcmds) -@@ -1177,25 +2205,73 @@ class ReInstallCommand(YumCommand): +@@ -1177,49 +2310,139 @@ class ReInstallCommand(YumCommand): return 1, [to_unicode(e)] def getSummary(self): @@ -158846,8 +162623,9 @@ index 4dcbea7..9c181ec 100644 self.doneCommand(base, _("Setting up Downgrade Process")) try: return base.downgradePkgs(extcmds) -@@ -1203,23 +2279,65 @@ class DowngradeCommand(YumCommand): - return 1, [str(e)] + except yum.Errors.YumBaseError, e: +- return 1, [str(e)] ++ return 1, [exception2msg(e)] def getSummary(self): + """Return a one line summary of this command. @@ -158912,7 +162690,25 @@ index 4dcbea7..9c181ec 100644 vcmd = 'installed' if extcmds: vcmd = extcmds[0] -@@ -1344,6 +2462,14 @@ class VersionCommand(YumCommand): +@@ -1308,7 +2531,7 @@ class VersionCommand(YumCommand): + str(data[2][grp]))) + _append_repos(cols, data[3][grp]) + except yum.Errors.YumBaseError, e: +- return 1, [str(e)] ++ return 1, [exception2msg(e)] + if vcmd in ('available', 'all', 'group-available', 'group-all'): + try: + data = base.pkgSack.simpleVersion(not verbose, groups=groups) +@@ -1327,7 +2550,7 @@ class VersionCommand(YumCommand): + if verbose: + _append_repos(cols, data[3][grp]) + except yum.Errors.YumBaseError, e: +- return 1, [str(e)] ++ return 1, [exception2msg(e)] + + data = {'rid' : {}, 'ver' : {}} + for (rid, ver) in cols: +@@ -1344,6 +2567,14 @@ class VersionCommand(YumCommand): return 0, ['version'] def needTs(self, base, basecmd, extcmds): @@ -158927,7 +162723,7 @@ index 4dcbea7..9c181ec 100644 vcmd = 'installed' if extcmds: vcmd = extcmds[0] -@@ -1354,23 +2480,62 @@ class VersionCommand(YumCommand): +@@ -1354,23 +2585,62 @@ class VersionCommand(YumCommand): class HistoryCommand(YumCommand): @@ -158991,7 +162787,7 @@ index 4dcbea7..9c181ec 100644 return 2, ["Repeating transaction %u" % (old.tid,)] def _hcmd_undo(self, base, extcmds): -@@ -1426,12 +2591,54 @@ class HistoryCommand(YumCommand): +@@ -1426,12 +2696,57 @@ class HistoryCommand(YumCommand): def _hcmd_new(self, base, extcmds): base.history._create_db_file() @@ -158999,9 +162795,12 @@ index 4dcbea7..9c181ec 100644 + print "File :", base.history._db_file + num = os.stat(base.history._db_file).st_size + print "Size :", locale.format("%d", num, True) ++ trans_N = base.history.last() ++ if trans_N is None: ++ print _("Transactions:"), 0 ++ return + counts = base.history._pkg_stats() + trans_1 = base.history.old("1")[0] -+ trans_N = base.history.last() + print _("Transactions:"), trans_N.tid + print _("Begin time :"), time.ctime(trans_1.beg_timestamp) + print _("End time :"), time.ctime(trans_N.end_timestamp) @@ -159047,7 +162846,7 @@ index 4dcbea7..9c181ec 100644 if extcmds and extcmds[0] not in cmds: base.logger.critical(_('Invalid history sub-command, use: %s.'), ", ".join(cmds)) -@@ -1444,6 +2651,19 @@ class HistoryCommand(YumCommand): +@@ -1444,6 +2759,19 @@ class HistoryCommand(YumCommand): raise cli.CliError def doCommand(self, base, basecmd, extcmds): @@ -159067,7 +162866,7 @@ index 4dcbea7..9c181ec 100644 vcmd = 'list' if extcmds: vcmd = extcmds[0] -@@ -1468,12 +2688,26 @@ class HistoryCommand(YumCommand): +@@ -1468,12 +2796,26 @@ class HistoryCommand(YumCommand): ret = self._hcmd_rollback(base, extcmds) elif vcmd == 'new': ret = self._hcmd_new(base, extcmds) @@ -159094,7 +162893,7 @@ index 4dcbea7..9c181ec 100644 vcmd = 'list' if extcmds: vcmd = extcmds[0] -@@ -1481,16 +2715,46 @@ class HistoryCommand(YumCommand): +@@ -1481,16 +2823,46 @@ class HistoryCommand(YumCommand): class CheckRpmdbCommand(YumCommand): @@ -159141,7 +162940,7 @@ index 4dcbea7..9c181ec 100644 chkcmd = 'all' if extcmds: chkcmd = extcmds -@@ -1505,19 +2769,57 @@ class CheckRpmdbCommand(YumCommand): +@@ -1505,19 +2877,57 @@ class CheckRpmdbCommand(YumCommand): return rc, ['%s %s' % (basecmd, chkcmd)] def needTs(self, base, basecmd, extcmds): @@ -159199,7 +162998,7 @@ index 4dcbea7..9c181ec 100644 if not extcmds: base.logger.critical(_("No saved transaction file specified.")) raise cli.CliError -@@ -1533,5 +2835,13 @@ class LoadTransactionCommand(YumCommand): +@@ -1533,5 +2943,13 @@ class LoadTransactionCommand(YumCommand): def needTs(self, base, basecmd, extcmds): @@ -159214,7 +163013,7 @@ index 4dcbea7..9c181ec 100644 return True diff --git a/yummain.py b/yummain.py -index 9f79f4f..c473df6 100755 +index 9f79f4f..648cf86 100755 --- a/yummain.py +++ b/yummain.py @@ -29,13 +29,13 @@ from yum import Errors @@ -159234,21 +163033,19 @@ index 9f79f4f..c473df6 100755 yum.misc.setup_locale(override_time=True) -@@ -102,15 +102,21 @@ def main(args): - return exFatal(e) +@@ -89,6 +89,23 @@ def main(args): + logger = logging.getLogger("yum.main") + verbose_logger = logging.getLogger("yum.verbose.main") - # Try to open the current directory to see if we have -- # read and write access. If not, chdir to / ++ # Try to open the current directory to see if we have + # read and execute access. If not, chdir to / - try: - f = open(".") - except IOError, e: - if e.errno == errno.EACCES: -- logger.critical(_('No read/write access in current directory, moving to /')) ++ try: ++ f = open(".") ++ except IOError, e: ++ if e.errno == errno.EACCES: + logger.critical(_('No read/execute access in current directory, moving to /')) - os.chdir("/") - else: -- close(f) ++ os.chdir("/") ++ else: + f.close() + try: + os.getcwd() @@ -159256,32 +163053,52 @@ index 9f79f4f..c473df6 100755 + if e.errno == errno.ENOENT: + logger.critical(_('No getcwd() access in current directory, moving to /')) + os.chdir("/") ++ + # our core object for the cli + base = cli.YumBaseCli() +@@ -101,17 +118,6 @@ def main(args): + except Errors.YumBaseError, e: + return exFatal(e) + +- # Try to open the current directory to see if we have +- # read and write access. If not, chdir to / +- try: +- f = open(".") +- except IOError, e: +- if e.errno == errno.EACCES: +- logger.critical(_('No read/write access in current directory, moving to /')) +- os.chdir("/") +- else: +- close(f) +- lockerr = "" while True: -@@ -120,16 +126,16 @@ def main(args): + try: +@@ -120,16 +126,14 @@ def main(args): if exception2msg(e) != lockerr: lockerr = exception2msg(e) logger.critical(lockerr) - if (e.errno not in (errno.EPERM, errno.EACCES) and - not base.conf.exit_on_lock): +- logger.critical(_("Another app is currently holding the yum lock; waiting for it to exit...")) +- tm = 0.1 +- if show_lock_owner(e.pid, logger): +- tm = 2 +- time.sleep(tm) +- elif e.errno in (errno.EPERM, errno.EACCES): + if e.errno in (errno.EPERM, errno.EACCES, errno.ENOSPC): -+ logger.critical(_("Can't create lock file; exiting")) -+ return 1 + logger.critical(_("Can't create lock file; exiting")) + return 1 + + if not base.conf.exit_on_lock: - logger.critical(_("Another app is currently holding the yum lock; waiting for it to exit...")) - tm = 0.1 - if show_lock_owner(e.pid, logger): - tm = 2 - time.sleep(tm) -- elif e.errno in (errno.EPERM, errno.EACCES): -- logger.critical(_("Can't create lock file; exiting")) -- return 1 ++ logger.critical(_("Another app is currently holding the yum lock; waiting for it to exit...")) ++ show_lock_owner(e.pid, logger) ++ time.sleep(2) else: logger.critical(_("Another app is currently holding the yum lock; exiting as configured by exit_on_lock")) return 1 -@@ -154,7 +160,7 @@ def main(args): +@@ -154,7 +158,7 @@ def main(args): for msg in resultmsgs: verbose_logger.log(logginglevels.INFO_2, '%s', msg) if unlock(): return 200 @@ -159290,7 +163107,7 @@ index 9f79f4f..c473df6 100755 elif result == 1: # Fatal error for msg in resultmsgs: -@@ -193,7 +199,7 @@ def main(args): +@@ -193,7 +197,7 @@ def main(args): if result == 0: # Normal exit if unlock(): return 200 @@ -159299,7 +163116,7 @@ index 9f79f4f..c473df6 100755 elif result == 1: # Fatal error for msg in resultmsgs: -@@ -238,16 +244,27 @@ def main(args): +@@ -238,16 +242,27 @@ def main(args): rpmdb_warn_checks() return_code = result if base._ts_save_file: @@ -159329,7 +163146,7 @@ index 9f79f4f..c473df6 100755 import hotshot.stats fn = os.path.expanduser("~/yum.prof") prof = hotshot.Profile(fn) -@@ -257,6 +274,11 @@ def hotshot(func, *args, **kwargs): +@@ -257,6 +272,11 @@ def hotshot(func, *args, **kwargs): return rc def cprof(func, *args, **kwargs): @@ -159341,7 +163158,7 @@ index 9f79f4f..c473df6 100755 import cProfile, pstats fn = os.path.expanduser("~/yum.prof") prof = cProfile.Profile() -@@ -266,6 +288,10 @@ def cprof(func, *args, **kwargs): +@@ -266,6 +286,10 @@ def cprof(func, *args, **kwargs): return rc def print_stats(stats): @@ -159352,7 +163169,7 @@ index 9f79f4f..c473df6 100755 stats.strip_dirs() stats.sort_stats('time', 'calls') stats.print_stats(20) -@@ -273,7 +299,14 @@ def print_stats(stats): +@@ -273,7 +297,14 @@ def print_stats(stats): stats.print_stats(40) def user_main(args, exit_code=False): diff --git a/yum-completion-helper.patch b/yum-completion-helper.patch new file mode 100644 index 0000000..44e6c73 --- /dev/null +++ b/yum-completion-helper.patch @@ -0,0 +1,11 @@ +diff -up yum-3.4.3/completion-helper.py.old yum-3.4.3/completion-helper.py +--- yum-3.4.3/completion-helper.py.old 2012-08-22 17:00:47.444104234 +0200 ++++ yum-3.4.3/completion-helper.py 2012-08-22 17:00:57.954647525 +0200 +@@ -70,6 +70,7 @@ def get_pattern(extcmds): + + def main(args): + base = cli.YumBaseCli() ++ base.setCacheDir = lambda *x: True # use the system cachedir + base.yum_cli_commands.clear() + base.registerCommand(GroupsCompletionCommand()) + base.registerCommand(ListCompletionCommand()) diff --git a/yum-ppc64-preferred.patch b/yum-ppc64-preferred.patch index 6c84924..3eaae57 100644 --- a/yum-ppc64-preferred.patch +++ b/yum-ppc64-preferred.patch @@ -2,8 +2,8 @@ diff -ru yum-3.4.3-orig/rpmUtils/arch.py yum-3.4.3/rpmUtils/arch.py --- yum-3.4.3-orig/rpmUtils/arch.py 2011-06-28 17:01:10.009680846 -0400 +++ yum-3.4.3/rpmUtils/arch.py 2011-06-28 17:01:31.849916539 -0400 @@ -3,7 +3,7 @@ - import os - import rpm + import ctypes + import struct -_ppc64_native_is_best = False +_ppc64_native_is_best = True diff --git a/yum.spec b/yum.spec index 639ca18..d8ea395 100644 --- a/yum.spec +++ b/yum.spec @@ -18,7 +18,7 @@ Summary: RPM package installer/updater/manager Name: yum Version: 3.4.3 -Release: 24%{?dist} +Release: 31%{?dist} License: GPLv2+ Group: System Environment/Base Source0: http://yum.baseurl.org/download/3.4/%{name}-%{version}.tar.gz @@ -30,8 +30,10 @@ Patch5: geode-arch.patch Patch6: yum-HEAD.patch Patch7: yum-ppc64-preferred.patch Patch8: BZ-803346-no-only-update.patch -Patch9: arm-arch-detection.patch +Patch9: BZ-908870-MD-files-bad.patch Patch20: yum-manpage-files.patch +Patch21: yum-completion-helper.patch +Patch22: ui_id-prevent-TB-on-invalid-repos-with-no-URLs.patch URL: http://yum.baseurl.org/ BuildArchitectures: noarch @@ -56,6 +58,7 @@ Requires: python-sqlite Requires: python-urlgrabber >= 3.9.0-8 Requires: yum-metadata-parser >= 1.1.0 Requires: pygpgme +Requires: pyxattr Conflicts: rpm >= 5-0 # Zif is a re-implementation of yum in C, however: @@ -139,9 +142,11 @@ Install this package if you want auto yum updates nightly via cron. %patch6 -p1 %patch7 -p1 %patch8 -p1 -%patch9 -p1 %patch20 -p1 +%patch21 -p1 +%patch22 -p1 %patch1 -p1 +%patch9 -p1 %build make @@ -294,7 +299,7 @@ exit 0 %files cron %defattr(-,root,root) %doc COPYING -%config(noreplace) %{_sysconfdir}/cron.daily/yum-update.cron +%config(noreplace) %{_sysconfdir}/cron.daily/0yum-update.cron %config(noreplace) %{_sysconfdir}/cron.daily/yum-cleanup.cron %{_sysconfdir}/rc.d/init.d/yum-cron %{_sbindir}/yum-cron @@ -315,6 +320,45 @@ exit 0 %endif %changelog +* Tue Feb 19 2013 James Antill - 3.4.3-32 +- Fix non-removal of corrupt metadata files. BZ 908870. + +* Mon Jan 7 2013 Zdenek Pavlas - 3.4.3-31 +- ui_id: prevent TB on invalid repos with no URLs. BZ 870691. + +* Mon Nov 12 2012 Zdenek Pavlas - 3.4.3-30 +- update to HEAD as of Oct 16 2012 (just before --downloadonly + and yum-cron changes) +- Fixes BZ 832166, 826419, 854974, 858632, 856969, 861264, 864018, 864294, + 864643, 864717, 864717, 859202. + +* Tue Aug 28 2012 Zdenek Pavlas - 3.4.3-29 +- update to latest HEAD. +- new groups code +- lots of bugfixes + +* Mon Jun 25 2012 Zdenek Pavlas - 3.4.3-28 +- update to latest HEAD. +- No async downloading when --cacheonly. BZ 830523. +- compare mtime without sub second precision. BZ 831918 +- show_lock_owner: report errors if we fail. BZ 745281 +- quote uids to keep cachedir ascii-clean. BZ 832195 +- A solution to the obsoletes but don't provide problem. BZ 834530 +- completion-helper: use the system cachedir + +* Fri Jun 8 2012 James Antill - 3.4.3-27 +- update to latest HEAD. +- Fix for ppc64p7 detection. + +* Thu Jun 7 2012 Zdenek Pavlas - 3.4.3-26 +- update to latest HEAD +- more completion helper patches +- parallel downloading of packages and metadata +- revert a hack that probably caused BZ 829505 + +* Thu May 31 2012 Zdenek Pavlas - 3.4.3-25 +- backported completion-helper.py patches from HEAD, BZ 809469. + * Fri Apr 27 2012 James Antill - 3.4.3-24 - Add code for arm detection.