[yum-commits] Branch 'yum-3_2_X' - 10 commits - yum/__init__.py yum/packageSack.py yum/repos.py yum/sqlitesack.py yumcommands.py
James Antill
james at osuosl.org
Fri Jul 17 19:30:43 UTC 2009
yum/__init__.py | 97 ++++++++++------
yum/packageSack.py | 27 +++-
yum/repos.py | 21 +++
yum/sqlitesack.py | 307 +++++++++++++++++++++++++++++++++++++----------------
yumcommands.py | 133 ++++++++++++++++++++++
5 files changed, 449 insertions(+), 136 deletions(-)
New commits:
commit f21d1dd80dbb2ad350655c4fd3fe68c03028a71b
Author: James Antill <james at and.org>
Date: Fri Jul 17 15:25:27 2009 -0400
Use new pkgExcluder code to speed up cost excludes.
Add listEnabled() cache to repos.
Add _pkgtup2pkgs to sqlitesack.
yum list blah ... now only needs to consider "blah" for cost excludes.
yum list updates ... does everything (worst case), and still 25-30% faster.
diff --git a/yum/__init__.py b/yum/__init__.py
index af41e3f..e683860 100644
--- a/yum/__init__.py
+++ b/yum/__init__.py
@@ -68,6 +68,8 @@ from yum.i18n import to_unicode
import string
+from weakref import proxy as weakref
+
from urlgrabber.grabber import default_grabber
__version__ = '3.2.23'
@@ -100,6 +102,28 @@ class _YumPreBaseConf:
self.arch = None
self.releasever = None
+class _YumCostExclude:
+ """ This excludes packages that are in repos. of lower cost than the passed
+ repo. """
+
+ def __init__(self, repo, repos):
+ self.repo = weakref(repo)
+ self._repos = weakref(repos)
+
+ def __contains__(self, pkgtup):
+ # (n, a, e, v, r) = pkgtup
+ for repo in self._repos.listEnabled():
+ if repo.cost >= self.repo.cost:
+ break
+ # searchNevra is a bit slower, although more generic for repos.
+ # that don't use sqlitesack as the backend ... although they are
+ # probably screwed anyway.
+ #
+ # if repo.sack.searchNevra(n, e, v, r, a):
+ if pkgtup in repo.sack._pkgtup2pkgs:
+ return True
+ return False
+
class YumBase(depsolve.Depsolve):
"""This is a primary structure and base class. It houses the objects and
methods needed to perform most things in yum. It is almost an abstract
@@ -1094,45 +1118,30 @@ class YumBase(depsolve.Depsolve):
self.rpmdb.dropCachedData()
def costExcludePackages(self):
- """exclude packages if they have an identical package in another repo
- and their repo.cost value is the greater one"""
+ """ Create an excluder for repos. with higher cost. Eg.
+ repo-A:cost=1 repo-B:cost=2 ... here we setup an excluder on repo-B
+ that looks for pkgs in repo-B."""
- # check to see if the cost per repo is anything other than equal
# if all the repo.costs are equal then don't bother running things
costs = {}
for r in self.repos.listEnabled():
- costs[r.cost] = 1
+ costs.setdefault(r.cost, []).append(r)
- if len(costs) <= 1: # if all of our costs are the same then return
+ if len(costs) <= 1:
return
-
- def _sort_by_cost(a, b):
- if a.repo.cost < b.repo.cost:
- return -1
- if a.repo.cost == b.repo.cost:
- return 0
- if a.repo.cost > b.repo.cost:
- return 1
-
- pkgdict = {}
- for po in self.pkgSack:
- if not pkgdict.has_key(po.pkgtup):
- pkgdict[po.pkgtup] = []
- pkgdict[po.pkgtup].append(po)
-
- for pkgs in pkgdict.values():
- if len(pkgs) == 1:
- continue
-
- pkgs.sort(_sort_by_cost)
- lowcost = pkgs[0].repo.cost
- #print '%s : %s : %s' % (pkgs[0], pkgs[0].repo, pkgs[0].repo.cost)
- for pkg in pkgs[1:]:
- if pkg.repo.cost > lowcost:
- msg = _('excluding for cost: %s from %s') % (pkg, pkg.repo.id)
- self.verbose_logger.log(logginglevels.DEBUG_3, msg)
- pkg.repo.sack.delPackage(pkg)
-
+
+ done = False
+ exid = "yum.costexcludes"
+ orepos = []
+ for cost in sorted(costs):
+ if done: # Skip the first one, as they have lowest cost so are good.
+ for repo in costs[cost]:
+ print "JDBG:", repo, cost, len(orepos)
+ yce = _YumCostExclude(repo, self.repos)
+ repo.sack.addPackageExcluder(repo.id, exid,
+ 'exclude.pkgtup.in', yce)
+ orepos.extend(costs[cost])
+ done = True
def excludePackages(self, repo=None):
"""removes packages from packageSacks based on global exclude lists,
diff --git a/yum/repos.py b/yum/repos.py
index 5d9a343..ac3e197 100644
--- a/yum/repos.py
+++ b/yum/repos.py
@@ -59,6 +59,10 @@ class RepoStorage:
self.gpg_import_func = _wrap_ayum_getKeyForRepo(ayum)
self.confirm_func = None
+ # This allow listEnabled() to be O(1) most of the time.
+ self._cache_enabled_repos = []
+ self.quick_enable_disable = {}
+
def doSetup(self, thisrepo = None):
self.ayum.plugins.run('prereposetup')
@@ -92,7 +96,10 @@ class RepoStorage:
if self.repos.has_key(repoobj.id):
raise Errors.DuplicateRepoError, 'Repository %s is listed more than once in the configuration' % (repoobj.id)
self.repos[repoobj.id] = repoobj
-
+ if hasattr(repoobj, 'quick_enable_disable'):
+ repoobj.quick_enable_disable = self.quick_enable_disable
+ else:
+ self._cache_enabled_repos = None
def delete(self, repoid):
if self.repos.has_key(repoid):
@@ -163,12 +170,21 @@ class RepoStorage:
def listEnabled(self):
"""return list of enabled repo objects"""
+
+ if (self._cache_enabled_repos is not None and
+ not self.quick_enable_disable):
+ return self._cache_enabled_repos
+
returnlist = []
for repo in self.repos.values():
if repo.isEnabled():
returnlist.append(repo)
returnlist.sort()
+
+ if self._cache_enabled_repos is not None:
+ self._cache_enabled_repos = returnlist
+ self.quick_enable_disable.clear()
return returnlist
def listGroupsEnabled(self):
@@ -266,6 +282,7 @@ class Repository:
def __init__(self, repoid):
self.id = repoid
+ self.quick_enable_disable = {}
self.disable()
def __cmp__(self, other):
@@ -303,9 +320,11 @@ class Repository:
def enable(self):
self.setAttribute('enabled', 1)
+ self.quick_enable_disable[self.id] = True
def disable(self):
self.setAttribute('enabled', 0)
+ self.quick_enable_disable[self.id] = False
def getExcludePkgList(self):
excludeList = self.getAttribute('exclude')
diff --git a/yum/sqlitesack.py b/yum/sqlitesack.py
index 3469867..7f61190 100644
--- a/yum/sqlitesack.py
+++ b/yum/sqlitesack.py
@@ -421,6 +421,7 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
}
self._key2pkg = {}
self._pkgname2pkgkeys = {}
+ self._pkgtup2pkgs = {}
self._pkgnames_loaded = set()
self._arch_allowed = None
self._pkgExcluder = []
@@ -527,7 +528,11 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
self._excludes.add((repo, pkgKey))
# Don't keep references around, just wastes memory.
if repo in self._key2pkg:
- self._key2pkg[repo].pop(pkgKey, 0)
+ po = self._key2pkg[repo].pop(pkgKey, None)
+ if po is not None: # Will also be in the pkgtup2pkgs cache...
+ pos = self._pkgtup2pkgs[po.pkgtup]
+ pos = filter(lambda x: id(x) == id(po), pos)
+ self._pkgtup2pkgs[po.pkgtup] = pos
# Remove a package
# Because we don't want to remove a package from the database we just
@@ -702,7 +707,9 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
raise Errors.RepoError, msg
if exclude and self._pkgExcludedRKD(repo, pkgKey, data):
return None
- self._key2pkg[repo][pkgKey] = self.pc(repo, data)
+ po = self.pc(repo, data)
+ self._key2pkg[repo][pkgKey] = po
+ self._pkgtup2pkgs.setdefault(po.pkgtup, []).append(po)
pkgkeys = self._pkgname2pkgkeys[repo].setdefault(data['name'], [])
pkgkeys.append(pkgKey)
return self._key2pkg[repo][pkgKey]
@@ -717,6 +724,7 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
if data['pkgKey'] not in self._key2pkg.get(repo, {}):
po = self.pc(repo, data)
self._key2pkg[repo][pkgKey] = po
+ self._pkgtup2pkgs.setdefault(po.pkgtup, []).append(po)
pkgkeys = self._pkgname2pkgkeys[repo].setdefault(data['name'], [])
pkgkeys.append(pkgKey)
return self._key2pkg[repo][data['pkgKey']]
commit f43f75bcb2943efcd6a38e15f258405328595ab2
Author: James Antill <james at and.org>
Date: Thu Jul 16 15:38:47 2009 -0400
Use cleaned pkgobjlist for length as well as returnPackages
diff --git a/yum/sqlitesack.py b/yum/sqlitesack.py
index 5d74a41..3469867 100644
--- a/yum/sqlitesack.py
+++ b/yum/sqlitesack.py
@@ -441,6 +441,18 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
sql = "SELECT count(pkgId) FROM packages"
return self._sql_MD('primary', repo, sql).fetchone()[0]
+ def _clean_pkgobjlist(self):
+ """ If the pkgobjlist is dirty (possible pkgs on it which are excluded)
+ then clean it, and return the clean list. """
+ assert hasattr(self, 'pkgobjlist')
+
+ if self._pkgobjlist_dirty:
+ pol = filter(lambda x: not self._pkgExcluded(x), self.pkgobjlist)
+ self.pkgobjlist = pol
+ self._pkgobjlist_dirty = False
+
+ return self.pkgobjlist
+
def __len__(self):
# First check if everything is excluded
all_excluded = True
@@ -451,12 +463,12 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
if all_excluded:
return 0
+ if hasattr(self, 'pkgobjlist'):
+ return len(self._clean_pkgobjlist())
+
exclude_num = 0
for repo in self.excludes:
exclude_num += len(self.excludes[repo])
- if hasattr(self, 'pkgobjlist') and not self._pkgobjlist_dirty:
- return len(self.pkgobjlist) - exclude_num
-
pkg_num = 0
sql = "SELECT count(pkgId) FROM packages"
for repo in self.primarydb:
@@ -1507,11 +1519,7 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
internal_pkgoblist = hasattr(self, 'pkgobjlist')
if internal_pkgoblist:
- if self._pkgobjlist_dirty:
- pol = filter(lambda x: not self._pkgExcluded(x),self.pkgobjlist)
- self.pkgobjlist = pol
- self._pkgobjlist_dirty = False
- pkgobjlist = self.pkgobjlist
+ pkgobjlist = self._clean_pkgobjlist()
else:
pkgobjlist = self._buildPkgObjList(repoid, patterns, ignore_case)
commit 36f74ef814d2c2c14edabbbd6deb78b45470cdae
Author: James Antill <james at and.org>
Date: Thu Jul 16 15:34:39 2009 -0400
Add excluderid to make some things easier/nicer with repos. going on/off
post init. ... add some explanation in the doc comments for
addPackageExcluder().
diff --git a/yum/__init__.py b/yum/__init__.py
index c0402a1..af41e3f 100644
--- a/yum/__init__.py
+++ b/yum/__init__.py
@@ -1150,14 +1150,19 @@ class YumBase(depsolve.Depsolve):
return
excludelist = self.conf.exclude
repoid = None
+ exid_beg = 'yum.excludepkgs'
else:
if repo.id in self.conf.disable_excludes:
return
excludelist = repo.getExcludePkgList()
repoid = repo.id
+ exid_beg = 'yum.excludepkgs.' + repoid
+ count = 0
for match in excludelist:
- self.pkgSack.addPackageExcluder(repoid, 'exclude.match', match)
+ count += 1
+ exid = "%s.%u" % (exid_beg, count)
+ self.pkgSack.addPackageExcluder(repoid, exid,'exclude.match', match)
def includePackages(self, repo):
"""removes packages from packageSacks based on list of packages, to include.
@@ -1171,10 +1176,15 @@ class YumBase(depsolve.Depsolve):
# includepkgs actually means "exclude everything that doesn't match".
# So we mark everything, then wash those we want to keep and then
# exclude everything that is marked.
- self.pkgSack.addPackageExcluder(repo.id, 'mark.washed')
+ exid = "yum.includepkgs.1"
+ self.pkgSack.addPackageExcluder(repo.id, exid, 'mark.washed')
+ count = 0
for match in includelist:
- self.pkgSack.addPackageExcluder(repo.id, 'wash.match', match)
- self.pkgSack.addPackageExcluder(repo.id, 'exclude.marked')
+ count += 1
+ exid = "%s.%u" % ("yum.includepkgs.2", count)
+ self.pkgSack.addPackageExcluder(repo.id, exid, 'wash.match', match)
+ exid = "yum.includepkgs.3"
+ self.pkgSack.addPackageExcluder(repo.id, exid, 'exclude.marked')
def doLock(self, lockfile = YUM_PID_FILE):
"""perform the yum locking, raise yum-based exceptions, not OSErrors"""
diff --git a/yum/packageSack.py b/yum/packageSack.py
index 633100d..b71356a 100644
--- a/yum/packageSack.py
+++ b/yum/packageSack.py
@@ -172,8 +172,14 @@ class PackageSackBase(object):
"""return list of all packages"""
raise NotImplementedError()
- def addPackageExcluder(self, repoid, excluder, *args):
- """exclude packages, for a variety of reasons"""
+ def addPackageExcluder(self, repoid, excluderid, excluder, *args):
+ """ Add an "excluder" for all packages in the repo/sack. Can basically
+ do anything based on nevra, changes lots of exclude decisions from
+ "preload package; test; delPackage" into "load excluder".
+ Excluderid is used so the caller doesn't have to track
+ "have I loaded the excluder for this repo.", it's probably only
+ useful when repoid is None ... if it turns out utterly worthless
+ then it's still not a huge wart. """
raise NotImplementedError()
def simpleVersion(self):
@@ -444,12 +450,19 @@ class MetaSack(PackageSackBase):
return self.sacks[repoid].returnPackages(patterns=patterns,
ignore_case=ignore_case)
- def addPackageExcluder(self, repoid, excluder, *args):
- """exclude packages, for a variety of reasons"""
+ def addPackageExcluder(self, repoid, excluderid, excluder, *args):
+ """ Add an "excluder" for all packages in the repo/sack. Can basically
+ do anything based on nevra, changes lots of exclude decisions from
+ "preload package; test; delPackage" into "load excluder".
+ Excluderid is used so the caller doesn't have to track
+ "have I loaded the excluder for this repo.", it's probably only
+ useful when repoid is None ... if it turns out utterly worthless
+ then it's still not a huge wart. """
if not repoid:
- return self._computeAggregateListResult("addPackageExcluder",
- None, excluder, *args)
- return self.sacks[repoid].addPackageExcluder(None, excluder, *args)
+ calr = self._computeAggregateListResult
+ return calr("addPackageExcluder", None, excluderid, excluder, *args)
+ return self.sacks[repoid].addPackageExcluder(None,
+ excluderid,excluder, *args)
def returnNewestByNameArch(self, naTup=None,
patterns=None, ignore_case=False):
diff --git a/yum/sqlitesack.py b/yum/sqlitesack.py
index 984c470..5d74a41 100644
--- a/yum/sqlitesack.py
+++ b/yum/sqlitesack.py
@@ -424,6 +424,7 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
self._pkgnames_loaded = set()
self._arch_allowed = None
self._pkgExcluder = []
+ self._pkgExcludeIds = {}
self._pkgobjlist_dirty = False
@catchSqliteException
@@ -494,6 +495,9 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
self._excludes = set()
self._exclude_whitelist = set()
self._all_excludes = {}
+ self._pkgExcluder = []
+ self._pkgExcludeIds = {}
+ self._pkgobjlist_dirty = False
yumRepo.YumPackageSack.close(self)
@@ -630,7 +634,17 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
Takes a package object. '''
return self._pkgExcludedRKT(po.repo, po.pkgKey, po.pkgtup)
- def addPackageExcluder(self, repoid, excluder, *args):
+ def addPackageExcluder(self, repoid, excluderid, excluder, *args):
+ """ Add an "excluder" for all packages in the repo/sack. Can basically
+ do anything based on nevra, changes lots of exclude decisions from
+ "preload package; test; delPackage" into "load excluder".
+ Excluderid is used so the caller doesn't have to track
+ "have I loaded the excluder for this repo.", it's probably only
+ useful when repoid is None ... if it turns out utterly worthless
+ then it's still not a huge wart. """
+ if excluderid is not None and excluderid in self._pkgExcludeIds:
+ return
+
match = None
regexp_match = None
if False: pass
@@ -655,6 +669,8 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
# or it does nothing.
# self._pkgobjlist_dirty = True
self._pkgExcluder.append((repoid, excluder, match, regexp_match))
+ if excluderid is not None:
+ self._pkgExcludeIds[excluderid] = len(self._pkgExcluder)
def _packageByKey(self, repo, pkgKey, exclude=True):
""" Lookup a pkg by it's pkgKey, if we don't have it load it """
commit 12eb1aa0566abcc4eb8897823a664ed592d41247
Author: James Antill <james at and.org>
Date: Wed Jul 15 23:12:10 2009 -0400
Fix moved code for n, now data["n"], in excluder code
diff --git a/yum/sqlitesack.py b/yum/sqlitesack.py
index 613dd19..984c470 100644
--- a/yum/sqlitesack.py
+++ b/yum/sqlitesack.py
@@ -123,13 +123,13 @@ def _excluder_match(excluder, match, regexp_match, data, e,v,r,a):
elif excluder == 'nevr.eq':
if 'nevr' not in data:
- data['nevr'] = '%s-%s:%s-%s' % (n, e, v, r)
+ data['nevr'] = '%s-%s:%s-%s' % (data['n'], e, v, r)
if match == data['nevr']:
return True
elif excluder in ('nevra.eq', 'nevra.match'):
if 'nevra' not in data:
- data['nevra'] = '%s-%s:%s-%s.%s' % (n, e, v, r, a)
+ data['nevra'] = '%s-%s:%s-%s.%s' % (data['n'], e, v, r, a)
if _parse_pkg_n(match, regexp_match, data['nevra']):
return True
@@ -139,25 +139,25 @@ def _excluder_match(excluder, match, regexp_match, data, e,v,r,a):
elif excluder == 'nevr.in':
if 'nevr' not in data:
- data['nevr'] = '%s-%s:%s-%s' % (n, e, v, r)
+ data['nevr'] = '%s-%s:%s-%s' % (data['n'], e, v, r)
if data['nevr'] in match:
return True
elif excluder == 'nevra.in':
if 'nevra' not in data:
- data['nevra'] = '%s-%s:%s-%s.%s' % (n, e, v, r, a)
+ data['nevra'] = '%s-%s:%s-%s.%s' % (data['n'], e, v, r, a)
if data['nevra'] in match:
return True
elif excluder == 'pkgtup.eq':
if 'pkgtup' not in data:
- data['pkgtup'] = (n, a, e, v, r)
+ data['pkgtup'] = (data['n'], a, e, v, r)
if match == data['pkgtup']:
return True
elif excluder == 'pkgtup.in':
if 'pkgtup' not in data:
- data['pkgtup'] = (n, a, e, v, r)
+ data['pkgtup'] = (data['n'], a, e, v, r)
if data['pkgtup'] in match:
return True
commit 4338a70aea599986bf380a3a0883baafd8143480
Author: James Antill <james at and.org>
Date: Wed Jul 15 16:23:17 2009 -0400
Add nevr and pkgtup to pkgExcluder
diff --git a/yum/sqlitesack.py b/yum/sqlitesack.py
index 2e2da3c..613dd19 100644
--- a/yum/sqlitesack.py
+++ b/yum/sqlitesack.py
@@ -121,6 +121,12 @@ def _excluder_match(excluder, match, regexp_match, data, e,v,r,a):
if _parse_pkg_n(match, regexp_match, a):
return True
+ elif excluder == 'nevr.eq':
+ if 'nevr' not in data:
+ data['nevr'] = '%s-%s:%s-%s' % (n, e, v, r)
+ if match == data['nevr']:
+ return True
+
elif excluder in ('nevra.eq', 'nevra.match'):
if 'nevra' not in data:
data['nevra'] = '%s-%s:%s-%s.%s' % (n, e, v, r, a)
@@ -131,12 +137,30 @@ def _excluder_match(excluder, match, regexp_match, data, e,v,r,a):
if data['n'] in match:
return True
+ elif excluder == 'nevr.in':
+ if 'nevr' not in data:
+ data['nevr'] = '%s-%s:%s-%s' % (n, e, v, r)
+ if data['nevr'] in match:
+ return True
+
elif excluder == 'nevra.in':
if 'nevra' not in data:
data['nevra'] = '%s-%s:%s-%s.%s' % (n, e, v, r, a)
if data['nevra'] in match:
return True
+ elif excluder == 'pkgtup.eq':
+ if 'pkgtup' not in data:
+ data['pkgtup'] = (n, a, e, v, r)
+ if match == data['pkgtup']:
+ return True
+
+ elif excluder == 'pkgtup.in':
+ if 'pkgtup' not in data:
+ data['pkgtup'] = (n, a, e, v, r)
+ if data['pkgtup'] in match:
+ return True
+
elif excluder == 'marked':
if data['marked']:
return True
commit 434679afead379a530d209af2bf3439d6c5ed0ac
Author: James Antill <james at and.org>
Date: Wed Jul 15 16:19:09 2009 -0400
Fix delPackage() after pkgobjlist has been set
diff --git a/yum/sqlitesack.py b/yum/sqlitesack.py
index 787dc85..2e2da3c 100644
--- a/yum/sqlitesack.py
+++ b/yum/sqlitesack.py
@@ -400,6 +400,7 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
self._pkgnames_loaded = set()
self._arch_allowed = None
self._pkgExcluder = []
+ self._pkgobjlist_dirty = False
@catchSqliteException
def _sql_MD(self, MD, repo, sql, *args):
@@ -428,7 +429,7 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
exclude_num = 0
for repo in self.excludes:
exclude_num += len(self.excludes[repo])
- if hasattr(self, 'pkgobjlist'):
+ if hasattr(self, 'pkgobjlist') and not self._pkgobjlist_dirty:
return len(self.pkgobjlist) - exclude_num
pkg_num = 0
@@ -444,6 +445,7 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
del self._memoize_provides
if hasattr(self, 'pkgobjlist'):
del self.pkgobjlist
+ self._pkgobjlist_dirty = False
self._key2pkg = {}
self._pkgname2pkgkeys = {}
self._pkgnames_loaded = set()
@@ -483,6 +485,9 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
''' Exclude a package so that _pkgExcluded*() knows it's gone.
Note that this doesn't update self.exclude. '''
self._excludes.add((repo, pkgKey))
+ # Don't keep references around, just wastes memory.
+ if repo in self._key2pkg:
+ self._key2pkg[repo].pop(pkgKey, 0)
# Remove a package
# Because we don't want to remove a package from the database we just
@@ -494,6 +499,7 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
if (obj.repo, obj.pkgKey) in self._exclude_whitelist:
self._exclude_whitelist.discard((obj.repo, obj.pkgKey))
self._delPackageRK(obj.repo, obj.pkgKey)
+ self._pkgobjlist_dirty = True
def _delAllPackages(self, repo):
""" Exclude all packages from the repo. """
@@ -621,6 +627,9 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
assert len(args) == 0
elif excluder.endswith('.washed'):
assert len(args) == 0
+ # Really need to do this, need to cleanup pkgExcluder first though
+ # or it does nothing.
+ # self._pkgobjlist_dirty = True
self._pkgExcluder.append((repoid, excluder, match, regexp_match))
def _packageByKey(self, repo, pkgKey, exclude=True):
@@ -1458,6 +1467,10 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
internal_pkgoblist = hasattr(self, 'pkgobjlist')
if internal_pkgoblist:
+ if self._pkgobjlist_dirty:
+ pol = filter(lambda x: not self._pkgExcluded(x),self.pkgobjlist)
+ self.pkgobjlist = pol
+ self._pkgobjlist_dirty = False
pkgobjlist = self.pkgobjlist
else:
pkgobjlist = self._buildPkgObjList(repoid, patterns, ignore_case)
@@ -1468,16 +1481,15 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
unique='repo-pkgkey')
pkgobjlist = pkgobjlist[0] + pkgobjlist[1]
- if True: # NOTE: Can't unexclude things...
+ # Can't unexclude things, and new excludes are done above...
+ if repoid is None:
if internal_pkgoblist:
pkgobjlist = pkgobjlist[:]
return pkgobjlist
returnList = []
for po in pkgobjlist:
- if repoid is not None and repoid != po.repoid:
- continue
- if self._pkgExcluded(po):
+ if repoid != po.repoid:
continue
returnList.append(po)
commit 36b366b33de1b2fdf39af14f4f17445452658780
Author: James Antill <james at and.org>
Date: Thu Apr 16 18:23:14 2009 -0400
Cache pkg names, faster for long lived YumBases.
Call searchNames() from searchNevra()
Call searchNames() from returnPackages() (if we can).
diff --git a/yum/sqlitesack.py b/yum/sqlitesack.py
index 61e60a2..787dc85 100644
--- a/yum/sqlitesack.py
+++ b/yum/sqlitesack.py
@@ -396,6 +396,8 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
'requires' : { },
}
self._key2pkg = {}
+ self._pkgname2pkgkeys = {}
+ self._pkgnames_loaded = set()
self._arch_allowed = None
self._pkgExcluder = []
@@ -443,6 +445,8 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
if hasattr(self, 'pkgobjlist'):
del self.pkgobjlist
self._key2pkg = {}
+ self._pkgname2pkgkeys = {}
+ self._pkgnames_loaded = set()
self._search_cache = {
'provides' : { },
'requires' : { },
@@ -498,6 +502,8 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
del self.excludes[repo]
if repo in self._key2pkg:
del self._key2pkg[repo]
+ if repo in self._pkgname2pkgkeys:
+ del self._pkgname2pkgkeys[repo]
def _excluded(self, repo, pkgId):
if repo in self._all_excludes:
@@ -625,6 +631,7 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
if not self._key2pkg.has_key(repo):
self._key2pkg[repo] = {}
+ self._pkgname2pkgkeys[repo] = {}
if not self._key2pkg[repo].has_key(pkgKey):
sql = "SELECT pkgKey, pkgId, name, epoch, version, release, arch " \
"FROM packages WHERE pkgKey = ?"
@@ -635,17 +642,39 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
if exclude and self._pkgExcludedRKD(repo, pkgKey, data):
return None
self._key2pkg[repo][pkgKey] = self.pc(repo, data)
+ pkgkeys = self._pkgname2pkgkeys[repo].setdefault(data['name'], [])
+ pkgkeys.append(pkgKey)
return self._key2pkg[repo][pkgKey]
def _packageByKeyData(self, repo, pkgKey, data, exclude=True):
""" Like _packageByKey() but we already have the data for .pc() """
if exclude and self._pkgExcludedRKD(repo, pkgKey, data):
return None
+ if repo not in self._key2pkg:
+ self._key2pkg[repo] = {}
+ self._pkgname2pkgkeys[repo] = {}
if data['pkgKey'] not in self._key2pkg.get(repo, {}):
po = self.pc(repo, data)
- self._key2pkg.setdefault(repo, {})[pkgKey] = po
+ self._key2pkg[repo][pkgKey] = po
+ pkgkeys = self._pkgname2pkgkeys[repo].setdefault(data['name'], [])
+ pkgkeys.append(pkgKey)
return self._key2pkg[repo][data['pkgKey']]
+ def _packagesByName(self, pkgname):
+ """ Load all pkgnames from cache, with a given name. """
+ ret = []
+ for repo in self.primarydb:
+ pkgkeys = self._pkgname2pkgkeys.get(repo, {}).get(pkgname, [])
+ if not pkgkeys:
+ continue
+
+ for pkgkey in pkgkeys:
+ pkg = self._packageByKey(repo, pkgkey)
+ if pkg is None:
+ continue
+ ret.append(pkg)
+ return ret
+
def addDict(self, repo, datatype, dataobj, callback=None):
if self.added.has_key(repo):
if datatype in self.added[repo]:
@@ -1098,23 +1127,25 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
if self._skip_all():
return []
+ loaded_all_names = hasattr(self, 'pkgobjlist')
returnList = []
- if hasattr(self, 'pkgobjlist'):
- names = set(names)
- for po in self.pkgobjlist:
- if po.name not in names:
- continue
- if self._pkgExcluded(po):
- continue
- returnList.append(po)
+ user_names = set(names)
+ names = []
+ for pkgname in user_names:
+ if loaded_all_names or pkgname in self._pkgnames_loaded:
+ returnList.extend(self._packagesByName(pkgname))
+ else:
+ names.append(pkgname)
+
+ if not names:
return returnList
max_entries = constants.PATTERNS_INDEXED_MAX
if len(names) > max_entries:
- returnList = set() # Unique
+ # Unique is done at user_names time, above.
for names in seq_max_split(names, max_entries):
- returnList.update(self.searchNames(names))
- return list(returnList)
+ returnList.extend(self.searchNames(names))
+ return returnList
pat_sqls = []
qsql = """select pkgId,pkgKey,name,epoch,version,release,arch
@@ -1125,10 +1156,13 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
for (repo, cache) in self.primarydb.items():
cur = cache.cursor()
- executeSQL(cur, qsql, list(names))
+ executeSQL(cur, qsql, names)
self._sql_pkgKey2po(repo, cur, returnList, have_data=True)
+ # Mark all the processed pkgnames as fully loaded
+ self._pkgnames_loaded.update([name for name in names])
+
return returnList
@catchSqliteException
@@ -1364,11 +1398,15 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
patterns = self._sql_esc_glob(patterns)
else:
tmp = []
+ need_glob = False
for pat in patterns:
if misc.re_glob(pat):
tmp.append((pat, 'glob'))
+ need_glob = True
else:
tmp.append((pat, '='))
+ if not need_full and not need_glob and patterns:
+ return self.searchNames(patterns)
patterns = tmp
for (repo,cache) in self.primarydb.items():
@@ -1401,8 +1439,13 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
if po is None:
continue
returnList.append(po)
- if not patterns:
+ if not patterns and repoid is None:
self.pkgobjlist = returnList
+ self._pkgnames_loaded = set() # Save memory
+ if not need_full and repoid is None:
+ # Mark all the processed pkgnames as fully loaded
+ self._pkgnames_loaded.update([po.name for po in returnList])
+
return returnList
def returnPackages(self, repoid=None, patterns=None, ignore_case=False):
@@ -1448,6 +1491,18 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
returnList = []
+ if name: # Almost always true...
+ for pkg in self.searchNames(names=[name]):
+ match = True
+ for (col, var) in [('epoch', epoch), ('version', ver),
+ ('arch', arch), ('release', rel)]:
+ if var and getattr(pkg, col) != var:
+ match = False
+ break
+ if match:
+ returnList.append(pkg)
+ return returnList
+
# make sure some dumbass didn't pass us NOTHING to search on
empty = True
for arg in (name, epoch, ver, rel, arch):
commit 5f83b3409061b7ff40a361b59291108c637cc288
Author: James Antill <james at and.org>
Date: Tue Jul 14 18:11:09 2009 -0400
Use mark/wash in includepkgs to allow further include/exclude(s)
diff --git a/yum/__init__.py b/yum/__init__.py
index 221d4d7..c0402a1 100644
--- a/yum/__init__.py
+++ b/yum/__init__.py
@@ -523,8 +523,8 @@ class YumBase(depsolve.Depsolve):
if repos == 'enabled':
repos = self.repos.listEnabled()
for repo in repos:
- self.excludePackages(repo)
self.includePackages(repo)
+ self.excludePackages(repo)
self.plugins.run('exclude')
self._pkgSack.buildIndexes()
@@ -1168,9 +1168,13 @@ class YumBase(depsolve.Depsolve):
if len(includelist) == 0:
return
+ # includepkgs actually means "exclude everything that doesn't match".
+ # So we mark everything, then wash those we want to keep and then
+ # exclude everything that is marked.
+ self.pkgSack.addPackageExcluder(repo.id, 'mark.washed')
for match in includelist:
- self.pkgSack.addPackageExcluder(repo.id, 'include.match', match)
- self.pkgSack.addPackageExcluder(repo.id, 'exclude.*')
+ self.pkgSack.addPackageExcluder(repo.id, 'wash.match', match)
+ self.pkgSack.addPackageExcluder(repo.id, 'exclude.marked')
def doLock(self, lockfile = YUM_PID_FILE):
"""perform the yum locking, raise yum-based exceptions, not OSErrors"""
commit 2ab9442f1d93f1dd8177118c26e5c5415945f4f8
Author: James Antill <james at and.org>
Date: Tue Jul 14 17:57:41 2009 -0400
Combine include/exclude tests in pkgExcluder, add mark/wash ops
diff --git a/yum/sqlitesack.py b/yum/sqlitesack.py
index 013156e..61e60a2 100644
--- a/yum/sqlitesack.py
+++ b/yum/sqlitesack.py
@@ -107,6 +107,54 @@ def _parse_pkg(match, regexp_match, data, e,v,r,a):
return True
return False
+def _excluder_match(excluder, match, regexp_match, data, e,v,r,a):
+ if False: pass
+ elif excluder in ('eq', 'match'):
+ if _parse_pkg(match, regexp_match, data, e,v,r,a):
+ return True
+
+ elif excluder in ('name.eq', 'name.match'):
+ if _parse_pkg_n(match, regexp_match, data['n']):
+ return True
+
+ elif excluder in ('arch.eq', 'arch.match'):
+ if _parse_pkg_n(match, regexp_match, a):
+ return True
+
+ elif excluder in ('nevra.eq', 'nevra.match'):
+ if 'nevra' not in data:
+ data['nevra'] = '%s-%s:%s-%s.%s' % (n, e, v, r, a)
+ if _parse_pkg_n(match, regexp_match, data['nevra']):
+ return True
+
+ elif excluder == 'name.in':
+ if data['n'] in match:
+ return True
+
+ elif excluder == 'nevra.in':
+ if 'nevra' not in data:
+ data['nevra'] = '%s-%s:%s-%s.%s' % (n, e, v, r, a)
+ if data['nevra'] in match:
+ return True
+
+ elif excluder == 'marked':
+ if data['marked']:
+ return True
+
+ elif excluder == 'washed':
+ if not data['marked']:
+ return True
+
+ elif excluder == '*':
+ return True
+
+ else:
+ assert False, 'Bad excluder: ' + excluder
+ return None
+
+ return False
+
+
class YumAvailablePackageSqlite(YumAvailablePackage, PackageObject, RpmBase):
def __init__(self, repo, db_obj):
self.prco = { 'obsoletes': (),
@@ -484,7 +532,7 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
self._delPackageRK(repo, pkgKey)
return True
- data = {'n' : n.lower()}
+ data = {'n' : n.lower(), 'marked' : False}
e = e.lower()
v = v.lower()
r = r.lower()
@@ -494,75 +542,33 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
if repoid is not None and repoid != repo.id:
continue
- if False: pass
- elif excluder in ('exclude.eq', 'exclude.match'):
- if _parse_pkg(match, regexp_match, data, e,v,r,a):
- self._delPackageRK(repo, pkgKey)
- return True
-
- elif excluder in ('exclude.name.eq', 'exclude.name.match'):
- if _parse_pkg_n(match, regexp_match, data['n']):
- self._delPackageRK(repo, pkgKey)
- return True
-
- elif excluder in ('exclude.arch.eq', 'exclude.arch.match'):
- if _parse_pkg_n(match, regexp_match, a):
- self._delPackageRK(repo, pkgKey)
- return True
-
- elif excluder in ('exclude.nevra.eq', 'exclude.nevra.match'):
- if 'nevra' not in data:
- data['nevra'] = '%s-%s:%s-%s.%s' % (n, e, v, r, a)
- if _parse_pkg_n(match, regexp_match, data['nevra']):
- self._delPackageRK(repo, pkgKey)
- return True
-
- elif excluder == 'exclude.name.in':
- if data['n'] in match:
- self._delPackageRK(repo, pkgKey)
- return True
+ exSPLIT = excluder.split('.', 1)
+ if len(exSPLIT) != 2:
+ assert False, 'Bad excluder: ' + excluder
+ continue
- elif excluder == 'exclude.nevra.in':
- if 'nevra' not in data:
- data['nevra'] = '%s-%s:%s-%s.%s' % (n, e, v, r, a)
- if data['nevra'] in match:
+ exT, exM = exSPLIT
+ if False: pass
+ elif exT == 'exclude':
+ if _excluder_match(exM, match, regexp_match, data, e,v,r,a):
self._delPackageRK(repo, pkgKey)
return True
- elif excluder in ('include.eq', 'include.match'):
- if _parse_pkg(match, regexp_match, data, e,v,r,a):
+ elif exT == 'include':
+ if _excluder_match(exM, match, regexp_match, data, e,v,r,a):
break
- elif excluder in ('include.name.eq', 'include.name.match'):
- if _parse_pkg_n(match, regexp_match, data['n']):
- break
-
- elif excluder in ('include.arch.eq', 'include.arch.match'):
- if _parse_pkg_n(match, regexp_match, a):
- break
+ elif exT == 'mark':
+ if data['marked']:
+ pass # Speed opt. don't do matches we don't need to do.
+ elif _excluder_match(exM, match, regexp_match, data, e,v,r,a):
+ data['marked'] = True
- elif excluder in ('include.nevra.eq', 'include.nevra.match'):
- if 'nevra' not in data:
- data['nevra'] = '%s-%s:%s-%s.%s' % (n, e, v, r, a)
- if _parse_pkg_n(match, regexp_match, data['nevra']):
- break
-
- elif excluder == 'include.name.in':
- if data['n'] in match:
- break
-
- elif excluder == 'include.nevra.in':
- if 'nevra' not in data:
- data['nevra'] = '%s-%s:%s-%s.%s' % (n, e, v, r, a)
- if data['nevra'] in match:
- break
-
- elif excluder == 'exclude.*':
- self._delPackageRK(repo, pkgKey)
- return True
-
- elif excluder == 'include.*':
- break
+ elif exT == 'wash':
+ if not data['marked']:
+ pass # Speed opt. don't do matches we don't need to do.
+ elif _excluder_match(exM, match, regexp_match, data, e,v,r,a):
+ data['marked'] = False
else:
assert False, 'Bad excluder: ' + excluder
@@ -605,6 +611,10 @@ class YumSqlitePackageSack(yumRepo.YumPackageSack):
regexp_match = re.compile(fnmatch.translate(match)).match
elif excluder.endswith('.*'):
assert len(args) == 0
+ elif excluder.endswith('.marked'):
+ assert len(args) == 0
+ elif excluder.endswith('.washed'):
+ assert len(args) == 0
self._pkgExcluder.append((repoid, excluder, match, regexp_match))
def _packageByKey(self, repo, pkgKey, exclude=True):
commit b41e57b64f4b511412ad7c98fd7e230c3a4b0ab4
Author: James Antill <james at and.org>
Date: Sun Jul 12 23:45:52 2009 -0400
Add "help recent", so people can see what new commands are available
diff --git a/yumcommands.py b/yumcommands.py
index edaddfd..1227484 100644
--- a/yumcommands.py
+++ b/yumcommands.py
@@ -28,7 +28,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, utf8_text_fill, to_unicode
def checkRootUID(base):
"""
@@ -120,7 +120,10 @@ def checkShellArg(base, basecmd, extcmds):
raise cli.CliError
class YumCommand:
-
+
+ # Epoch seconds. Use: date -d '2006-09-26 13:24:58 +0000' +'%s' ... etc.
+ created = 0
+
def __init__(self):
self.done_command_once = False
@@ -160,6 +163,9 @@ class YumCommand:
return True
class InstallCommand(YumCommand):
+
+ created = 1159277098
+
def getNames(self):
return ['install']
@@ -182,6 +188,9 @@ class InstallCommand(YumCommand):
return 1, [str(e)]
class UpdateCommand(YumCommand):
+
+ created = 1159277098
+
def getNames(self):
return ['update']
@@ -234,6 +243,9 @@ def _list_cmd_calc_columns(base, ypl):
return (-columns[0], -columns[1], -columns[2])
class InfoCommand(YumCommand):
+
+ created = 1159277098
+
def getNames(self):
return ['info']
@@ -340,6 +352,9 @@ class InfoCommand(YumCommand):
return True
class ListCommand(InfoCommand):
+
+ created = 1159277098
+
def getNames(self):
return ['list']
@@ -348,7 +363,9 @@ class ListCommand(InfoCommand):
class EraseCommand(YumCommand):
-
+
+ created = 1159277098
+
def getNames(self):
return ['erase', 'remove']
@@ -376,6 +393,9 @@ class EraseCommand(YumCommand):
return True
class GroupCommand(YumCommand):
+
+ created = 1159277098
+
def doCommand(self, base, basecmd, extcmds):
self.doneCommand(base, _("Setting up Group Process"))
@@ -479,6 +499,8 @@ class GroupInfoCommand(GroupCommand):
class MakeCacheCommand(YumCommand):
+ created = 1159277098
+
def getNames(self):
return ['makecache']
@@ -520,7 +542,9 @@ class MakeCacheCommand(YumCommand):
return False
class CleanCommand(YumCommand):
-
+
+ created = 1159277098
+
def getNames(self):
return ['clean']
@@ -541,6 +565,9 @@ class CleanCommand(YumCommand):
return False
class ProvidesCommand(YumCommand):
+
+ created = 1159277098
+
def getNames(self):
return ['provides', 'whatprovides']
@@ -561,6 +588,9 @@ class ProvidesCommand(YumCommand):
return 1, [str(e)]
class CheckUpdateCommand(YumCommand):
+
+ created = 1159277098
+
def getNames(self):
return ['check-update']
@@ -610,6 +640,9 @@ class CheckUpdateCommand(YumCommand):
return result, []
class SearchCommand(YumCommand):
+
+ created = 1159277098
+
def getNames(self):
return ['search']
@@ -633,6 +666,9 @@ class SearchCommand(YumCommand):
return False
class UpgradeCommand(YumCommand):
+
+ created = 1159277098
+
def getNames(self):
return ['upgrade']
@@ -655,6 +691,9 @@ class UpgradeCommand(YumCommand):
return 1, [str(e)]
class LocalInstallCommand(YumCommand):
+
+ created = 1159277098
+
def getNames(self):
return ['localinstall', 'localupdate']
@@ -682,6 +721,9 @@ class LocalInstallCommand(YumCommand):
return False
class ResolveDepCommand(YumCommand):
+
+ created = 1159277098
+
def getNames(self):
return ['resolvedep']
@@ -699,6 +741,9 @@ class ResolveDepCommand(YumCommand):
return 1, [str(e)]
class ShellCommand(YumCommand):
+
+ created = 1159277098
+
def getNames(self):
return ['shell']
@@ -723,6 +768,9 @@ class ShellCommand(YumCommand):
class DepListCommand(YumCommand):
+
+ created = 1159277098
+
def getNames(self):
return ['deplist']
@@ -744,6 +792,8 @@ class DepListCommand(YumCommand):
class RepoListCommand(YumCommand):
+
+ created = 1178147972
def getNames(self):
return ('repolist',)
@@ -932,6 +982,24 @@ class RepoListCommand(YumCommand):
class HelpCommand(YumCommand):
+ created = 1201218844
+
+ @staticmethod
+ def _key_created(cmd):
+ if hasattr(cmd, 'created'):
+ return cmd.created
+ return 0
+ @staticmethod
+ def _key_summary(cmd):
+ if hasattr(cmd, 'getSummary'):
+ return cmd.getSummary()
+ return ''
+ @staticmethod
+ def _key_usage(cmd):
+ if hasattr(cmd, 'getUsage'):
+ return cmd.getUsage()
+ return ''
+
def getNames(self):
return ['help']
@@ -945,6 +1013,10 @@ class HelpCommand(YumCommand):
if len(extcmds) == 0:
base.usage()
raise cli.CliError
+ elif len(extcmds) in (1, 2) and extcmds[0] == 'recent':
+ pass
+ elif len(extcmds) == 2 and extcmds[0] in ('cmd', 'command'):
+ pass
elif len(extcmds) > 1 or extcmds[0] not in base.yum_cli_commands:
base.usage()
raise cli.CliError
@@ -986,6 +1058,50 @@ class HelpCommand(YumCommand):
return help_output
def doCommand(self, base, basecmd, extcmds):
+ if extcmds[0] == 'recent':
+ last = "4"
+ if len(extcmds) > 1:
+ last = extcmds[1]
+ last = int(last)
+ if last < 1:
+ last = 1
+
+ cols = base.term.columns
+ print base.fmtSection("%d most recent command(s)" % (last,))
+ colname = _("Command")
+ maxname = utf8_width(colname)
+ cur = 0
+ for cmd in sorted(base.yum_cli_commands.values(), reverse=True,
+ key=self._key_created):
+ if maxname < len(cmd.getNames()[0]):
+ maxname = len(cmd.getNames()[0])
+ cur += 1
+ if cur >= last:
+ break
+ print "%s %s %s" % (utf8_width_fill(colname, maxname),
+ utf8_width_fill(_("Created"), 10, 10),
+ _("Summary"))
+ cur = 0
+ for cmd in sorted(base.yum_cli_commands.values(), reverse=True,
+ key=self._key_created):
+ name = cmd.getNames()[0]
+ created = self._key_created(cmd)
+ if not created:
+ created = 'N/A' # Not i18n, because it should be fixed
+ else:
+ created = time.strftime("%Y-%m-%d", time.localtime(created))
+ summary = self._key_summary(cmd)
+ text = "%s %-10s " % (utf8_width_fill(name, maxname), created)
+ print utf8_text_fill(summary, width=cols, initial_indent=text,
+ subsequent_indent=' ' * utf8_width(text))
+
+ cur += 1
+ if cur >= last:
+ break
+ return 0, []
+
+ if extcmds[0] in ('cmd', 'command'):
+ extcmds = extcmds[1:]
if base.yum_cli_commands.has_key(extcmds[0]):
command = base.yum_cli_commands[extcmds[0]]
base.verbose_logger.log(logginglevels.INFO_2,
@@ -996,6 +1112,9 @@ class HelpCommand(YumCommand):
return False
class ReInstallCommand(YumCommand):
+
+ created = 1202159716
+
def getNames(self):
return ['reinstall']
@@ -1022,6 +1141,9 @@ class ReInstallCommand(YumCommand):
return False
class DowngradeCommand(YumCommand):
+
+ created = 1237904399
+
def getNames(self):
return ['downgrade']
@@ -1048,6 +1170,9 @@ class DowngradeCommand(YumCommand):
class VersionCommand(YumCommand):
+
+ created = 1242925190
+
def getNames(self):
return ['version']
More information about the Yum-commits
mailing list